From c841a25acf9a1973dd1c2c87f09a5d31b11568c8 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Sat, 9 Mar 2024 17:12:54 +0000 Subject: [PATCH] Add -S|--session option ...to start on startup and to terminate the compositor when exits. This is useful for session management as it allows the session client (for example `lxqt-session`) to terminate labwc - be exiting itself. Under X, xinit starts the server and keeps it alive for as long as lxqt-session runs. 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. Co-authored-by: @Consolatis --- docs/labwc.1.scd | 13 +++++++++++ include/common/spawn.h | 6 +++++ include/labwc.h | 2 ++ src/common/spawn.c | 50 ++++++++++++++++++++++++++++++++++++++++-- src/main.c | 19 +++++++++++++++- src/server.c | 10 +++++++-- 6 files changed, 95 insertions(+), 5 deletions(-) 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");