diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..99746764 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,98 @@ +name: CI +on: + workflow_dispatch: + inputs: + ref: + description: 'ref' + required: false + push: + pull_request: + +jobs: + docs: + runs-on: ubuntu-20.04 + env: + COLOR: "ON" + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.ref }} + - name: Install Dependencies + run: sudo apt-get install -y python3-sphinx + - name: Build Documentation + run: | + mkdir -p doc/build + cd doc/build + cmake .. + make doc + + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + cxx: [g++, clang++] + polybar_build_type: ["full"] + build_type: ["Release"] + include: + - cxx: g++ + polybar_build_type: "tests" + build_type: "Coverage" + - cxx: g++ + polybar_build_type: "minimal" + build_type: "Release" + env: + CXX: ${{ matrix.cxx }} + BUILD_TYPE: ${{ matrix.build_type }} + POLYBAR_BUILD_TYPE: ${{ matrix.polybar_build_type }} + POLYBAR_DIR: ${{ github.workspace }} + BUILD_DIR: "${{ github.workspace}}/build" + MAKEFLAGS: "-j4" + COLOR: "ON" + steps: + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libxcb-composite0-dev \ + libxcb-ewmh-dev \ + libxcb-icccm4-dev \ + libxcb-image0-dev \ + libxcb-randr0-dev \ + libxcb-util0-dev \ + libxcb1-dev \ + libcairo2-dev \ + python3-xcbgen \ + xcb-proto + + if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then + sudo apt-get install -y \ + libxcb-xkb-dev \ + libxcb-cursor-dev \ + libxcb-xrm-dev \ + i3-wm \ + libcurl4-openssl-dev \ + libjsoncpp-dev \ + libasound2-dev \ + libpulse-dev \ + libiw-dev \ + libmpdclient-dev + fi + - uses: actions/checkout@v2 + with: + submodules: true + ref: ${{ github.event.inputs.ref }} + - name: Summary + run: ./common/ci/summary.sh + - name: Configure + run: ./common/ci/configure.sh + - name: Build + run: | + cd $BUILD_DIR + make + - name: Tests + if: ${{ matrix.polybar_build_type == 'tests' }} + run: | + cd $BUILD_DIR + make check + cd $POLYBAR_DIR + bash <(curl -s https://codecov.io/bash) -F unittests -a "-ap" -Z diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9e1615c6..00000000 --- a/.travis.yml +++ /dev/null @@ -1,109 +0,0 @@ -sudo: required -dist: focal -language: cpp - -env: - global: - - JOBS=4 - - MAKEFLAGS="-j ${JOBS}" - - POLYBAR_BUILD_TYPE="compile" - -# Build configurations can either not specify anything for 'addon' and use this -# default list of packages. Or they can pick and choose which package groups to -# install -addons: - apt: - packages: - - &base_deps - - libxcb-composite0-dev - - libxcb-ewmh-dev - - libxcb-icccm4-dev - - libxcb-image0-dev - - libxcb-randr0-dev - - libxcb-util0-dev - - python3-xcbgen - - xcb-proto - - &optional_deps - - libxcb-xkb-dev - - libxcb-cursor-dev - - libxcb-xrm-dev - - libxcb1-dev - - xutils-dev - - i3-wm - - libjsoncpp-dev - - libasound2-dev - - libpulse-dev - - libcairo2-dev - - libiw-dev - - libmpdclient-dev - -script: source ${TRAVIS_BUILD_DIR}/common/travis/build.sh - -matrix: - include: - # Only builds the documentation - - language: generic - # Doesn't actually do anything, just used for the indicator on travis - compiler: Sphinx - addons: {apt: {packages: [python3-sphinx]}} - before_script: - - mkdir -p doc/build - - cd doc/build - - cmake .. - script: make doc - # Disable unnecessary commands - cache: - - - compiler: clang - env: BUILD_TYPE=Release - addons: {apt: {packages: [*base_deps, *optional_deps]}} - - - compiler: gcc - env: BUILD_TYPE=Coverage POLYBAR_BUILD_TYPE=tests BUILD_TESTS=ON - addons: {apt: {packages: [*base_deps, *optional_deps]}} - script: make check - after_success: - - cd ${TRAVIS_BUILD_DIR} - - bash <(curl -s https://codecov.io/bash) -F unittests -a "-ap" -Z || echo "Codecov did not collect coverage reports" - - - compiler: gcc - env: BUILD_TYPE=Release - addons: {apt: {packages: [*base_deps, *optional_deps]}} - - # Minimal build, contains no optional dependencies. This makes sure that - # we properly remove files from compilation that depend on libraries that - # are not installed - - compiler: gcc - env: BUILD_TYPE=Release POLYBAR_BUILD_TYPE=minimal - addons: {apt: {packages: [*base_deps]}} - -cache: - ccache: true - apt: true - -before_script: - - source ${TRAVIS_BUILD_DIR}/common/travis/summary.sh - - source ${TRAVIS_BUILD_DIR}/common/travis/configure.sh - -# Only fetch the newest 5 commits instead of 50 -git: - depth: 5 - -notifications: - email: false - irc: - channels: - - "irc.freenode.org#polybar" - template: - - " %{repository_slug}(%{branch})#%{build_number} | \"%{commit_subject}\" by %{author} | Commit #%{commit} %{result}: %{build_url}" - use_notice: true - on_success: never - on_failure: change - on_start: never - webhooks: - urls: - # For the https://gitter.im/polybar/polybar gitter room - - https://webhooks.gitter.im/e/10bdbe25961312646ace - on_success: never - on_failure: always - on_start: never diff --git a/README.md b/README.md index c4c0434f..a2cf7daa 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ A fast and easy-to-use tool for creating status bars.

+ - diff --git a/common/ci/configure.sh b/common/ci/configure.sh new file mode 100755 index 00000000..90718748 --- /dev/null +++ b/common/ci/configure.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -d "$BUILD_DIR" ]; then + rm -Rf "$BUILD_DIR" +fi + +mkdir -p "${BUILD_DIR}" +cd "${BUILD_DIR}" + +if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then + ENABLE_PULSEAUDIO=ON + ENABLE_NETWORK=ON + ENABLE_MPD=ON + ENABLE_CURL=ON + ENABLE_ALSA=ON + ENABLE_I3=ON + WITH_XRM=ON + WITH_XKB=ON + WITH_XRANDR_MONITORS=ON + WITH_XCURSOR=ON +fi + +if [ "$POLYBAR_BUILD_TYPE" = "tests" ]; then + BUILD_TESTS=ON +fi + +cmake \ + -DCMAKE_CXX_COMPILER="${CXX}" \ + -DCMAKE_CXX_FLAGS="${CXXFLAGS} -Werror" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DBUILD_TESTS:BOOL="${BUILD_TESTS:-OFF}" \ + -DBUILD_DOC:BOOL="${BUILD_DOC:-OFF}" \ + -DWITH_XRANDR=ON \ + -DENABLE_PULSEAUDIO="${ENABLE_PULSEAUDIO:-OFF}" \ + -DENABLE_NETWORK="${ENABLE_NETWORK:-OFF}" \ + -DENABLE_MPD="${ENABLE_MPD:-OFF}" \ + -DENABLE_CURL="${ENABLE_CURL:-OFF}" \ + -DENABLE_ALSA="${ENABLE_ALSA:-OFF}" \ + -DENABLE_I3="${ENABLE_I3:-OFF}" \ + -DWITH_XRM="${WITH_XRM:-OFF}" \ + -DWITH_XKB="${WITH_XKB:-OFF}" \ + -DWITH_XRANDR_MONITORS="${WITH_XRANDR_MONITORS:-OFF}" \ + -DWITH_XCURSOR="${WITH_XCURSOR:-OFF}" \ + .. diff --git a/common/ci/summary.sh b/common/ci/summary.sh new file mode 100755 index 00000000..a6bfcf82 --- /dev/null +++ b/common/ci/summary.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +set -x + +"${CXX}" --version +cmake --version + +set +x + +echo "PATH=${PATH}" +echo "CXX=${CXX}" +echo "CXXFLAGS=${CXXFLAGS}" +echo "LDFLAGS=${LDFLAGS}" +echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" +echo "MAKEFLAGS=${MAKEFLAGS}" +echo "POLYBAR_BUILD_TYPE=${POLYBAR_BUILD_TYPE}" +echo "CMAKE_BUILD_TYPE=${BUILD_TYPE}" diff --git a/common/travis/build.sh b/common/travis/build.sh deleted file mode 100755 index 9e376049..00000000 --- a/common/travis/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd "${TRAVIS_BUILD_DIR}/build" || false -make || exit $? diff --git a/common/travis/configure.sh b/common/travis/configure.sh deleted file mode 100755 index b67d78bd..00000000 --- a/common/travis/configure.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -mkdir -p "${TRAVIS_BUILD_DIR}/build" -cd "${TRAVIS_BUILD_DIR}/build" || false - -FLAGS="" - -# Disable all extra modules and X extensions for minimal builds -# Most of these should already be turned off because their libraries are not -# installed, but some may not be -if [ "$POLYBAR_BUILD_TYPE" == "minimal" ]; then - FLAGS=( - "-DENABLE_PULSEAUDIO=OFF" - "-DENABLE_NETWORK=OFF" - "-DENABLE_MPD=OFF" - "-DENABLE_CURL=OFF" - "-DENABLE_ALSA=OFF" - "-DENABLE_I3=OFF" - "-DWITH_XRM=OFF" - "-DWITH_XKB=OFF" - "-DWITH_XRANDR_MONITORS=OFF" - "-DWITH_XCURSOR=OFF" - "-DWITH_XRANDR=ON" - ) -fi - -cmake \ - -DCMAKE_CXX_COMPILER="${CXX}" \ - -DCMAKE_CXX_FLAGS="${CXXFLAGS} -Werror" \ - -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ - -DBUILD_TESTS:BOOL="${BUILD_TESTS:-OFF}" \ - -DBUILD_DOC:BOOL="${BUILD_DOC:-OFF}" \ - "${FLAGS[@]}" .. diff --git a/common/travis/summary.sh b/common/travis/summary.sh deleted file mode 100755 index 708d6f7b..00000000 --- a/common/travis/summary.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -echo "${CXX} --version" -eval "${CXX} --version" - -echo "cmake --version" -cmake --version - -echo "PATH=${PATH}" -echo "CXX=${CXX}" -echo "CXXFLAGS=${CXXFLAGS}" -echo "LDFLAGS=${LDFLAGS}" -echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" -echo "JOBS=${JOBS}" diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 39eceb66..09398ac1 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -63,7 +63,7 @@ class pulseaudio { pa_cvolume cv; bool muted{false}; // default sink name - static constexpr auto DEFAULT_SINK{"@DEFAULT_SINK@"}; + static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@"; pa_context* m_context{nullptr}; pa_threaded_mainloop* m_mainloop{nullptr}; diff --git a/include/components/logger.hpp b/include/components/logger.hpp index 5c7d1ba2..6d995072 100644 --- a/include/components/logger.hpp +++ b/include/components/logger.hpp @@ -100,7 +100,8 @@ class logger { /** * Convert string */ - const char* convert(string arg) const; // NOLINT + const char* convert(string& arg) const; + const char* convert(const string& arg) const; /** * Convert thread id diff --git a/include/modules/meta/base.inl b/include/modules/meta/base.inl index 5e713cd1..6792c02f 100644 --- a/include/modules/meta/base.inl +++ b/include/modules/meta/base.inl @@ -48,7 +48,7 @@ namespace modules { template string module::type() const { - return string(CONST_MOD(Impl).TYPE); + return string(module::TYPE); } template diff --git a/include/modules/meta/timer_module.hpp b/include/modules/meta/timer_module.hpp index d4f1a4a2..2cd5f3bf 100644 --- a/include/modules/meta/timer_module.hpp +++ b/include/modules/meta/timer_module.hpp @@ -17,6 +17,20 @@ namespace modules { } protected: + /** + * Loads and sets the interval for this module. + * + * Will throw an exception if a non-positive (<= 0) number is given. + */ + void set_interval(interval_t def) { + m_interval = this->m_conf.template get(this->name(), "interval", def); + + if (m_interval <= 0s) { + throw module_error( + this->name() + ": 'interval' must be larger than 0 (got '" + to_string(m_interval.count()) + "s')"); + } + } + void runner() { this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id())); diff --git a/include/modules/xworkspaces.hpp b/include/modules/xworkspaces.hpp index 62aafd90..d58cf455 100644 --- a/include/modules/xworkspaces.hpp +++ b/include/modules/xworkspaces.hpp @@ -110,8 +110,6 @@ namespace modules { // The following mutex is here to protect the data of this modules. // This can't be achieved using m_buildlock since we "CRTP override" get_output(). mutable mutex m_workspace_mutex; - - event_timer m_timer{0L, 25L}; }; } // namespace modules diff --git a/include/utils/process.hpp b/include/utils/process.hpp index eb380532..3ab5fd7b 100644 --- a/include/utils/process.hpp +++ b/include/utils/process.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "common.hpp" POLYBAR_NS @@ -10,11 +12,14 @@ namespace process_util { void redirect_stdio_to_dev_null(); - pid_t fork_detached(std::function const& lambda); + pid_t spawn_async(std::function const& lambda); + void fork_detached(std::function const& lambda); void exec(char* cmd, char** args); void exec_sh(const char* cmd); + int wait(pid_t pid); + pid_t wait_for_completion(pid_t process_id, int* status_addr = nullptr, int waitflags = 0); pid_t wait_for_completion(int* status_addr, int waitflags = 0); pid_t wait_for_completion_nohang(pid_t process_id, int* status); diff --git a/src/components/config.cpp b/src/components/config.cpp index a47050be..a94aaee2 100644 --- a/src/components/config.cpp +++ b/src/components/config.cpp @@ -72,34 +72,58 @@ void config::warn_deprecated(const string& section, const string& key, string re * Look for sections set up to inherit from a base section * and copy the missing parameters * + * Multiple sections can be specified, separated by a space. + * * [sub/section] - * inherit = base/section + * inherit = section1 section2 */ void config::copy_inherited() { for (auto&& section : m_sections) { + std::vector inherit_sections; + + // Collect all sections to be inherited for (auto&& param : section.second) { - if (param.first == "inherit") { - // Get name of base section + string key_name = param.first; + if (key_name == "inherit") { auto inherit = param.second; - if ((inherit = dereference(section.first, param.first, inherit, inherit)).empty()) { - throw value_error("Invalid section \"\" defined for \"" + section.first + ".inherit\""); + inherit = dereference(section.first, key_name, inherit, inherit); + + std::vector sections = string_util::split(std::move(inherit), ' '); + + inherit_sections.insert(inherit_sections.end(), sections.begin(), sections.end()); + + } else if (key_name.find("inherit") == 0) { + // Legacy support for keys that just start with 'inherit' + m_log.warn( + "\"%s.%s\": Using anything other than 'inherit' for inheriting section keys is deprecated. " + "The 'inherit' key supports multiple section names separated by a space.", + section.first, key_name); + + auto inherit = param.second; + inherit = dereference(section.first, key_name, inherit, inherit); + if (inherit.empty() || m_sections.find(inherit) == m_sections.end()) { + throw value_error( + "Invalid section \"" + inherit + "\" defined for \"" + section.first + "." + key_name + "\""); } - // Find and validate base section - auto base_section = m_sections.find(inherit); - if (base_section == m_sections.end()) { - throw value_error("Invalid section \"" + inherit + "\" defined for \"" + section.first + ".inherit\""); - } + inherit_sections.push_back(std::move(inherit)); + } + } - m_log.trace("config: Copying missing params (sub=\"%s\", base=\"%s\")", section.first, inherit); + for (const auto& base_name : inherit_sections) { + const auto base_section = m_sections.find(base_name); + if (base_section == m_sections.end()) { + throw value_error("Invalid section \"" + base_name + "\" defined for \"" + section.first + ".inherit\""); + } - /* - * Iterate the base and copy the parameters that haven't been defined - * for the sub-section - */ - for (auto&& base_param : base_section->second) { - section.second.emplace(base_param.first, base_param.second); - } + m_log.trace("config: Inheriting keys from \"%s\" in \"%s\"", base_name, section.first); + + /* + * Iterate the base and copy the parameters that haven't been defined + * yet. + */ + for (auto&& base_param : base_section->second) { + section.second.emplace(base_param.first, base_param.second); } } } diff --git a/src/components/logger.cpp b/src/components/logger.cpp index f482eeaf..f5151799 100644 --- a/src/components/logger.cpp +++ b/src/components/logger.cpp @@ -1,6 +1,7 @@ +#include "components/logger.hpp" + #include -#include "components/logger.hpp" #include "errors.hpp" #include "settings.hpp" #include "utils/concurrency.hpp" @@ -12,7 +13,11 @@ POLYBAR_NS /** * Convert string */ -const char* logger::convert(string arg) const { // NOLINT +const char* logger::convert(string& arg) const { + return arg.c_str(); +} + +const char* logger::convert(const string& arg) const { return arg.c_str(); } diff --git a/src/modules/counter.cpp b/src/modules/counter.cpp index 25c7ae9e..3abd5e40 100644 --- a/src/modules/counter.cpp +++ b/src/modules/counter.cpp @@ -9,7 +9,7 @@ namespace modules { counter_module::counter_module(const bar_settings& bar, string name_) : timer_module(bar, move(name_)) { - m_interval = m_conf.get(name(), "interval", m_interval); + set_interval(1s); m_formatter->add(DEFAULT_FORMAT, TAG_COUNTER, {TAG_COUNTER}); } @@ -25,6 +25,6 @@ namespace modules { } return false; } -} +} // namespace modules POLYBAR_NS_END diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 69673511..16d0bba1 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -16,7 +16,7 @@ namespace modules { template class module; cpu_module::cpu_module(const bar_settings& bar, string name_) : timer_module(bar, move(name_)) { - m_interval = m_conf.get(name(), "interval", 1s); + set_interval(1s); m_totalwarn = m_conf.get(name(), "warn-percentage", m_totalwarn); m_ramp_padding = m_conf.get(name(), "ramp-coreload-spacing", 1); diff --git a/src/modules/date.cpp b/src/modules/date.cpp index 127e7832..5bb4b6b0 100644 --- a/src/modules/date.cpp +++ b/src/modules/date.cpp @@ -22,7 +22,7 @@ namespace modules { throw module_error("No date or time format specified"); } - m_interval = m_conf.get(name(), "interval", 1s); + set_interval(1s); m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_DATE}); diff --git a/src/modules/fs.cpp b/src/modules/fs.cpp index 339caac3..b8193fd2 100644 --- a/src/modules/fs.cpp +++ b/src/modules/fs.cpp @@ -31,7 +31,7 @@ namespace modules { m_perc_used_warn = m_conf.get(name(), "warn-percentage", 90); m_fixed = m_conf.get(name(), "fixed-values", m_fixed); m_spacing = m_conf.get(name(), "spacing", m_spacing); - m_interval = m_conf.get(name(), "interval", 30s); + set_interval(30s); // Add formats and elements m_formatter->add( diff --git a/src/modules/github.cpp b/src/modules/github.cpp index 29ef5d1c..f40b15ac 100644 --- a/src/modules/github.cpp +++ b/src/modules/github.cpp @@ -23,7 +23,7 @@ namespace modules { m_api_url += '/'; } - m_interval = m_conf.get(name(), "interval", 60s); + set_interval(60s); m_empty_notifications = m_conf.get(name(), "empty-notifications", m_empty_notifications); m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL}); diff --git a/src/modules/memory.cpp b/src/modules/memory.cpp index 0227d61a..f97864fb 100644 --- a/src/modules/memory.cpp +++ b/src/modules/memory.cpp @@ -16,7 +16,7 @@ namespace modules { template class module; memory_module::memory_module(const bar_settings& bar, string name_) : timer_module(bar, move(name_)) { - m_interval = m_conf.get(name(), "interval", 1s); + set_interval(1s); m_perc_memused_warn = m_conf.get(name(), "warn-percentage", 90); m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_USED, TAG_BAR_FREE, TAG_RAMP_USED, TAG_RAMP_FREE, diff --git a/src/modules/menu.cpp b/src/modules/menu.cpp index dbf9adea..714bc464 100644 --- a/src/modules/menu.cpp +++ b/src/modules/menu.cpp @@ -140,7 +140,7 @@ namespace modules { m_log.info("%s: Opening menu level '%i'", name(), static_cast(m_level)); if (static_cast(m_level) >= m_levels.size()) { - m_log.warn("%s: Cannot open unexisting menu level '%i'", name(), level); + m_log.warn("%s: Cannot open unexisting menu level '%s'", name(), level); m_level = -1; } } else if (action == EVENT_CLOSE) { diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 39a31a24..ddebde02 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -19,7 +19,7 @@ namespace modules { m_ping_nth_update = m_conf.get(name(), "ping-interval", m_ping_nth_update); m_udspeed_minwidth = m_conf.get(name(), "udspeed-minwidth", m_udspeed_minwidth); m_accumulate = m_conf.get(name(), "accumulate-stats", m_accumulate); - m_interval = m_conf.get(name(), "interval", 1s); + set_interval(1s); m_unknown_up = m_conf.get(name(), "unknown-as-up", false); m_udspeed_unit = m_conf.get(name(), "speed-unit", m_udspeed_unit); diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 07bb4e5d..06cf88ff 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -19,7 +19,7 @@ namespace modules { m_path = m_conf.get(name(), "hwmon-path", ""s); m_tempbase = m_conf.get(name(), "base-temperature", 0); m_tempwarn = m_conf.get(name(), "warn-temperature", 80); - m_interval = m_conf.get(name(), "interval", 1s); + set_interval(1s); m_units = m_conf.get(name(), "units", m_units); if (m_path.empty()) { diff --git a/src/modules/xworkspaces.cpp b/src/modules/xworkspaces.cpp index 128524fd..f8b7b9b1 100644 --- a/src/modules/xworkspaces.cpp +++ b/src/modules/xworkspaces.cpp @@ -126,9 +126,7 @@ namespace modules { return; } - if (m_timer.allow(evt->time)) { - broadcast(); - } + broadcast(); } /** diff --git a/src/utils/command.cpp b/src/utils/command.cpp index 2f2afc2c..7e8d3a9f 100644 --- a/src/utils/command.cpp +++ b/src/utils/command.cpp @@ -33,7 +33,7 @@ command::~command() { * Execute the command */ int command::exec(bool wait_for_completion) { - m_forkpid = process_util::fork_detached([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); }); + m_forkpid = process_util::spawn_async([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); }); if (wait_for_completion) { auto status = wait(); m_forkpid = -1; diff --git a/src/utils/process.cpp b/src/utils/process.cpp index f5ec0857..1127c606 100644 --- a/src/utils/process.cpp +++ b/src/utils/process.cpp @@ -45,15 +45,11 @@ namespace process_util { } /** - * Forks a child process and completely detaches it. + * Forks a child process and executes the given lambda function in it. * - * In the child process, the given lambda function is executed. - * - * Use this if you want to run a command and just forget about it. - * - * \returns The PID of the child process + * Processes spawned this way need to be waited on by the caller. */ - pid_t fork_detached(std::function const& lambda) { + pid_t spawn_async(std::function const& lambda) { pid_t pid = fork(); switch (pid) { case -1: @@ -71,6 +67,50 @@ namespace process_util { } } + /** + * Forks a child process and completely detaches it. + * + * In the child process, the given lambda function is executed. + * We fork twice so that the first forked process can exit and it's child is + * reparented to the init process. + * + * Ref: https://web.archive.org/web/20120914180018/http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16 + * + * Use this if you want to run a command and just forget about it. + * + * \returns The PID of the child process + */ + void fork_detached(std::function const& lambda) { + pid_t pid = fork(); + switch (pid) { + case -1: + throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno))); + case 0: + // Child + setsid(); + + pid = fork(); + switch (pid) { + case -1: + throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno))); + case 0: + // Child + umask(0); + redirect_stdio_to_dev_null(); + lambda(); + _Exit(0); + } + + _Exit(0); + default: + /* + * The first fork immediately exits and we have to collect its exit + * status + */ + wait(pid); + } + } + /** * Execute command */ @@ -92,6 +132,15 @@ namespace process_util { } } + int wait(pid_t pid) { + int forkstatus; + do { + process_util::wait_for_completion(pid, &forkstatus, WCONTINUED | WUNTRACED); + } while (!WIFEXITED(forkstatus) && !WIFSIGNALED(forkstatus)); + + return WEXITSTATUS(forkstatus); + } + /** * Wait for child process */ diff --git a/tests/unit_tests/utils/process.cpp b/tests/unit_tests/utils/process.cpp index cfd8351c..64ff21f6 100644 --- a/tests/unit_tests/utils/process.cpp +++ b/tests/unit_tests/utils/process.cpp @@ -12,8 +12,8 @@ using namespace polybar; using namespace process_util; -TEST(ForkDetached, is_detached) { - pid_t pid = fork_detached([] { exec_sh("sleep 0.1"); }); +TEST(SpawnAsync, is_async) { + pid_t pid = spawn_async([] { exec_sh("sleep 0.1"); }); int status; pid_t res = process_util::wait_for_completion_nohang(pid, &status); @@ -23,8 +23,8 @@ TEST(ForkDetached, is_detached) { EXPECT_FALSE(WIFEXITED(status)); } -TEST(ForkDetached, exit_code) { - pid_t pid = fork_detached([] { exec_sh("exit 42"); }); +TEST(SpawnAsync, exit_code) { + pid_t pid = spawn_async([] { exec_sh("exit 42"); }); int status = 0; pid_t res = waitpid(pid, &status, 0); diff --git a/version.txt b/version.txt index 7c5ff776..161ce030 100644 --- a/version.txt +++ b/version.txt @@ -1,4 +1,4 @@ # Polybar version information # Update this on every release # This is used to create the version string if a git repo is not available -3.5.0 +3.5.1