diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 0d096207..2bb66572 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -56,6 +56,19 @@ the `--exit` and `--reconfigure` options use. *-s, --startup* Run command on startup +*-S, --session* + Run command on startup and terminate compositor on exit. This is useful + for session management as it allows the session client to terminate + labwc by exiting itself. This is a Wayland specific use-case because + under X, xinit starts the server and keeps it alive for as long as the + session client. Thus either the session client starts the Window + Manager, or the Window Manager can be launched independently first. On + Wayland, the Compositor is both Display Server and Window Manager, so + the described session management mechanisms do not work because the + Compositor needs to be running before the session can function. As some + session clients support both X11 and Wayland, this command line option + avoids re-writes and fragmentation. + *-v, --version* Show the version number and quit diff --git a/include/common/spawn.h b/include/common/spawn.h index 28557482..d864e953 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -4,6 +4,12 @@ #include +/** + * spawn_primary_client - execute asynchronously + * @command: command to be executed + */ +pid_t spawn_primary_client(const char *command); + /** * spawn_async_no_shell - execute asynchronously * @command: command to be executed diff --git a/include/labwc.h b/include/labwc.h index 482ce35f..e0790727 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -348,6 +348,8 @@ struct server { struct menu *menu_current; struct wl_list menus; + + pid_t primary_client_pid; }; #define LAB_NR_LAYERS (4) diff --git a/src/common/spawn.c b/src/common/spawn.c index 8c0e10ba..9d729e22 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -25,11 +25,22 @@ reset_signals_and_limits(void) signal(SIGPIPE, SIG_DFL); } -static void +static bool set_cloexec(int fd) { int flags = fcntl(fd, F_GETFD); - fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + if (flags == -1) { + wlr_log_errno(WLR_ERROR, + "Unable to set the CLOEXEC flag: fnctl failed"); + return false; + } + flags = flags | FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) { + wlr_log_errno(WLR_ERROR, + "Unable to set the CLOEXEC flag: fnctl failed"); + return false; + } + return true; } void @@ -79,6 +90,41 @@ out: g_strfreev(argv); } +pid_t +spawn_primary_client(const char *command) +{ + assert(command); + + GError *err = NULL; + gchar **argv = NULL; + + /* Use glib's shell-parse to mimic Openbox's behaviour */ + g_shell_parse_argv((gchar *)command, NULL, &argv, &err); + if (err) { + g_message("%s", err->message); + g_error_free(err); + return -1; + } + + pid_t child = fork(); + switch (child) { + case -1: + wlr_log_errno(WLR_ERROR, "Failed to fork"); + g_strfreev(argv); + return -1; + case 0: + /* child */ + close(STDIN_FILENO); + reset_signals_and_limits(); + execvp(argv[0], argv); + wlr_log_errno(WLR_ERROR, "Failed to execute primary client %s", command); + _exit(1); + default: + g_strfreev(argv); + return child; + } +} + pid_t spawn_piped(const char *command, int *pipe_fd) { diff --git a/src/main.c b/src/main.c index 35b2f3fa..1860efa5 100644 --- a/src/main.c +++ b/src/main.c @@ -24,6 +24,7 @@ static const struct option long_options[] = { {"merge-config", no_argument, NULL, 'm'}, {"reconfigure", no_argument, NULL, 'r'}, {"startup", required_argument, NULL, 's'}, + {"session", required_argument, NULL, 'S'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {0, 0, 0, 0} @@ -39,6 +40,7 @@ static const char labwc_usage[] = " -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" " -r, --reconfigure Reload the compositor configuration\n" " -s, --startup Run command on startup\n" +" -S, --session Run command on startup and terminate on exit\n" " -v, --version Show version number and quit\n" " -V, --verbose Enable more verbose logging\n"; @@ -87,12 +89,13 @@ main(int argc, char *argv[]) textdomain(GETTEXT_PACKAGE); #endif char *startup_cmd = NULL; + char *primary_client = NULL; enum wlr_log_importance verbosity = WLR_ERROR; int c; while (1) { int index = 0; - c = getopt_long(argc, argv, "c:C:dehmrs:vV", long_options, &index); + c = getopt_long(argc, argv, "c:C:dehmrs:S:vV", long_options, &index); if (c == -1) { break; } @@ -118,6 +121,9 @@ main(int argc, char *argv[]) case 's': startup_cmd = optarg; break; + case 'S': + primary_client = optarg; + break; case 'v': printf("labwc " LABWC_VERSION "\n"); exit(0); @@ -171,6 +177,16 @@ main(int argc, char *argv[]) menu_init(&server); + /* Start session-manager if one is specified by -S|--session */ + if (primary_client) { + server.primary_client_pid = spawn_primary_client(primary_client); + if (server.primary_client_pid < 0) { + wlr_log(WLR_ERROR, "fatal error starting primary client: %s", + primary_client); + goto out; + } + } + session_autostart_init(&server); if (startup_cmd) { spawn_async_no_shell(startup_cmd); @@ -178,6 +194,7 @@ main(int argc, char *argv[]) wl_display_run(server.wl_display); +out: session_shutdown(&server); server_finish(&server); diff --git a/src/server.c b/src/server.c index 4210f069..962fd5d5 100644 --- a/src/server.c +++ b/src/server.c @@ -90,6 +90,7 @@ handle_sigchld(int signal, void *data) { siginfo_t info; info.si_pid = 0; + struct server *server = data; /* First call waitid() with NOWAIT which doesn't consume the zombie */ if (waitid(P_ALL, /*id*/ 0, &info, WEXITED | WNOHANG | WNOWAIT) == -1) { @@ -102,8 +103,7 @@ handle_sigchld(int signal, void *data) } #if HAVE_XWAYLAND - /* Verify that we do not break xwayland lazy initialization */ - struct server *server = data; + /* Ensure that we do not break xwayland lazy initialization */ if (server->xwayland && server->xwayland->server && info.si_pid == server->xwayland->server->pid) { return 0; @@ -139,6 +139,11 @@ handle_sigchld(int signal, void *data) " please report", (long)info.si_pid, info.si_code); } + if (info.si_pid == server->primary_client_pid) { + wlr_log(WLR_INFO, "primary client %ld exited", (long)info.si_pid); + wl_display_terminate(server->wl_display); + } + return 0; } @@ -290,6 +295,7 @@ get_headless_backend(struct wlr_backend *backend, void *data) void server_init(struct server *server) { + server->primary_client_pid = -1; server->wl_display = wl_display_create(); if (!server->wl_display) { wlr_log(WLR_ERROR, "cannot allocate a wayland display");