-
Notifications
You must be signed in to change notification settings - Fork 627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DOC] make systemd-run example more reliable #2048
base: next
Are you sure you want to change the base?
Conversation
This is a follow-up to davatorium#1752 If the `{cmd}` had spaces in it, it would error out with something like Failed to find executable gui-22345: No such file or directory (In this case the command was `/nix/store/6jlqjighx0v0lqx9bahc7nl6npshim2r-lact-0.5.6/bin/lact gui`) This fixes that. What this also fixes is that this would result in incredibly long names such as `--unit=app-rofi--nix-store-6jlqjighx0v0lqx9bahc7nl6npshim2r\x2dlact\x2d0.5.6-bin-lact` because the desktop entry contained the full path which can be quite long in Nix and is quite common in Nix packaging. We only really care about the executable name here. The current iteration still has command arguments in its name but getting rid of those would be quite bothersome in bash. IME apps also usually don't have command line args and if they do it's usually something short like in the example above. This could certainly be improved though.
Aaaand I already found something that breaks this: The emacsclient desktop file does this:
Ugh. What we really want here is the desktop file's name. |
We could pass the desktop file full path ? would that help? |
That'd be immensely helpful indeed. We only need the basename but that's easy enough to extract as shown here. Although getting the name without the |
A quick test, will this work? diff --git a/include/helper.h b/include/helper.h
index ed161931..6e9a28f1 100644
--- a/include/helper.h
+++ b/include/helper.h
@@ -330,6 +330,23 @@ gboolean helper_execute_command(const char *wd, const char *cmd,
gboolean run_in_term,
RofiHelperExecuteContext *context);
+/**
+ * @param wd The work directory (optional)
+ * @param cmd The cmd to execute
+ * @param run_in_term Indicate if command should be run in a terminal
+ * @param context The startup notification context, if any
+ * @param ... tuples of extra parameters the string can search/replace
+ *
+ * Execute command.
+ * If needed members of context are NULL, they will be filled.
+ * Pass {cmd} into the va-arg list.
+ *
+ * @returns FALSE On failure, TRUE on success
+ */
+gboolean helper_execute_command_full(const char *wd, const char *cmd,
+ gboolean run_in_term,
+ RofiHelperExecuteContext *context, ...);
+
/**
* @param file The file path
* @param height The wanted height
diff --git a/source/helper.c b/source/helper.c
index 53f366bf..0bf417c6 100644
--- a/source/helper.c
+++ b/source/helper.c
@@ -72,7 +72,8 @@ void cmd_set_arguments(int argc, char **argv) {
stored_argv = argv;
}
-int helper_parse_setup(char *string, char ***output, int *length, ...) {
+static int helper_parse_setup_v(char *string, char ***output, int *length,
+ va_list ap) {
GError *error = NULL;
GHashTable *h;
h = g_hash_table_new(g_str_hash, g_str_equal);
@@ -80,8 +81,6 @@ int helper_parse_setup(char *string, char ***output, int *length, ...) {
g_hash_table_insert(h, "{terminal}", config.terminal_emulator);
g_hash_table_insert(h, "{ssh-client}", config.ssh_client);
// Add list from variable arguments.
- va_list ap;
- va_start(ap, length);
while (1) {
char *key = va_arg(ap, char *);
if (key == (char *)0) {
@@ -93,7 +92,6 @@ int helper_parse_setup(char *string, char ***output, int *length, ...) {
}
g_hash_table_insert(h, key, value);
}
- va_end(ap);
char *res = helper_string_replace_if_exists_v(string, h);
// Destroy key-value storage.
@@ -115,6 +113,13 @@ int helper_parse_setup(char *string, char ***output, int *length, ...) {
}
return FALSE;
}
+int helper_parse_setup(char *string, char ***output, int *length, ...) {
+ va_list ap;
+ va_start(ap, length);
+ int retv = helper_parse_setup_v(string, output, length, ap);
+ va_end(ap);
+ return retv;
+}
void helper_tokenize_free(rofi_int_matcher **tokens) {
for (size_t i = 0; tokens && tokens[i]; i++) {
@@ -1027,15 +1032,21 @@ gboolean helper_execute(const char *wd, char **args, const char *error_precmd,
gboolean helper_execute_command(const char *wd, const char *cmd,
gboolean run_in_term,
RofiHelperExecuteContext *context) {
+ return helper_execute_command_full(wd, cmd, run_in_term, context, "{cmd}",
+ cmd, (char *)0);
+}
+
+static gboolean helper_execute_command_full_v(const char *wd, const char *cmd,
+ gboolean run_in_term,
+ RofiHelperExecuteContext *context,
+ va_list ap) {
char **args = NULL;
int argc = 0;
if (run_in_term) {
- helper_parse_setup(config.run_shell_command, &args, &argc, "{cmd}", cmd,
- (char *)0);
+ helper_parse_setup_v(config.run_shell_command, &args, &argc, ap);
} else {
- helper_parse_setup(config.run_command, &args, &argc, "{cmd}", cmd,
- (char *)0);
+ helper_parse_setup_v(config.run_command, &args, &argc, ap);
}
if (args == NULL) {
@@ -1063,10 +1074,19 @@ gboolean helper_execute_command(const char *wd, const char *cmd,
return helper_execute(wd, args, "", cmd, context);
}
+gboolean helper_execute_command_full(const char *wd, const char *cmd,
+ gboolean run_in_term,
+ RofiHelperExecuteContext *context, ...) {
+ va_list ap;
+ va_start(ap, context);
+ gboolean retv =
+ helper_execute_command_full_v(wd, cmd, run_in_term, context, ap);
+ va_end(ap);
+ return retv;
+}
char *helper_get_theme_path(const char *file, const char **ext,
const char *parent_file) {
-
char *filename = rofi_expand_path(file);
g_debug("Opening theme, testing: %s\n", filename);
if (g_path_is_absolute(filename)) {
diff --git a/source/modes/drun.c b/source/modes/drun.c
index c18d8f95..bdfebfb9 100644
--- a/source/modes/drun.c
+++ b/source/modes/drun.c
@@ -402,7 +402,10 @@ static void exec_cmd_entry(DRunModeEntry *e, const char *path) {
// terminal.
gboolean terminal =
g_key_file_get_boolean(e->key_file, e->action, "Terminal", NULL);
- if (helper_execute_command(exec_path, fp, terminal, sn ? &context : NULL)) {
+ if (helper_execute_command_full(exec_path, fp, terminal, sn ? &context : NULL,
+ "{cmd}", fp, "{desktop_file_path}", e->path,
+ "{app_id}", e->app_id, "{desktop_id}",
+ e->desktop_id, (char *)0)) {
char *drun_cach_path = g_build_filename(cache_dir, DRUN_CACHE_FILE, NULL);
// Store it based on the unique identifiers (desktop_id).
history_set(drun_cach_path, e->desktop_id); This adds:
Its completely untested. |
It is no longer completely untested. It works! Really well actually. Rofi is now able to launch all of my apps without issue using:
Thank you so much!
Though with the slight caveat that I'm using the wayland fork based on f2c0f75. It does not apply atop the Nixpkgs package of this repo ( Should I add your patch as a commit to this branch or do you just want to do that yourself? |
This requires a patched rofi from davatorium/rofi#2048 (comment)
Feel free to add the patch, can you update the documentation with the entries from this patch? |
This adds: * `{desktop_file_path}` * `{app_id}` // filename without .desktop * `{desktop_id}` // Path with all / replaced by -. -> https://specifications.freedesktop.org/desktop-entry-spec/latest/file-naming.html These allow the user to use i.e. just the app name (based on its desktop file name) for purposes such as running it as part of an ad-hoc systemd user service via systemd-run. Tested-by: Atemu <[email protected]> (This was posted as a patch in davatorium#2048 (comment).)
f2ffb5a
to
0fecb6a
Compare
Hm, unfortunately I found something that breaks.
That seems misguided but it should work. However
It works in regular rofi execution. It also works using I still haven't quite understood why this happens. This PR's recommendation isn't a regression compared to status quo though as that also fails but with a different error:
|
Another pitfall I just noticed is that this is not compatible with I'll make the |
thanks,forgot to reopen yesterday. |
helper_parse_setup(config.run_shell_command, &args, &argc, "{cmd}", cmd, | ||
(char *)0); | ||
helper_parse_setup_v(config.run_shell_command, &args, &argc, ap); | ||
} else { | ||
helper_parse_setup(config.run_command, &args, &argc, "{cmd}", cmd, | ||
(char *)0); | ||
helper_parse_setup_v(config.run_command, &args, &argc, ap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed this: aren't these flipped now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run_in terminal calls the 'run_shell_command'.. I think this is ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a clue how this code actually works, I only noticed that the _v
-suffixed version is now called in the other conditional.
@@ -609,7 +609,7 @@ Example to run applications in a dedicated cgroup with systemd. Requires a | |||
shell to escape and interpolate the unit name correctly. | |||
|
|||
```bash | |||
"bash -c 'systemd-run --user --unit=app-rofi-\$(systemd-escape {cmd})-\$RANDOM {cmd}'" | |||
"bash -c 'systemd-run --user [--unit=app-rofi-\"\$(systemd-escape \"{app_id}\")\"-\$RANDOM] {cmd}'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it's nice to mark this as optional, that doesn't actually help anyone because you can only have one or the other but both drun and run could be used.
What we actually need is some sort of identifier that is unit-name-safe, represents what is being ran decently well and works with both drun and run.
For desktop applications, I see the desktop_id fulfilling that role. Though I'd prefer the .desktop
suffix be stripped.
For commands, I see the (escaped) executable name as the best candidate. Of course it'd "break" (as in: not represent what is being ran) if you ran sh -c ...
or something but at that point you're explicitly doing that.
Perhaps it'd be best to do that within rofi and provide an abstract execution_id
or something that works like I described.
Perhaps it'd be even better if we had run-as-system service functionality built right into rofi?
Executing systemd-run
like this in -run-command
really isn't very robust as I've discovered with scrcpy, so perhaps "native" handling would be better.
I'm really not sure how to proceed with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know.. I never use systemd-run. What does 'run-as-system' service entail? do you have a link to some documentation?
(we did add the DBusActivatable to latest rofi).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means that any app launched is started as an ad-hoc systemd unit. For example the firefox I'm typing this in runs as the app-rofi-firefox-24354.service
user unit.
What this means in practice is that the initial process aswell as every process ever spawned it will be run in a cgroup that is separate from all other apps and you can apply any cgroup-based policy to it if you like.
This is most useful for stopping units (e.g. early OOM) but you could in theory do anything cgroups with this.
It also brings with it all of systemd's accounting and centralised log collection. See e.g. this example output:
$ systemctl --user status app-rofi-firefox-24354.service
● app-rofi-firefox-24354.service - [systemd-run] /run/current-system/sw/bin/firefox --name firefox
Loaded: loaded (/run/user/1000/systemd/transient/app-rofi-firefox-24354.service; transient)
Transient: yes
Active: active (running) since Wed 2025-01-29 14:04:39 CET; 1 day 8h ago
Invocation: 06509bffe1dc46f79fafe6a952641d60
Main PID: 437580 (.firefox-wrappe)
Tasks: 594 (limit: 37644)
Memory: 3.4G (peak: 3.8G, swap: 833M, swap peak: 1.8G, zswap: 231.6M)
CPU: 1h 54min 32.764s
CGroup: /user.slice/user-1000.slice/[email protected]/app.slice/app-rofi-firefox-24354.service
├─437580 /nix/store/kbin6wx8bciz7c50i4y93n35wfqnrfzs-firefox-134.0.2/bin/.firefox-wrapped --name firefox
├─437678 /nix/store/kbin6wx8bciz7c50i4y93n35wfqnrfzs-firefox-134.0.2/lib/firefox/firefox -contentproc ...
...
Jan 30 19:56:22 THESEUS firefox[437699]: Failed to create /Users for shader cache (Permission denied)---disabling.
Jan 30 19:56:23 THESEUS firefox[437699]: Failed to create /Users for shader cache (Permission denied)---disabling.
...
I can see that the "firefox app" I launched currently uses 3.4GiB and has 833M in swap across all of its processes. I can also see all the processes Firefox has spawned. I can also see this app launch instance's logs in isolation.
Theoretically I could set e.g. memory or swap limits here though I'm not currently doing that.
Setting a memory limit without running firefox in its own cgroup would be very hard indeed and even finding out how much memory it's using usually isn't trivial with such multi-process applications. cgroups make everything simpler here.
AFAIK the big DEs' launchers also run every app launched within an ad hoc systemd unit but I don't use those, so I can't tell you how it works there.
There isn't really documentation on this other than standard systemd (user) service/unit docs and cgroup docs. There isn't really much to it other than simply using these systems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know what it does, but not how to do this from the rofi? Is there a dbus API I can call to launch the service?
This is a follow-up to #1752
If the
{cmd}
had spaces in it, it would error out with something like(In this case the command was
/nix/store/6jlqjighx0v0lqx9bahc7nl6npshim2r-lact-0.5.6/bin/lact gui
)This fixes that.
What this also fixes is that this would result in incredibly long names such as
--unit=app-rofi--nix-store-6jlqjighx0v0lqx9bahc7nl6npshim2r\x2dlact\x2d0.5.6-bin-lact
because the desktop entry contained the full path which can be quite long in Nix and is quite common in Nix packaging.We only really care about the executable name here.
The current iteration still has command arguments in its name but getting rid of those would be quite bothersome in bash. IME apps also usually don't have command line args and if they do it's usually something short like in the example above. This could certainly be improved though.
cc @mweinelt