From bbfbd28dfa0d6bc15203637e43be75b7c127eb29 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Oct 2011 22:18:17 +0100 Subject: [PATCH 1/5] Add a --no-startup-id flag for exec (command), exec (config), exec_always (config) --- include/data.h | 3 ++ include/startup.h | 5 ++- src/cfgparse.y | 26 +++++++++++++-- src/cmdparse.y | 11 +++++-- src/main.c | 6 ++-- src/startup.c | 83 ++++++++++++++++++++++++++--------------------- 6 files changed, 89 insertions(+), 45 deletions(-) diff --git a/include/data.h b/include/data.h index cd58ec26..740278ae 100644 --- a/include/data.h +++ b/include/data.h @@ -218,6 +218,9 @@ struct Binding { struct Autostart { /** Command, like in command mode */ char *command; + /** no_startup_id flag for start_application(). Determines whether a + * startup notification context/ID should be created. */ + bool no_startup_id; TAILQ_ENTRY(Autostart) autostarts; TAILQ_ENTRY(Autostart) autostarts_always; }; diff --git a/include/startup.h b/include/startup.h index 9d110c6d..290c8d21 100644 --- a/include/startup.h +++ b/include/startup.h @@ -25,8 +25,11 @@ * The shell is determined by looking for the SHELL environment variable. If * it does not exist, /bin/sh is used. * + * The no_startup_id flag determines whether a startup notification context + * (and ID) should be created, which is the default and encouraged behavior. + * */ -void start_application(const char *command); +void start_application(const char *command, bool no_startup_id); /** * Called by libstartup-notification when something happens diff --git a/src/cfgparse.y b/src/cfgparse.y index 7cd0ce9b..2e0bbe0b 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1507,8 +1507,19 @@ restart_state: exec: TOKEXEC STR { + char *command = $2; + bool no_startup_id = false; + if (strncasecmp($2, "--no-startup-id ", strlen("--no-startup-id ")) == 0) { + no_startup_id = true; + /* We need to make a copy here, otherwise we leak the + * --no-startup-id bytes in the beginning of the string */ + command = sstrdup(command + strlen("--no-startup-id ")); + free($2); + } + struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $2; + new->command = command; + new->no_startup_id = no_startup_id; TAILQ_INSERT_TAIL(&autostarts, new, autostarts); } ; @@ -1516,8 +1527,19 @@ exec: exec_always: TOKEXEC_ALWAYS STR { + char *command = $2; + bool no_startup_id = false; + if (strncasecmp($2, "--no-startup-id ", strlen("--no-startup-id ")) == 0) { + no_startup_id = true; + /* We need to make a copy here, otherwise we leak the + * --no-startup-id bytes in the beginning of the string */ + command = sstrdup(command + strlen("--no-startup-id ")); + free($2); + } + struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $2; + new->command = command; + new->no_startup_id = no_startup_id; TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always); } ; diff --git a/src/cmdparse.y b/src/cmdparse.y index ab26fa59..3eb33426 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -389,8 +389,15 @@ operation: exec: TOK_EXEC STR { - printf("should execute %s\n", $2); - start_application($2); + char *command = $2; + bool no_startup_id = false; + if (strncasecmp($2, "--no-startup-id ", strlen("--no-startup-id ")) == 0) { + no_startup_id = true; + command += strlen("--no-startup-id "); + } + + printf("should execute %s, no_startup_id = %d\n", command, no_startup_id); + start_application(command, no_startup_id); free($2); } ; diff --git a/src/main.c b/src/main.c index 52c9e2cd..3cbdfca5 100644 --- a/src/main.c +++ b/src/main.c @@ -635,7 +635,7 @@ int main(int argc, char *argv[]) { struct Autostart *exec; TAILQ_FOREACH(exec, &autostarts, autostarts) { LOG("auto-starting %s\n", exec->command); - start_application(exec->command); + start_application(exec->command, exec->no_startup_id); } } @@ -643,7 +643,7 @@ int main(int argc, char *argv[]) { struct Autostart *exec_always; TAILQ_FOREACH(exec_always, &autostarts_always, autostarts_always) { LOG("auto-starting (always!) %s\n", exec_always->command); - start_application(exec_always->command); + start_application(exec_always->command, exec_always->no_startup_id); } /* Start i3bar processes for all configured bars */ @@ -653,7 +653,7 @@ int main(int argc, char *argv[]) { sasprintf(&command, "i3bar --bar_id=%s --socket=\"%s\"", barconfig->id, current_socketpath); LOG("Starting bar process: %s\n", command); - start_application(command); + start_application(command, true); free(command); } diff --git a/src/startup.c b/src/startup.c index 6a98f3cd..bddf7da1 100644 --- a/src/startup.c +++ b/src/startup.c @@ -63,44 +63,50 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { * The shell is determined by looking for the SHELL environment variable. If it * does not exist, /bin/sh is used. * + * The no_startup_id flag determines whether a startup notification context + * (and ID) should be created, which is the default and encouraged behavior. + * */ -void start_application(const char *command) { - /* Create a startup notification context to monitor the progress of this - * startup. */ +void start_application(const char *command, bool no_startup_id) { SnLauncherContext *context; - context = sn_launcher_context_new(sndisplay, conn_screen); - sn_launcher_context_set_name(context, "i3"); - sn_launcher_context_set_description(context, "exec command in i3"); - /* Chop off everything starting from the first space (if there are any - * spaces in the command), since we don’t want the parameters. */ - char *first_word = sstrdup(command); - char *space = strchr(first_word, ' '); - if (space) - *space = '\0'; - sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); - free(first_word); - /* Trigger a timeout after 60 seconds */ - struct ev_timer *timeout = scalloc(sizeof(struct ev_timer)); - ev_timer_init(timeout, startup_timeout, 60.0, 0.); - timeout->data = context; - ev_timer_start(main_loop, timeout); + if (!no_startup_id) { + /* Create a startup notification context to monitor the progress of this + * startup. */ + context = sn_launcher_context_new(sndisplay, conn_screen); + sn_launcher_context_set_name(context, "i3"); + sn_launcher_context_set_description(context, "exec command in i3"); + /* Chop off everything starting from the first space (if there are any + * spaces in the command), since we don’t want the parameters. */ + char *first_word = sstrdup(command); + char *space = strchr(first_word, ' '); + if (space) + *space = '\0'; + sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); + free(first_word); - LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); + /* Trigger a timeout after 60 seconds */ + struct ev_timer *timeout = scalloc(sizeof(struct ev_timer)); + ev_timer_init(timeout, startup_timeout, 60.0, 0.); + timeout->data = context; + ev_timer_start(main_loop, timeout); - /* Save the ID and current workspace in our internal list of startup - * sequences */ - Con *ws = con_get_workspace(focused); - struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence)); - sequence->id = sstrdup(sn_launcher_context_get_startup_id(context)); - sequence->workspace = sstrdup(ws->name); - sequence->context = context; - TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences); + LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); - /* Increase the refcount once (it starts with 1, so it will be 2 now) for - * the timeout. Even if the sequence gets completed, the timeout still - * needs the context (but will unref it then) */ - sn_launcher_context_ref(context); + /* Save the ID and current workspace in our internal list of startup + * sequences */ + Con *ws = con_get_workspace(focused); + struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence)); + sequence->id = sstrdup(sn_launcher_context_get_startup_id(context)); + sequence->workspace = sstrdup(ws->name); + sequence->context = context; + TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences); + + /* Increase the refcount once (it starts with 1, so it will be 2 now) for + * the timeout. Even if the sequence gets completed, the timeout still + * needs the context (but will unref it then) */ + sn_launcher_context_ref(context); + } LOG("executing: %s\n", command); if (fork() == 0) { @@ -108,7 +114,8 @@ void start_application(const char *command) { setsid(); if (fork() == 0) { /* Setup the environment variable(s) */ - sn_launcher_context_setup_child_process(context); + if (!no_startup_id) + sn_launcher_context_setup_child_process(context); /* Stores the path of the shell */ static const char *shell = NULL; @@ -125,10 +132,12 @@ void start_application(const char *command) { } wait(0); - /* Change the pointer of the root window to indicate progress */ - if (xcursor_supported) - xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH); - else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH); + if (!no_startup_id) { + /* Change the pointer of the root window to indicate progress */ + if (xcursor_supported) + xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH); + else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH); + } } /* From 15f36cdc91cbfaca9bf4812b4b91ac269bbf9776 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Oct 2011 22:18:40 +0100 Subject: [PATCH 2/5] docs/userguide: document the --no-startup-id flag --- docs/userguide | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/userguide b/docs/userguide index 6db4d5e7..c40b6676 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1034,6 +1034,35 @@ The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on how to use them. +=== Executing applications (exec) + +What good is a window manager if you can’t actually start any applications? +The exec command starts an application by passing the command you specify to a +shell. This implies that you can use globbing (wildcards) and programs will be +searched in your $PATH. + +*Syntax*: +------------------------------ +exec [--no-startup-id] command +------------------------------ + +*Example*: +------------------------------ +# Start the GIMP +bindsym mod+g exec gimp + +# Start the terminal emulator urxvt which is not yet startup-notification-aware +bindsym mod+enter exec --no-startup-id urxvt +------------------------------ + +The +--no-startup-id+ parameter disables startup-notification support for this +particular exec command. With startup-notification, i3 can make sure that a +window appears on the workspace on which you used the exec command. Also, it +will change the X11 cursor to +watch+ (a clock) while the application is +launching. So, if an application is not startup-notification aware (most GTK +and Qt using applications seem to be, though), you will end up with a watch +cursor for 60 seconds. + === Splitting containers The split command makes the current window a split container. Split containers From 73d4737e42bbd46abfcc043fffe12ac2d3ee18be Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Oct 2011 22:21:09 +0100 Subject: [PATCH 3/5] docs/userguide: document the --no-startup-id flag for exec directives in the config --- docs/userguide | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index c40b6676..851d66c3 100644 --- a/docs/userguide +++ b/docs/userguide @@ -541,16 +541,21 @@ keyword. These commands will be run in order. *Syntax*: ------------------- -exec command -exec_always command +exec [--no-startup-id] command +exec_always [--no-startup-id] command ------------------- *Examples*: -------------------------------- exec chromium exec_always ~/my_script.sh + +# Execute the terminal emulator urxvt, which is not yet startup-notification aware. +exec --no-startup-id urxvt -------------------------------- +The flag --no-startup-id is explained in <>. + [[workspace_screen]] === Automatically putting workspaces on specific screens @@ -1034,6 +1039,8 @@ The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on how to use them. +[[exec]] + === Executing applications (exec) What good is a window manager if you can’t actually start any applications? From 91d589b17637fbd38f9b57a7fb075dcf881c5995 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Oct 2011 22:21:37 +0100 Subject: [PATCH 4/5] tests: extend t/175-startup-notification to test the --no-startup-id flag --- testcases/t/175-startup-notification.t | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index 58b3e0cd..635ee476 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -84,6 +84,8 @@ close($fh); unlink($tmp); +isnt($startup_id, '', 'startup_id not empty'); + $ENV{DESKTOP_STARTUP_ID} = $startup_id; # Create a new libstartup-notification launchee context @@ -132,4 +134,21 @@ sync_with_i3($x); my $otherwin = open_window($x); is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace'); +###################################################################### +# 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID +# environment variable. +###################################################################### + +mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp"; + +cmd qq|exec --no-startup-id echo \$DESKTOP_STARTUP_ID >$tmp|; + +open($fh, '<', $tmp); +chomp($startup_id = <$fh>); +close($fh); + +unlink($tmp); + +is($startup_id, '', 'startup_id empty'); + done_testing; From af6f8ca7c2e458a697ce667b6c984d56231e997d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Oct 2011 22:21:59 +0100 Subject: [PATCH 5/5] complete-run: clear the DESKTOP_STARTUP_ID environment variable before starting i3 --- testcases/lib/SocketActivation.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 11a672e3..1ecfb2aa 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -53,6 +53,7 @@ sub activate_i3 { if ($pid == 0) { $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; + delete $ENV{DESKTOP_STARTUP_ID}; $ENV{DISPLAY} = $args{display}; $ENV{PATH} = join(':', '../i3-nagbar',