From 2aae3a70b186c68f685d3a30d1cd2857c9953c11 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 13:41:10 +0800 Subject: [PATCH 01/10] feat: support daemon mode --- src/main.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 3b55fec8e..a0f60eb3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,8 +9,11 @@ #include #include #include +#include #include #include +#include +#include namespace { @@ -26,6 +29,7 @@ namespace { "Options:\n" " --help Show this help message\n" " --version Show version information\n" + " --daemon Run in background\n" "\n" "Subcommands:\n" " msg Send a command to the running instance\n" @@ -42,6 +46,47 @@ namespace { return -1; } + // daemonize + bool daemonize(pid_t* out_pid) { + pid_t pid = ::fork(); + if (pid < 0) { + std::perror("fork"); + return true; + } + + if (pid > 0) { + if (out_pid) + *out_pid = pid; + return true; + } + + if (::setsid() < 0) { + std::perror("setsid"); + _exit(1); + } + + pid = ::fork(); + if (pid < 0) { + std::perror("fork"); + _exit(1); + } + + if (pid > 0) { + _exit(0); + } + + int fd = ::open("/dev/null", O_RDWR); + if (fd != -1) { + ::dup2(fd, STDIN_FILENO); + ::dup2(fd, STDOUT_FILENO); + ::dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) + ::close(fd); + } + + return false; + } + int runShell() { if (IpcClient::isRunning()) { std::fputs("error: noctalia is already running\n", stderr); @@ -61,6 +106,22 @@ namespace { int main(int argc, char* argv[]) { std::setlocale(LC_ALL, ""); + + bool shouldDaemonize = false; + + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--daemon") == 0 || std::strcmp(argv[i], "-d") == 0) { + + shouldDaemonize = true; + + for (int j = i; j < argc - 1; ++j) { + argv[j] = argv[j + 1]; + } + --argc; + break; + } + } + if (argc >= 2) { if (std::strcmp(argv[1], "theme") == 0) return noctalia::theme::runCli(argc, argv); @@ -76,5 +137,18 @@ int main(int argc, char* argv[]) { return rc; } + if (IpcClient::isRunning()) { + std::fputs("error: noctalia is already running\n", stderr); + return 1; + } + + if (shouldDaemonize) { + pid_t pid; + if (daemonize(&pid)) { + std::printf("noctalia started [pid: %d]\n", pid); + return 0; + } + } + return runShell(); } From 882c3d2e8fd631935222d2174a09e359f9cbebca Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 13:53:36 +0800 Subject: [PATCH 02/10] feat: daemon set umask(0) and chdir('/') --- src/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index a0f60eb3d..853e56541 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,12 @@ namespace { _exit(1); } + ::umask(0); + if (::chdir("/") != 0) { + std::perror("chdir"); + _exit(1); + } + pid = ::fork(); if (pid < 0) { std::perror("fork"); From 3b68d05bddfceab1acbb3a6226f610cca8fe6ece Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:01:09 +0800 Subject: [PATCH 03/10] cli: error on unknown top-level options --- src/main.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 853e56541..0196cc8df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -139,9 +139,14 @@ int main(int argc, char* argv[]) { } for (int i = 1; i < argc; ++i) { - const int rc = runTopLevelFlag(argv[i]); - if (rc >= 0) - return rc; + if (argv[i][0] == '-') { + const int rc = runTopLevelFlag(argv[i]); + if (rc >= 0) + return rc; + + std::fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return 1; + } } if (IpcClient::isRunning()) { From 90b1108d199aac087b9cfe9c60dd318bf1cf82fd Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:04:59 +0800 Subject: [PATCH 04/10] feat: daemon improve chdir error message --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 0196cc8df..70cefcff0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,7 @@ namespace { ::umask(0); if (::chdir("/") != 0) { - std::perror("chdir"); + std::perror("chdir(\"/\")"); _exit(1); } From 18a98e0c731eaeadd786cf2559e3794a5613727d Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:16:50 +0800 Subject: [PATCH 05/10] feat(daemon): introduce SpawnResult and fix fork error handling --- src/main.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 70cefcff0..00a6ce93d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,8 @@ namespace { + enum class SpawnResult { Parent, Child, Error }; + int runTopLevelFlag(const char* flag) { if (std::strcmp(flag, "--version") == 0) { const std::string version = noctalia::build_info::displayVersion(); @@ -28,9 +30,9 @@ namespace { std::puts("Usage: noctalia [OPTIONS]\n" "\n" "Options:\n" - " --help Show this help message\n" - " --version Show version information\n" - " --daemon Run in background\n" + " --help Show this help message\n" + " --version Show version information\n" + " --daemon Run in background\n" "\n" "Subcommands:\n" " msg Send a command to the running instance\n" @@ -47,18 +49,17 @@ namespace { return -1; } - // daemonize - bool daemonize(pid_t* out_pid) { + SpawnResult daemonize(pid_t* out_pid) { pid_t pid = ::fork(); if (pid < 0) { - std::perror("fork"); - return true; + std::perror("fork (first)"); + return SpawnResult::Error; } if (pid > 0) { if (out_pid) *out_pid = pid; - return true; + return SpawnResult::Parent; } if (::setsid() < 0) { @@ -74,7 +75,7 @@ namespace { pid = ::fork(); if (pid < 0) { - std::perror("fork"); + std::perror("fork (second)"); _exit(1); } @@ -91,7 +92,7 @@ namespace { ::close(fd); } - return false; + return SpawnResult::Child; } int runShell() { @@ -118,9 +119,7 @@ int main(int argc, char* argv[]) { for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "--daemon") == 0 || std::strcmp(argv[i], "-d") == 0) { - shouldDaemonize = true; - for (int j = i; j < argc - 1; ++j) { argv[j] = argv[j + 1]; } @@ -155,8 +154,13 @@ int main(int argc, char* argv[]) { } if (shouldDaemonize) { - pid_t pid; - if (daemonize(&pid)) { + pid_t pid = -1; + SpawnResult result = daemonize(&pid); + + if (result == SpawnResult::Error) { + return 1; + } + if (result == SpawnResult::Parent) { std::printf("noctalia started [pid: %d]\n", pid); return 0; } From 5f5c23991aac5e4ed3efcd9b161f258e6b67fab8 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:20:01 +0800 Subject: [PATCH 06/10] feat(cli): add -h and -v short options and update help output --- src/main.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 00a6ce93d..4f6ab183f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,18 +21,18 @@ namespace { enum class SpawnResult { Parent, Child, Error }; int runTopLevelFlag(const char* flag) { - if (std::strcmp(flag, "--version") == 0) { + if (std::strcmp(flag, "--version") == 0 || std::strcmp(flag, "-v") == 0) { const std::string version = noctalia::build_info::displayVersion(); std::printf("noctalia %s\n", version.c_str()); return 0; } - if (std::strcmp(flag, "--help") == 0) { + if (std::strcmp(flag, "--help") == 0 || std::strcmp(flag, "-h") == 0) { std::puts("Usage: noctalia [OPTIONS]\n" "\n" "Options:\n" - " --help Show this help message\n" - " --version Show version information\n" - " --daemon Run in background\n" + " -h, --help Show this help message\n" + " -v, --version Show version information\n" + " -d, --daemon Run in background\n" "\n" "Subcommands:\n" " msg Send a command to the running instance\n" From f4f514d4be598e8af20b3dc9e98b6f78add93330 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:55:20 +0800 Subject: [PATCH 07/10] ci: add missing librsvg dependency for meson build --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d94c59fb8..8dd7fba13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,8 @@ jobs: cairo pango \ libxkbcommon \ sdbus-cpp libpipewire \ - pam curl libwebp + pam curl libwebp \ + librsvg - uses: actions/checkout@v4 From 48784a5a7a6d3b5a40676f083bfd270bdd396c9e Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 14:57:59 +0800 Subject: [PATCH 08/10] ci: add missing polkit dependency for meson build --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dd7fba13..de0de9039 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: libxkbcommon \ sdbus-cpp libpipewire \ pam curl libwebp \ - librsvg + librsvg polkit - uses: actions/checkout@v4 From c99be6a1b97ed0054a7053c9c40b0020ade08b43 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 5 May 2026 21:54:40 +0800 Subject: [PATCH 09/10] fix: remove unused sys/wait.h --- src/main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4f6ab183f..4d6174024 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include namespace { @@ -49,7 +48,7 @@ namespace { return -1; } - SpawnResult daemonize(pid_t* out_pid) { + SpawnResult daemonize(pid_t* outPid) { pid_t pid = ::fork(); if (pid < 0) { std::perror("fork (first)"); @@ -57,8 +56,8 @@ namespace { } if (pid > 0) { - if (out_pid) - *out_pid = pid; + if (outPid) + *outPid = pid; return SpawnResult::Parent; } From 5702893aaab6f4dda86c2c4cceea2e0c9fb314b4 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Wed, 6 May 2026 10:46:23 +0800 Subject: [PATCH 10/10] fix: adapt to upstream asynchronous polkit agent api --- src/dbus/polkit/polkit_agent.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/dbus/polkit/polkit_agent.cpp b/src/dbus/polkit/polkit_agent.cpp index cfae9c7df..abd121bc2 100644 --- a/src/dbus/polkit/polkit_agent.cpp +++ b/src/dbus/polkit/polkit_agent.cpp @@ -312,14 +312,16 @@ struct PolkitAgent::Impl { void onSessionReady(GObject* /*source*/, GAsyncResult* result) { starting = false; GError* error = nullptr; - PolkitSubject* subject = polkit_unix_session_new_for_process_finish(result, &error); + PolkitSubject* subject = nullptr; + + PolkitSubject* pidSubject = polkit_unix_session_new_for_process_finish(result, &error); // If we were cancelled (destruction), bail out without touching members // beyond the cancellable cleanup that the destructor already handled. if (error != nullptr && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_clear_error(&error); - if (subject != nullptr) { - g_object_unref(subject); + if (pidSubject != nullptr) { + g_object_unref(pidSubject); } return; } @@ -329,6 +331,18 @@ struct PolkitAgent::Impl { registerCancellable = nullptr; } + // Polkit's underlying authorization logic is session-based rather than tied to isolated processes + const char* sessionId = std::getenv("XDG_SESSION_ID"); + if (sessionId != nullptr && sessionId[0] != '\0') { + subject = polkit_unix_session_new(sessionId); + if (pidSubject != nullptr) { + g_object_unref(pidSubject); + } + } else { + // Retained for non-systemd environment support + subject = pidSubject; + } + if (subject == nullptr || error != nullptr) { std::string message = error != nullptr ? error->message : "failed to create polkit session subject"; g_clear_error(&error);