[
  {
    "path": ".clang-format",
    "content": "---\nBasedOnStyle: Google\nAlignConsecutiveDeclarations: false\nColumnLimit: 100\n...\n"
  },
  {
    "path": ".clang-tidy",
    "content": "Checks: >\n  -*,\n  bugprone-*\n  misc-*,\n  modernize-*,\n  performance-*,\n  portability-*,\n  readability-*,\n  -fuchsia-trailing-return,\n  -readability-magic-numbers,\n  -modernize-use-nodiscard,\n  -modernize-use-trailing-return-type,\n  -readability-braces-around-statements,\n  -readability-redundant-access-specifiers,\n  -readability-redundant-member-init,\n  -readability-redundant-string-init,\n  -readability-identifier-length\n# CheckOptions:\n#   - { key: readability-identifier-naming.NamespaceCase,          value: lower_case }\n#   - { key: readability-identifier-naming.ClassCase,              value: CamelCase  }\n#   - { key: readability-identifier-naming.StructCase,             value: CamelCase  }\n#   - { key: readability-identifier-naming.FunctionCase,           value: camelBack  }\n#   - { key: readability-identifier-naming.VariableCase,           value: camelBack  }\n#   - { key: readability-identifier-naming.PrivateMemberCase,      value: camelBack  }\n#   - { key: readability-identifier-naming.PrivateMemberSuffix,    value: _          }\n#   - { key: readability-identifier-naming.EnumCase,               value: CamelCase  }\n#   - { key: readability-identifier-naming.EnumConstantCase,       value: UPPER_CASE }\n#   - { key: readability-identifier-naming.GlobalConstantCase,     value: UPPER_CASE }\n#   - { key: readability-identifier-naming.StaticConstantCase,     value: UPPER_CASE }\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig configuration for Waybar\n# http://EditorConfig.org\n\n# Top-most EditorConfig file\nroot = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\ncharset = utf-8\n\n[*.{build,css}]\nindent_style = space\nindent_size = 4\n\n[*.{hpp,cpp}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".envrc",
    "content": "use flake\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: Alexays\ncustom: https://paypal.me/ARouillard\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "bug:\n  - \"(crash|bug|error|coredump|freeze|segfault|issue|problem)\"\n\nenhancement:\n  - \"(feature|enhancement|improvement|request|suggestion)\"\n\nhyprland:\n  - \"(hyprland)\"\n\nnetwork:\n  - \"(network|wifi|ethernet)\"\n\nbluetooth:\n  - \"(bluetooth|bluez)\"\n\nsway:\n  - \"(sway)\"\n\ncpu:\n  - \"(cpu)\"\n\nmemory:\n  - \"(memory|ram)\"\n\ndisk:\n  - \"(disk|storage)\"\n\nbattery:\n  - \"(upower|battery)\"\n\nsni:\n  - \"(sni|tray)\"\n\ndwl:\n  - \"(dwl)\"\n\ncustom:\n  - \"(custom|module|extension|plugin|script)\"\n\nmpd:\n  - \"(mpd|music)\"\n\naudio:\n  - \"(pulseaudio|alsa|jack|audio|pirewire|wireplumber)\"\n\ntemperature:\n  - \"(temperature|thermal|hwmon)\"\n\nclock:\n  - \"(clock|time|date)\"\n\ngamemode:\n  - \"(gamemode|game|gaming)\"\n\ninhibitor:\n  - \"(inhibitor|idle|lock|suspend|hibernate|logout)\"\n\ncava:\n  - \"(cava|audio-visualizer)\"\n\nbacklight:\n  - \"(backlight|brightness)\"\n\nkeyboard:\n  - \"(keyboard|keymap|layout|shortcut)\"\n"
  },
  {
    "path": ".github/workflows/clang-format.yml",
    "content": "name: clang-format\n\non: [push, pull_request]\n\nconcurrency:\n  group: ${{ github.workflow }}-format-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: RafikFarhad/clang-format-github-action@v6\n        name: clang-format\n        with:\n          sources: \"src/**/*.hpp,src/**/*.cpp\"\n          style: \"file\"\n"
  },
  {
    "path": ".github/workflows/clang-tidy.yml.bak",
    "content": "name: clang-tidy\n\non: [push, pull_request]\n\nconcurrency:\n  group: ${{ github.workflow }}-tidy-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    container:\n      image: alexays/waybar:debian\n    steps:\n      - uses: actions/checkout@v6\n      - name: configure\n        run: |\n          meson -Dcpp_std=c++20 build  # necessary to generate compile_commands.json\n          ninja -C build               # necessary to find certain .h files (xdg, wayland, etc.)\n      - uses: actions/setup-python@v5\n        with:\n          python-version: '3.10'   # to be kept in sync with cpp-linter-action\n          update-environment: true # the python dist installed by the action needs LD_LIBRARY_PATH to work\n      - uses: cpp-linter/cpp-linter-action@v2.9.1\n        name: clang-tidy\n        id: clang-tidy-check\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          PIP_NO_CACHE_DIR: false\n        with:\n          style: \"\" # empty string => don't do clang-format checks here, we do them in clang-format.yml\n          files-changed-only: true # only check files that have changed\n          lines-changed-only: true # only check lines that have changed\n          tidy-checks: \"\" # empty string => use the .clang-tidy file\n          version: \"17\" # clang-tools version\n          database: \"build\" # path to the compile_commands.json file\n      - name: Check if clang-tidy failed on any files\n        if: steps.clang-tidy-check.outputs.checks-failed > 0\n        run: echo \"Some files failed the linting checks!\" && exit 1\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Build and Push Docker Image\n\non:\n  workflow_dispatch:\n  schedule:\n    # run monthly\n    - cron: '0 0 1 * *'\n\njobs:\n  build-and-push:\n    runs-on: ubuntu-latest\n    if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'\n    strategy:\n      fail-fast: false # don't fail the other jobs if one of the images fails to build\n      matrix:\n        os: [ 'alpine', 'archlinux', 'debian', 'fedora', 'gentoo', 'opensuse' ]\n\n    steps:\n      - name: Checkout repository\n      - uses: actions/checkout@v6\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          file: Dockerfiles/${{ matrix.os }}\n          push: true\n          tags: alexays/waybar:${{ matrix.os }}\n"
  },
  {
    "path": ".github/workflows/freebsd.yml",
    "content": "name: freebsd\n\non: [push, pull_request]\n\nconcurrency:\n  group: ${{ github.workflow }}-freebsd-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    # Run actions in a FreeBSD VM on the ubuntu runner\n    # https://github.com/actions/runner/issues/385 - for FreeBSD runner support\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: Test in FreeBSD VM\n        uses: cross-platform-actions/action@v0.28.0\n        timeout-minutes: 180\n        env:\n          CPPFLAGS: '-isystem/usr/local/include'\n          LDFLAGS: '-L/usr/local/lib'\n        with:\n          operating_system: freebsd\n          version: \"14.2\"\n          environment_variables: CPPFLAGS LDFLAGS\n          sync_files: runner-to-vm\n          run: |\n            sudo pkg install -y git #  subprojects/date\n            sudo pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \\\n              libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \\\n              pkgconf pipewire pulseaudio scdoc sndio spdlog wayland-protocols upower \\\n              libinotify\n            meson setup build -Dman-pages=enabled\n            ninja -C build\n            meson test -C build --no-rebuild --print-errorlogs --suite waybar\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: \"Issue Labeler\"\non:\n  issues:\n    types: [opened, edited]\n\npermissions:\n  issues: write\n  contents: read\n\njobs:\n  triage:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: github/issue-labeler@v3.4\n        with:\n          configuration-path: .github/labeler.yml\n          enable-versioned-regex: 0\n          include-title: 1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/linux.yml",
    "content": "name: linux\n\non: [push, pull_request]\n\nconcurrency:\n  group: ${{ github.workflow }}-linux-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        distro:\n          - alpine\n          - archlinux\n          - debian\n          - fedora\n          - opensuse\n          - gentoo\n        cpp_std: [c++20]\n\n    runs-on: ubuntu-latest\n    container:\n      image: alexays/waybar:${{ matrix.distro }}\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: configure\n        run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build\n      - name: build\n        run: ninja -C build\n      - name: test\n        run: make test\n"
  },
  {
    "path": ".github/workflows/nix-tests.yml",
    "content": "name: \"Nix-Tests\"\non:\n  pull_request:\n  push:\nconcurrency:\n  group: ${{ github.workflow }}-nix-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\njobs:\n  nix-flake-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: cachix/install-nix-action@v31\n        with:\n          extra_nix_config: |\n            experimental-features = nix-command flakes\n            access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}\n      - run: nix flake show\n      - run: nix flake check --print-build-logs\n      - run: nix build --print-build-logs\n"
  },
  {
    "path": ".github/workflows/nix-update-flake-lock.yml",
    "content": "name: update-flake-lock\non:\n  workflow_dispatch: # allows manual triggering\n  schedule:\n    - cron: '0 0 1 * *' # Run monthly\n  push:\n    paths:\n      - 'flake.nix'\njobs:\n  lockfile:\n    runs-on: ubuntu-latest\n    if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar'\n    steps:\n      - name: Checkout repository\n      - uses: actions/checkout@v6\n      - name: Install Nix\n        uses: cachix/install-nix-action@v31\n        with:\n          extra_nix_config: |\n            access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}\n      - name: Update flake.lock\n        uses: DeterminateSystems/update-flake-lock@v28\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*~\nvgcore.*\n/.vscode\n/.idea\n/.cache\n*.swp\npackagecache\n/subprojects/**/\n/subprojects/.wraplock\n/build*\n/dist\n/meson.egg-info\n\n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n/.direnv/\n\n# Nix\nresult\nresult-*\n\n.ccls-cache\n_codeql_detected_source_root\nheaptrack*\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"package/archlinux\"]\n\tpath = package/archlinux\n\turl = https://aur.archlinux.org/waybar-git.git\n"
  },
  {
    "path": "Dockerfiles/alpine",
    "content": "# vim: ft=Dockerfile\n\nFROM alpine:latest\n\nRUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata playerctl-dev\n"
  },
  {
    "path": "Dockerfiles/archlinux",
    "content": "# vim: ft=Dockerfile\n\nFROM archlinux:base-devel\n\nRUN pacman -Syu --noconfirm && \\\n    pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols glib2-devel pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl iniparser fftw && \\\n    sed -Ei 's/#(en_(US|GB)\\.UTF)/\\1/' /etc/locale.gen && locale-gen\n"
  },
  {
    "path": "Dockerfiles/debian",
    "content": "# vim: ft=Dockerfile\n\nFROM debian:sid-slim\n\nRUN apt update && \\\n    apt install --no-install-recommends --no-install-suggests -y \\\n        build-essential \\\n        catch2 \\\n        cmake \\\n        git \\\n        gobject-introspection \\\n        libdbusmenu-gtk3-dev \\\n        libegl1-mesa-dev \\\n        libfmt-dev \\\n        libgbm-dev \\\n        libgirepository1.0-dev \\\n        libgles2-mesa-dev \\\n        libgtk-layer-shell-dev \\\n        libgtkmm-3.0-dev \\\n        libhowardhinnant-date-dev \\\n        libiniparser-dev \\\n        libinput-dev     \\\n        libjack-jackd2-dev \\\n        libjsoncpp-dev \\\n        libmpdclient-dev \\\n        libnl-3-dev \\\n        libnl-genl-3-dev \\\n        libpixman-1-dev  \\\n        libplayerctl-dev \\\n        libpugixml-dev \\\n        libpulse-dev \\\n        libsndio-dev \\\n        libspdlog-dev \\\n        libudev-dev \\\n        libupower-glib-dev \\\n        libwayland-dev \\\n        libwireplumber-0.5-dev \\\n        libxkbcommon-dev \\\n        libxkbregistry-dev \\\n        locales \\\n        meson \\\n        ninja-build \\\n        pkg-config \\\n        python3-pip \\\n        python3-venv \\\n        scdoc \\\n        sudo \\\n        wayland-protocols \\\n    && apt clean\n"
  },
  {
    "path": "Dockerfiles/fedora",
    "content": "# vim: ft=Dockerfile\n\nFROM fedora:latest\n\nRUN dnf install -y @c-development \\\n    git-core glibc-langpack-en meson scdoc \\\n    'pkgconfig(catch2)' \\\n    'pkgconfig(date)' \\\n    'pkgconfig(dbusmenu-gtk3-0.4)' \\\n    'pkgconfig(fmt)' \\\n    'pkgconfig(gdk-pixbuf-2.0)' \\\n    'pkgconfig(gio-unix-2.0)' \\\n    'pkgconfig(gtk-layer-shell-0)' \\\n    'pkgconfig(gtkmm-3.0)' \\\n    'pkgconfig(jack)' \\\n    'pkgconfig(jsoncpp)' \\\n    'pkgconfig(libevdev)' \\\n    'pkgconfig(libinput)' \\\n    'pkgconfig(libmpdclient)' \\\n    'pkgconfig(libnl-3.0)' \\\n    'pkgconfig(libnl-genl-3.0)' \\\n    'pkgconfig(libpulse)' \\\n    'pkgconfig(libudev)' \\\n    'pkgconfig(playerctl)' \\\n    'pkgconfig(pugixml)' \\\n    'pkgconfig(sigc++-2.0)' \\\n    'pkgconfig(spdlog)' \\\n    'pkgconfig(upower-glib)' \\\n    'pkgconfig(wayland-client)' \\\n    'pkgconfig(wayland-cursor)' \\\n    'pkgconfig(wayland-protocols)' \\\n    'pkgconfig(wireplumber-0.5)' \\\n    'pkgconfig(xkbregistry)' && \\\n    dnf clean all -y\n"
  },
  {
    "path": "Dockerfiles/gentoo",
    "content": "# vim: ft=Dockerfile\n\nFROM gentoo/stage3:latest\n\nRUN export FEATURES=\"-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersandbox\" && \\\n\t\temerge --sync && \\\n\t\teselect news read --quiet new 1>/dev/null 2>&1 && \\\n\t\temerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \\\n\t\tUSE=\"wayland gtk3 gtk -doc X pulseaudio minimal\" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols dev-cpp/gtkmm:3.0 x11-libs/libxkbcommon \\\n\t\tx11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \\\n\t\tmedia-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw\n"
  },
  {
    "path": "Dockerfiles/opensuse",
    "content": "# vim: ft=Dockerfile\n\nFROM opensuse/tumbleweed:latest\n\nRUN zypper -n up && \\\n    zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \\\n    zypper -n refresh && \\\n    zypper -n install -t pattern devel_C_C++ && \\\n    zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel python3-packaging\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Alex\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: build build-debug run clean default install\n\ndefault: build\n\nbuild:\n\tmeson setup build\n\tninja -C build\n\nbuild-debug:\n\tmeson setup build --buildtype=debug\n\tninja -C build\n\ninstall: build\n\tninja -C build install\n\nrun: build\n\t./build/waybar\n\ndebug-run: build-debug\n\t./build/waybar --log-level debug\n\ntest:\n\tmeson test -C build --verbose --suite waybar\n.PHONY: test\n\ntest-detailed:\n\tmeson test -C build --verbose --print-errorlogs --test-args='--reporter console -s'\n.PHONY: test-detailed\n\nclean:\n\trm -rf build\n"
  },
  {
    "path": "README.md",
    "content": "# Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)\n\n> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>\n> Available in [all major distributions](https://github.com/Alexays/Waybar/wiki/Installation)<br>\n> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*\n\n#### Current features\n- Sway (Workspaces, Binding mode, Focused window name)\n- River (Mapping mode, Tags, Focused window name)\n- Hyprland (Window Icons, Workspaces, Focused window name)\n- Niri (Workspaces, Focused window name, Language)\n- DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc)\n- Tray [#21](https://github.com/Alexays/Waybar/issues/21)\n- Local time\n- Battery\n- UPower\n- Power profiles daemon\n- Network\n- Bluetooth\n- Pulseaudio\n- Privacy Info\n- Wireplumber\n- Disk\n- Memory\n- Cpu load average\n- Temperature\n- MPD\n- Custom scripts\n- Custom image\n- Multiple output configuration\n- And many more customizations\n\n#### Configuration and Styling\n\n[See the wiki for more details](https://github.com/Alexays/Waybar/wiki).\n\n### Installation\n\nWaybar is available from a number of Linux distributions:\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/waybar.svg?columns=3&header=Waybar%20Downstream%20Packaging)](https://repology.org/project/waybar/versions)\n\nAn Ubuntu PPA with more recent versions is available\n[here](https://launchpad.net/~nschloe/+archive/ubuntu/waybar).\n\n\n#### Building from source\n\n```bash\n$ git clone https://github.com/Alexays/Waybar\n$ cd Waybar\n$ meson setup build\n$ ninja -C build\n$ ./build/waybar\n# If you want to install it\n$ ninja -C build install\n$ waybar\n```\n\n**Dependencies**\n\n```\ngtkmm3\njsoncpp\nlibsigc++\nfmt\nwayland\nchrono-date\nspdlog\nlibgtk-3-dev [gtk-layer-shell]\ngobject-introspection [gtk-layer-shell]\nlibgirepository1.0-dev [gtk-layer-shell]\nlibpulse [Pulseaudio module]\nlibnl [Network module]\nlibappindicator-gtk3 [Tray module]\nlibdbusmenu-gtk3 [Tray module]\nlibmpdclient [MPD module]\nlibsndio [sndio module]\nlibevdev [KeyboardState module]\nxkbregistry\nupower [UPower battery module]\n```\n\n**Build dependencies**\n\n```\ncmake\nmeson\nscdoc\nwayland-protocols\n```\n\nOn Ubuntu, you can install all the relevant dependencies using this command (tested with 19.10 and 20.04):\n\n```\nsudo apt install \\\n  clang-tidy \\\n  gobject-introspection \\\n  libdbusmenu-gtk3-dev \\\n  libevdev-dev \\\n  libfmt-dev \\\n  libgirepository1.0-dev \\\n  libgtk-3-dev \\\n  libgtkmm-3.0-dev \\\n  libinput-dev \\\n  libjsoncpp-dev \\\n  libmpdclient-dev \\\n  libnl-3-dev \\\n  libnl-genl-3-dev \\\n  libpulse-dev \\\n  libsigc++-2.0-dev \\\n  libspdlog-dev \\\n  libwayland-dev \\\n  scdoc \\\n  upower \\\n  libxkbregistry-dev\n```\n\nOn Arch, you can use this command:\n\n```\npacman -S --asdeps \\\n  gtkmm3 \\\n  jsoncpp \\\n  libsigc++ \\\n  fmt \\\n  wayland \\\n  chrono-date \\\n  spdlog \\\n  gtk3 \\\n  gobject-introspection \\\n  libgirepository \\\n  libpulse \\\n  libnl \\\n  libappindicator-gtk3 \\\n  libdbusmenu-gtk3 \\\n  libmpdclient \\\n  sndio \\\n  libevdev \\\n  libxkbcommon \\\n  upower \\\n  meson \\\n  cmake \\\n  scdoc \\\n  wayland-protocols \\\n  glib2-devel\n```\n\n\nContributions welcome!<br>\nHave fun :)<br>\nThe style guidelines are [Google's](https://google.github.io/styleguide/cppguide.html)\n\n> [!CAUTION]\n> Distributions of Waybar are only released on the [official GitHub page](https://github.com/Alexays/Waybar).<br/>\n> Waybar does **not** have an official website. Do not trust any sites that claim to be official.\n\n## License\n\nWaybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/Alexays/Waybar/blob/master/LICENSE).\n"
  },
  {
    "path": "asan.supp",
    "content": "# Suppress common address sanitizer issues in dependencies, these are often non-fixable or not an issue.\n# Use it like this (when in repo root): ASAN_OPTIONS=\"suppressions=./asan.supp\" ./build/waybar\nleak:libpangoft2-1.0.so.0\nleak:libgtk-3.so.0\nleak:libfontconfig.so.1"
  },
  {
    "path": "default.nix",
    "content": "(import (\n  let\n    lock = builtins.fromJSON (builtins.readFile ./flake.lock);\n  in\n  fetchTarball {\n    url = \"https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz\";\n    sha256 = lock.nodes.flake-compat.locked.narHash;\n  }\n) { src = ./.; }).defaultNix\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"Highly customizable Wayland bar for Sway and Wlroots based compositors\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n    flake-compat = {\n      url = \"github:edolstra/flake-compat\";\n      flake = false;\n    };\n  };\n\n  outputs =\n    { self, nixpkgs, ... }:\n    let\n      inherit (nixpkgs) lib;\n      genSystems =\n        func:\n        lib.genAttrs\n          [\n            \"x86_64-linux\"\n            \"aarch64-linux\"\n          ]\n          (\n            system:\n            func (\n              import nixpkgs {\n                inherit system;\n                overlays = with self.overlays; [\n                  waybar\n                ];\n              }\n            )\n          );\n\n      mkDate =\n        longDate:\n        (lib.concatStringsSep \"-\" [\n          (builtins.substring 0 4 longDate)\n          (builtins.substring 4 2 longDate)\n          (builtins.substring 6 2 longDate)\n        ]);\n    in\n    {\n      devShells = genSystems (pkgs: {\n        default = pkgs.mkShell {\n          name = \"waybar-shell\";\n\n          # inherit attributes from upstream nixpkgs derivation\n          inherit (pkgs.waybar)\n            buildInputs\n            depsBuildBuild\n            depsBuildBuildPropagated\n            depsBuildTarget\n            depsBuildTargetPropagated\n            depsHostHost\n            depsHostHostPropagated\n            depsTargetTarget\n            depsTargetTargetPropagated\n            propagatedBuildInputs\n            propagatedNativeBuildInputs\n            strictDeps\n            ;\n\n          # overrides for local development\n          nativeBuildInputs =\n            pkgs.waybar.nativeBuildInputs\n            ++ (with pkgs; [\n              nixfmt\n              clang-tools\n              gdb\n            ]);\n        };\n      });\n\n      formatter = genSystems (\n        pkgs:\n        pkgs.treefmt.withConfig {\n          settings = {\n            formatter = {\n              clang-format = {\n                options = [ \"-i\" ];\n                command = lib.getExe' pkgs.clang-tools \"clang-format\";\n                excludes = [ ];\n                includes = [\n                  \"*.c\"\n                  \"*.cpp\"\n                  \"*.h\"\n                  \"*.hpp\"\n                ];\n              };\n              nixfmt = {\n                command = lib.getExe pkgs.nixfmt;\n                includes = [ \"*.nix\" ];\n              };\n            };\n            tree-root-file = \".git/index\";\n          };\n        }\n      );\n\n      overlays = {\n        default = self.overlays.waybar;\n        waybar = final: prev: {\n          waybar = final.callPackage ./nix/default.nix {\n            waybar = prev.waybar;\n            # take the first \"version: '...'\" from meson.build\n            version =\n              (builtins.head (\n                builtins.split \"'\" (\n                  builtins.elemAt (builtins.split \" version: '\" (builtins.readFile ./meson.build)) 2\n                )\n              ))\n              + \"+date=\"\n              + (mkDate (self.lastModifiedDate or \"19700101\"))\n              + \"_\"\n              + (self.shortRev or \"dirty\");\n          };\n        };\n      };\n\n      packages = genSystems (pkgs: {\n        default = self.packages.${pkgs.stdenv.hostPlatform.system}.waybar;\n        inherit (pkgs) waybar;\n      });\n    };\n}\n"
  },
  {
    "path": "include/AAppIconLabel.hpp",
    "content": "#pragma once\n\n#include <gtkmm/box.h>\n#include <gtkmm/image.h>\n\n#include \"AIconLabel.hpp\"\n\nnamespace waybar {\n\nclass AAppIconLabel : public AIconLabel {\n public:\n  AAppIconLabel(const Json::Value& config, const std::string& name, const std::string& id,\n                const std::string& format, uint16_t interval = 0, bool ellipsize = false,\n                bool enable_click = false, bool enable_scroll = false);\n  virtual ~AAppIconLabel() = default;\n  auto update() -> void override;\n\n protected:\n  void updateAppIconName(const std::string& app_identifier,\n                         const std::string& alternative_app_identifier);\n  void updateAppIcon();\n  unsigned app_icon_size_{24};\n  bool update_app_icon_{true};\n  std::string app_icon_name_;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/AIconLabel.hpp",
    "content": "#pragma once\n\n#include <gtkmm/box.h>\n#include <gtkmm/image.h>\n\n#include \"ALabel.hpp\"\n\nnamespace waybar {\n\nclass AIconLabel : public ALabel {\n public:\n  AIconLabel(const Json::Value& config, const std::string& name, const std::string& id,\n             const std::string& format, uint16_t interval = 0, bool ellipsize = false,\n             bool enable_click = false, bool enable_scroll = false);\n  virtual ~AIconLabel() = default;\n  auto update() -> void override;\n\n protected:\n  Gtk::Image image_;\n  Gtk::Box box_;\n\n  bool iconEnabled() const;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/ALabel.hpp",
    "content": "#pragma once\n\n#include <glibmm/markup.h>\n#include <gtkmm/label.h>\n#include <json/json.h>\n\n#include \"AModule.hpp\"\n\nnamespace waybar {\n\nclass ALabel : public AModule {\n public:\n  ALabel(const Json::Value&, const std::string&, const std::string&, const std::string& format,\n         uint16_t interval = 0, bool ellipsize = false, bool enable_click = false,\n         bool enable_scroll = false);\n  virtual ~ALabel() = default;\n  auto update() -> void override;\n  virtual std::string getIcon(uint16_t, const std::string& alt = \"\", uint16_t max = 0);\n  virtual std::string getIcon(uint16_t, const std::vector<std::string>& alts, uint16_t max = 0);\n\n protected:\n  Gtk::Label label_;\n  std::string format_;\n  const std::chrono::milliseconds interval_;\n  bool alt_ = false;\n  std::string default_format_;\n\n  bool handleToggle(GdkEventButton* const& e) override;\n  virtual std::string getState(uint8_t value, bool lesser = false);\n\n  std::map<std::string, GtkMenuItem*> submenus_;\n  std::map<std::string, std::string> menuActionsMap_;\n  static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data);\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/AModule.hpp",
    "content": "#pragma once\n\n#include <glibmm/dispatcher.h>\n#include <glibmm/markup.h>\n#include <gtkmm.h>\n#include <gtkmm/eventbox.h>\n#include <json/json.h>\n\n#include \"IModule.hpp\"\n\nnamespace waybar {\n\nclass AModule : public IModule {\n public:\n  static constexpr const char* MODULE_CLASS = \"module\";\n\n  ~AModule() override;\n  auto update() -> void override;\n  virtual auto refresh(int shouldRefresh) -> void {};\n  operator Gtk::Widget&() override;\n  auto doAction(const std::string& name) -> void override;\n\n  /// Emitting on this dispatcher triggers a update() call\n  Glib::Dispatcher dp;\n\n  bool expandEnabled() const;\n\n protected:\n  // Don't need to make an object directly\n  // Derived classes are able to use it\n  AModule(const Json::Value&, const std::string&, const std::string&, bool enable_click = false,\n          bool enable_scroll = false);\n\n  enum SCROLL_DIR { NONE, UP, DOWN, LEFT, RIGHT };\n\n  SCROLL_DIR getScrollDir(GdkEventScroll* e);\n  bool tooltipEnabled() const;\n\n  std::vector<int> pid_children_;\n  const std::string name_;\n  const Json::Value& config_;\n  Gtk::EventBox event_box_;\n\n  virtual void setCursor(Gdk::CursorType const& c);\n\n  virtual bool handleToggle(GdkEventButton* const& ev);\n  virtual bool handleMouseEnter(GdkEventCrossing* const& ev);\n  virtual bool handleMouseLeave(GdkEventCrossing* const& ev);\n  virtual bool handleScroll(GdkEventScroll*);\n  virtual bool handleRelease(GdkEventButton* const& ev);\n  GObject* menu_ = nullptr;\n\n private:\n  bool handleUserEvent(GdkEventButton* const& ev);\n  const bool isTooltip;\n  const bool isExpand;\n  bool hasUserEvents_;\n  gdouble distance_scrolled_y_;\n  gdouble distance_scrolled_x_;\n  std::map<std::string, std::string> eventActionMap_;\n  static const inline std::map<std::pair<uint, GdkEventType>, std::string> eventMap_{\n      {std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), \"on-click\"},\n      {std::make_pair(1, GdkEventType::GDK_BUTTON_RELEASE), \"on-click-release\"},\n      {std::make_pair(1, GdkEventType::GDK_2BUTTON_PRESS), \"on-double-click\"},\n      {std::make_pair(1, GdkEventType::GDK_3BUTTON_PRESS), \"on-triple-click\"},\n      {std::make_pair(2, GdkEventType::GDK_BUTTON_PRESS), \"on-click-middle\"},\n      {std::make_pair(2, GdkEventType::GDK_BUTTON_RELEASE), \"on-click-middle-release\"},\n      {std::make_pair(2, GdkEventType::GDK_2BUTTON_PRESS), \"on-double-click-middle\"},\n      {std::make_pair(2, GdkEventType::GDK_3BUTTON_PRESS), \"on-triple-click-middle\"},\n      {std::make_pair(3, GdkEventType::GDK_BUTTON_PRESS), \"on-click-right\"},\n      {std::make_pair(3, GdkEventType::GDK_BUTTON_RELEASE), \"on-click-right-release\"},\n      {std::make_pair(3, GdkEventType::GDK_2BUTTON_PRESS), \"on-double-click-right\"},\n      {std::make_pair(3, GdkEventType::GDK_3BUTTON_PRESS), \"on-triple-click-right\"},\n      {std::make_pair(8, GdkEventType::GDK_BUTTON_PRESS), \"on-click-backward\"},\n      {std::make_pair(8, GdkEventType::GDK_BUTTON_RELEASE), \"on-click-backward-release\"},\n      {std::make_pair(8, GdkEventType::GDK_2BUTTON_PRESS), \"on-double-click-backward\"},\n      {std::make_pair(8, GdkEventType::GDK_3BUTTON_PRESS), \"on-triple-click-backward\"},\n      {std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), \"on-click-forward\"},\n      {std::make_pair(9, GdkEventType::GDK_BUTTON_RELEASE), \"on-click-forward-release\"},\n      {std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), \"on-double-click-forward\"},\n      {std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), \"on-triple-click-forward\"}};\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/ASlider.hpp",
    "content": "#pragma once\n\n#include \"AModule.hpp\"\n#include \"gtkmm/scale.h\"\n\nnamespace waybar {\n\nclass ASlider : public AModule {\n public:\n  ASlider(const Json::Value& config, const std::string& name, const std::string& id);\n  virtual void onValueChanged();\n\n protected:\n  bool vertical_ = false;\n  int min_ = 0, max_ = 100, curr_ = 50;\n  Gtk::Scale scale_;\n};\n\n}  // namespace waybar"
  },
  {
    "path": "include/IModule.hpp",
    "content": "#pragma once\n\n#include <gtkmm/widget.h>\n\nnamespace waybar {\n\nclass IModule {\n public:\n  virtual ~IModule() = default;\n  virtual auto update() -> void = 0;\n  virtual operator Gtk::Widget&() = 0;\n  virtual auto doAction(const std::string& name) -> void = 0;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/bar.hpp",
    "content": "#pragma once\n\n#include <gdkmm/monitor.h>\n#include <glibmm/refptr.h>\n#include <gtkmm/box.h>\n#include <gtkmm/cssprovider.h>\n#include <gtkmm/main.h>\n#include <gtkmm/window.h>\n#include <json/json.h>\n\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"group.hpp\"\n#include \"util/kill_signal.hpp\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nnamespace waybar {\n\nclass Factory;\nstruct waybar_output {\n  Glib::RefPtr<Gdk::Monitor> monitor;\n  std::string name;\n  std::string identifier;\n\n  std::unique_ptr<struct zxdg_output_v1, decltype(&zxdg_output_v1_destroy)> xdg_output = {\n      nullptr, &zxdg_output_v1_destroy};\n};\n\nenum class bar_layer : uint8_t {\n  BOTTOM,\n  TOP,\n  OVERLAY,\n};\n\nstruct bar_margins {\n  int top = 0;\n  int right = 0;\n  int bottom = 0;\n  int left = 0;\n};\n\nstruct bar_mode {\n  bar_layer layer;\n  bool exclusive;\n  bool passthrough;\n  bool visible;\n};\n\n#ifdef HAVE_SWAY\nnamespace modules::sway {\nclass BarIpcClient;\n}\n#endif  // HAVE_SWAY\n\nclass Bar : public sigc::trackable {\n public:\n  using bar_mode_map = std::map<std::string, struct bar_mode>;\n  static const bar_mode_map PRESET_MODES;\n  static const std::string MODE_DEFAULT;\n  static const std::string MODE_INVISIBLE;\n\n  Bar(struct waybar_output* w_output, const Json::Value&);\n  Bar(const Bar&) = delete;\n  ~Bar();\n\n  void setMode(const std::string& mode);\n  void setVisible(bool value);\n  void toggle();\n  void show();\n  void hide();\n  void handleSignal(int);\n  util::KillSignalAction getOnSigusr1Action();\n  util::KillSignalAction getOnSigusr2Action();\n\n  struct waybar_output* output;\n  Json::Value config;\n  struct wl_surface* surface;\n  bool visible = true;\n  Gtk::Window window;\n  Gtk::Orientation orientation = Gtk::ORIENTATION_HORIZONTAL;\n  Gtk::PositionType position = Gtk::POS_TOP;\n\n  int x_global;\n  int y_global;\n\n#ifdef HAVE_SWAY\n  std::string bar_id;\n#endif\n\n private:\n  void onMap(GdkEventAny*);\n  auto setupWidgets() -> void;\n  void getModules(const Factory&, const std::string&, waybar::Group*);\n  void setupAltFormatKeyForModule(const std::string& module_name);\n  void setupAltFormatKeyForModuleList(const char* module_list_name);\n  void setMode(const bar_mode&);\n  void setPassThrough(bool passthrough);\n  void setPosition(Gtk::PositionType position);\n  void onConfigure(GdkEventConfigure* ev);\n  void configureGlobalOffset(int width, int height);\n  void onOutputGeometryChanged();\n\n  /* Copy initial set of modes to allow customization */\n  bar_mode_map configured_modes = PRESET_MODES;\n  std::string last_mode_{MODE_DEFAULT};\n\n  struct bar_margins margins_;\n  uint32_t width_, height_;\n  bool passthrough_;\n\n  Gtk::Box left_;\n  Gtk::Box center_;\n  Gtk::Box right_;\n  Gtk::Box box_;\n  std::vector<std::shared_ptr<waybar::AModule>> modules_left_;\n  std::vector<std::shared_ptr<waybar::AModule>> modules_center_;\n  std::vector<std::shared_ptr<waybar::AModule>> modules_right_;\n#ifdef HAVE_SWAY\n  using BarIpcClient = modules::sway::BarIpcClient;\n  std::unique_ptr<BarIpcClient> _ipc_client;\n#endif\n  std::vector<std::shared_ptr<waybar::AModule>> modules_all_;\n\n  waybar::util::KillSignalAction onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1;\n  waybar::util::KillSignalAction onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/client.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <gdk/gdk.h>\n#include <gdk/gdkwayland.h>\n#include <wayland-client.h>\n\n#include \"bar.hpp\"\n#include \"config.hpp\"\n#include \"util/css_reload_helper.hpp\"\n#include \"util/portal.hpp\"\n\nstruct zwp_idle_inhibitor_v1;\nstruct zwp_idle_inhibit_manager_v1;\n\nnamespace waybar {\n\nclass Client {\n public:\n  static Client* inst();\n  int main(int argc, char* argv[]);\n  void reset();\n\n  Glib::RefPtr<Gtk::Application> gtk_app;\n  Glib::RefPtr<Gdk::Display> gdk_display;\n  struct wl_display* wl_display = nullptr;\n  struct wl_registry* registry = nullptr;\n  struct zxdg_output_manager_v1* xdg_output_manager = nullptr;\n  struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager = nullptr;\n  std::vector<std::unique_ptr<Bar>> bars;\n  Config config;\n  std::string bar_id;\n\n private:\n  Client() = default;\n  const std::string getStyle(const std::string& style, std::optional<Appearance> appearance);\n  void bindInterfaces();\n  void handleOutput(struct waybar_output& output);\n  auto setupCss(const std::string& css_file) -> void;\n  struct waybar_output& getOutput(void*);\n  std::vector<Json::Value> getOutputConfigs(struct waybar_output& output);\n\n  static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name,\n                           const char* interface, uint32_t version);\n  static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name);\n  static void handleOutputDone(void*, struct zxdg_output_v1*);\n  static void handleOutputName(void*, struct zxdg_output_v1*, const char*);\n  static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*);\n  void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);\n  void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);\n  void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);\n\n  Glib::RefPtr<Gtk::StyleContext> style_context_;\n  Glib::RefPtr<Gtk::CssProvider> css_provider_;\n  std::unique_ptr<Portal> portal;\n  std::list<struct waybar_output> outputs_;\n  std::unique_ptr<CssReloadHelper> m_cssReloadHelper;\n  std::string m_cssFile;\n  sigc::connection monitor_added_connection_;\n  sigc::connection monitor_removed_connection_;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/config.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n\n#include <optional>\n#include <string>\n\n#ifndef SYSCONFDIR\n#define SYSCONFDIR \"/etc\"\n#endif\n\nnamespace waybar {\n\nclass Config {\n public:\n  static const std::vector<std::string> CONFIG_DIRS;\n  static const char* CONFIG_PATH_ENV;\n\n  /* Try to find any of provided names in the supported set of config directories */\n  static std::optional<std::string> findConfigPath(\n      const std::vector<std::string>& names, const std::vector<std::string>& dirs = CONFIG_DIRS);\n\n  static std::vector<std::string> tryExpandPath(const std::string& base,\n                                                const std::string& filename);\n\n  Config() = default;\n\n  void load(const std::string& config);\n\n  Json::Value& getConfig() { return config_; }\n\n  std::vector<Json::Value> getOutputConfigs(const std::string& name, const std::string& identifier);\n\n private:\n  void setupConfig(Json::Value& dst, const std::string& config_file, int depth);\n  void resolveConfigIncludes(Json::Value& config, int depth);\n  void mergeConfig(Json::Value& a_config_, Json::Value& b_config_);\n  static std::vector<std::string> findIncludePath(\n      const std::string& name, const std::vector<std::string>& dirs = CONFIG_DIRS);\n\n  std::string config_file_;\n\n  Json::Value config_;\n};\n}  // namespace waybar\n"
  },
  {
    "path": "include/factory.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n\n#include <AModule.hpp>\n\nnamespace waybar {\n\nclass Bar;\n\nclass Factory {\n public:\n  Factory(const Bar& bar, const Json::Value& config);\n  AModule* makeModule(const std::string& name, const std::string& pos) const;\n\n private:\n  const Bar& bar_;\n  const Json::Value& config_;\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/group.hpp",
    "content": "#pragma once\n\n#include <gtkmm/box.h>\n#include <gtkmm/widget.h>\n#include <json/json.h>\n\n#include \"AModule.hpp\"\n#include \"gtkmm/revealer.h\"\n\nnamespace waybar {\n\nclass Group : public AModule {\n public:\n  Group(const std::string&, const std::string&, const Json::Value&, bool);\n  ~Group() override = default;\n  auto update() -> void override;\n  operator Gtk::Widget&() override;\n\n  virtual Gtk::Box& getBox();\n  void addWidget(Gtk::Widget& widget);\n\n protected:\n  Gtk::Box box;\n  Gtk::Box revealer_box;\n  Gtk::Revealer revealer;\n  bool is_first_widget = true;\n  bool is_drawer = false;\n  bool click_to_reveal = false;\n  std::string add_class_to_drawer_children;\n  bool handleMouseEnter(GdkEventCrossing* const& ev) override;\n  bool handleMouseLeave(GdkEventCrossing* const& ev) override;\n  bool handleToggle(GdkEventButton* const& ev) override;\n  bool handleScroll(GdkEventScroll* e) override;\n  void show_group();\n  void hide_group();\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/modules/backlight.hpp",
    "content": "#pragma once\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/backlight_backend.hpp\"\n#include \"util/json.hpp\"\n\nstruct udev;\nstruct udev_device;\n\nnamespace waybar::modules {\n\nclass Backlight : public ALabel {\n public:\n  Backlight(const std::string&, const Json::Value&);\n  virtual ~Backlight() = default;\n  auto update() -> void override;\n\n  bool handleScroll(GdkEventScroll* e) override;\n\n  const std::string preferred_device_;\n\n  std::string previous_format_;\n\n  util::BacklightBackend backend;\n};\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/backlight_slider.hpp",
    "content": "#pragma once\n\n#include <chrono>\n\n#include \"ASlider.hpp\"\n#include \"util/backlight_backend.hpp\"\n\nnamespace waybar::modules {\n\nclass BacklightSlider : public ASlider {\n public:\n  BacklightSlider(const std::string&, const Json::Value&);\n  virtual ~BacklightSlider() = default;\n\n  void update() override;\n  void onValueChanged() override;\n\n private:\n  std::chrono::milliseconds interval_;\n  std::string preferred_device_;\n  util::BacklightBackend backend;\n};\n\n}  // namespace waybar::modules"
  },
  {
    "path": "include/modules/battery.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <filesystem>\n#if defined(__linux__)\n#include <sys/inotify.h>\n#endif\n#include <poll.h>\n\n#include <algorithm>\n#include <fstream>\n#include <string>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"util/sleeper_thread.hpp\"\n#include \"util/udev_deleter.hpp\"\n\nnamespace waybar::modules {\n\nnamespace fs = std::filesystem;\n\nclass Battery : public ALabel {\n public:\n  Battery(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Battery();\n  auto update() -> void override;\n\n private:\n  static inline const fs::path data_dir_ = \"/sys/class/power_supply/\";\n\n  void refreshBatteries();\n  void worker();\n  const std::string getAdapterStatus(uint8_t capacity) const;\n  std::tuple<uint8_t, float, std::string, float, uint16_t, float> getInfos();\n  const std::string formatTimeRemaining(float hoursRemaining);\n  void setBarClass(std::string&);\n  void processEvents(std::string& state, std::string& status, uint8_t capacity);\n\n  std::map<fs::path, int> batteries_;\n  std::unique_ptr<udev, util::UdevDeleter> udev_;\n  std::array<pollfd, 1> poll_fds_;\n  std::unique_ptr<udev_monitor, util::UdevMonitorDeleter> mon_;\n  fs::path adapter_;\n  int battery_watch_fd_;\n  std::mutex battery_list_mutex_;\n  std::string old_status_;\n  std::string last_event_;\n  bool warnFirstTime_{true};\n  bool weightedAverage_{true};\n  const Bar& bar_;\n\n  util::SleeperThread thread_;\n  util::SleeperThread thread_battery_update_;\n  util::SleeperThread thread_timer_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/bluetooth.hpp",
    "content": "#pragma once\n\n#include \"ALabel.hpp\"\n#ifdef WANT_RFKILL\n#include \"util/rfkill.hpp\"\n#endif\n#include <gio/gio.h>\n\n#include <optional>\n#include <string>\n#include <vector>\n\nnamespace waybar::modules {\n\nclass Bluetooth : public ALabel {\n  struct ControllerInfo {\n    std::string path;\n    std::string address;\n    std::string address_type;\n    // std::string name; // just use alias instead\n    std::string alias;\n    bool powered;\n    bool discoverable;\n    bool pairable;\n    bool discovering;\n  };\n\n  // NOTE: there are some properties that not all devices provide\n  struct DeviceInfo {\n    std::string path;\n    std::string paired_controller;\n    std::string address;\n    std::string address_type;\n    // std::optional<std::string> name; // just use alias instead\n    std::string alias;\n    std::optional<std::string> icon;\n    bool paired;\n    bool trusted;\n    bool blocked;\n    bool connected;\n    bool services_resolved;\n    // NOTE: experimental feature in bluez\n    std::optional<unsigned char> battery_percentage;\n  };\n\n public:\n  Bluetooth(const std::string&, const Json::Value&);\n  virtual ~Bluetooth() = default;\n  auto update() -> void override;\n\n private:\n  static auto onObjectAdded(GDBusObjectManager*, GDBusObject*, gpointer) -> void;\n  static auto onObjectRemoved(GDBusObjectManager*, GDBusObject*, gpointer) -> void;\n\n  static auto onInterfaceAddedOrRemoved(GDBusObjectManager*, GDBusObject*, GDBusInterface*,\n                                        gpointer) -> void;\n  static auto onInterfaceProxyPropertiesChanged(GDBusObjectManagerClient*, GDBusObjectProxy*,\n                                                GDBusProxy*, GVariant*, const gchar* const*,\n                                                gpointer) -> void;\n\n  auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional<unsigned char>;\n  auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool;\n  auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool;\n\n  // Returns std::nullopt if no controller could be found\n  auto findCurController() -> std::optional<ControllerInfo>;\n  auto findConnectedDevices(const std::string&, std::vector<DeviceInfo>&) -> void;\n\n#ifdef WANT_RFKILL\n  util::Rfkill rfkill_;\n#endif\n  const std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)> manager_;\n\n  std::string state_;\n  std::optional<ControllerInfo> cur_controller_;\n  std::vector<DeviceInfo> connected_devices_;\n  DeviceInfo cur_focussed_device_;\n  std::string device_enumerate_;\n\n  std::vector<std::string> device_preference_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/cava/cavaGLSL.hpp",
    "content": "#pragma once\n\n#include <epoxy/gl.h>\n\n#include \"AModule.hpp\"\n#include \"cava_backend.hpp\"\n\nnamespace waybar::modules::cava {\n\nclass CavaGLSL final : public AModule, public Gtk::GLArea {\n public:\n  CavaGLSL(const std::string&, const Json::Value&);\n  ~CavaGLSL() = default;\n\n private:\n  std::shared_ptr<CavaBackend> backend_;\n  struct ::cava::config_params prm_;\n  int frame_counter{0};\n  bool silence_{false};\n  bool hide_on_silence_{false};\n  // Cava method\n  auto onUpdate(const ::cava::audio_raw& input) -> void;\n  auto onSilence() -> void;\n  // Member variable to store the shared pointer\n  std::shared_ptr<::cava::audio_raw> m_data_;\n  GLuint shaderProgram_;\n  // OpenGL variables\n  GLuint fbo_;\n  GLuint texture_;\n  GLint uniform_bars_;\n  GLint uniform_previous_bars_;\n  GLint uniform_bars_count_;\n  GLint uniform_time_;\n  // Methods\n  void onRealize();\n  bool onRender(const Glib::RefPtr<Gdk::GLContext>& context);\n\n  void initShaders();\n  void initSurface();\n  void initGLSL();\n  GLuint loadShader(const std::string& fileName, GLenum type);\n};\n}  // namespace waybar::modules::cava\n"
  },
  {
    "path": "include/modules/cava/cavaRaw.hpp",
    "content": "#pragma once\n\n#include \"ALabel.hpp\"\n#include \"cava_backend.hpp\"\n\nnamespace waybar::modules::cava {\n\nclass Cava final : public ALabel, public sigc::trackable {\n public:\n  Cava(const std::string&, const Json::Value&);\n  ~Cava() = default;\n  auto doAction(const std::string& name) -> void override;\n\n private:\n  std::shared_ptr<CavaBackend> backend_;\n  // Text to display\n  Glib::ustring label_text_{\"\"};\n  bool silence_{false};\n  bool hide_on_silence_{false};\n  std::string format_silent_{\"\"};\n  int ascii_range_{0};\n  // Cava method\n  void pause_resume();\n  auto onUpdate(const std::string& input) -> void;\n  auto onSilence() -> void;\n  // ModuleActionMap\n  static inline std::map<const std::string, void (waybar::modules::cava::Cava::* const)()>\n      actionMap_{{\"mode\", &waybar::modules::cava::Cava::pause_resume}};\n};\n}  // namespace waybar::modules::cava\n"
  },
  {
    "path": "include/modules/cava/cava_backend.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n#include <sigc++/sigc++.h>\n\n#include \"util/sleeper_thread.hpp\"\n\nnamespace cava {\nextern \"C\" {\n// Need sdl_glsl output feature to be enabled on libcava\n#ifndef SDL_GLSL\n#define SDL_GLSL\n#endif\n\n#include <cava/common.h>\n\n#ifdef SDL_GLSL\n#undef SDL_GLSL\n#endif\n}\n}  // namespace cava\n\nnamespace waybar::modules::cava {\nusing namespace std::literals::chrono_literals;\n\nclass CavaBackend final {\n public:\n  static std::shared_ptr<CavaBackend> inst(const Json::Value& config);\n\n  virtual ~CavaBackend();\n  // Methods\n  int getAsciiRange();\n  void doPauseResume();\n  void Update();\n  const struct ::cava::config_params* getPrm();\n  std::chrono::milliseconds getFrameTimeMilsec();\n\n  // Signal accessor\n  using type_signal_update = sigc::signal<void(const std::string&)>;\n  type_signal_update signal_update();\n  using type_signal_audio_raw_update = sigc::signal<void(const ::cava::audio_raw&)>;\n  type_signal_audio_raw_update signal_audio_raw_update();\n  using type_signal_silence = sigc::signal<void()>;\n  type_signal_silence signal_silence();\n\n private:\n  CavaBackend(const Json::Value& config);\n  util::SleeperThread read_thread_;\n  util::SleeperThread out_thread_;\n\n  // Cava API to read audio source\n  ::cava::ptr input_source_{NULL};\n\n  struct ::cava::error_s error_{};          // cava errors\n  struct ::cava::config_params prm_{};      // cava parameters\n  struct ::cava::audio_raw audio_raw_{};    // cava handled raw audio data(is based on audio_data)\n  struct ::cava::audio_data audio_data_{};  // cava audio data\n  struct ::cava::cava_plan* plan_{NULL};    //{new cava_plan{}};\n\n  std::chrono::seconds fetch_input_delay_{4};\n  // Delay to handle audio source\n  std::chrono::milliseconds frame_time_milsec_{1s};\n\n  const Json::Value& config_;\n  int re_paint_{0};\n  bool silence_{false};\n  bool silence_prev_{false};\n  std::chrono::seconds suspend_silence_delay_{0};\n  int sleep_counter_{0};\n  std::string output_{};\n  // Methods\n  void invoke();\n  void execute();\n  bool isSilence();\n  void doUpdate(bool force = false);\n  void loadConfig();\n  void freeBackend();\n\n  // Signal\n  type_signal_update m_signal_update_;\n  type_signal_audio_raw_update m_signal_audio_raw_;\n  type_signal_silence m_signal_silence_;\n};\n}  // namespace waybar::modules::cava\n"
  },
  {
    "path": "include/modules/cava/cava_frontend.hpp",
    "content": "#pragma once\n\n#ifdef HAVE_LIBCAVA\n#include \"cavaRaw.hpp\"\n#include \"cava_backend.hpp\"\n#ifdef HAVE_LIBCAVAGLSL\n#include \"cavaGLSL.hpp\"\n#endif\n#endif\n\nnamespace waybar::modules::cava {\nAModule* getModule(const std::string& id, const Json::Value& config) {\n#ifdef HAVE_LIBCAVA\n  const std::shared_ptr<CavaBackend> backend_{waybar::modules::cava::CavaBackend::inst(config)};\n  switch (backend_->getPrm()->output) {\n#ifdef HAVE_LIBCAVAGLSL\n    case ::cava::output_method::OUTPUT_SDL_GLSL:\n      return new waybar::modules::cava::CavaGLSL(id, config);\n#endif\n    default:\n      return new waybar::modules::cava::Cava(id, config);\n  }\n#else\n  throw std::runtime_error(\"Unknown module\");\n#endif\n};\n}  // namespace waybar::modules::cava\n"
  },
  {
    "path": "include/modules/cffi.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"AModule.hpp\"\n#include \"util/command.hpp\"\n#include \"util/json.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nnamespace ffi {\nextern \"C\" {\ntypedef struct wbcffi_module wbcffi_module;\n\ntypedef struct {\n  wbcffi_module* obj;\n  const char* waybar_version;\n  GtkContainer* (*get_root_widget)(wbcffi_module*);\n  void (*queue_update)(wbcffi_module*);\n} wbcffi_init_info;\n\nstruct wbcffi_config_entry {\n  const char* key;\n  const char* value;\n};\n}\n}  // namespace ffi\n\nclass CFFI : public AModule {\n public:\n  CFFI(const std::string&, const std::string&, const Json::Value&);\n  virtual ~CFFI();\n\n  virtual auto refresh(int signal) -> void override;\n  virtual auto doAction(const std::string& name) -> void override;\n  virtual auto update() -> void override;\n\n private:\n  ///\n  void* cffi_instance_ = nullptr;\n\n  typedef void*(InitFn)(const ffi::wbcffi_init_info* init_info,\n                        const ffi::wbcffi_config_entry* config_entries, size_t config_entries_len);\n  typedef void(DenitFn)(void* instance);\n  typedef void(RefreshFn)(void* instance, int signal);\n  typedef void(DoActionFn)(void* instance, const char* name);\n  typedef void(UpdateFn)(void* instance);\n\n  // FFI hooks\n  struct {\n    std::function<InitFn> init = nullptr;\n    std::function<DenitFn> deinit = nullptr;\n    std::function<RefreshFn> refresh = [](void*, int) {};\n    std::function<DoActionFn> doAction = [](void*, const char*) {};\n    std::function<UpdateFn> update = [](void*) {};\n  } hooks_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/clock.hpp",
    "content": "#pragma once\n\n#include \"ALabel.hpp\"\n#include \"util/date.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nconst std::string kCldPlaceholder{\"calendar\"};\nconst std::string kTZPlaceholder{\"tz_list\"};\nconst std::string kOrdPlaceholder{\"ordinal_date\"};\n\nenum class CldMode { MONTH, YEAR };\nenum class WS { LEFT, RIGHT, HIDDEN };\n\nclass Clock final : public ALabel {\n public:\n  Clock(const std::string&, const Json::Value&);\n  virtual ~Clock() = default;\n  auto update() -> void override;\n  auto doAction(const std::string&) -> void override;\n\n private:\n  const std::locale m_locale_;\n  // tooltip\n  const std::string m_tlpFmt_;\n  std::string m_tlpText_{\"\"};                 // tooltip text to print\n  const Glib::RefPtr<Gtk::Label> m_tooltip_;  // tooltip as a separate Gtk::Label\n  bool query_tlp_cb(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip);\n  // Calendar\n  const bool cldInTooltip_;  // calendar in tooltip\n  /*\n    0 - calendar.format.months\n    1 - calendar.format.weekdays\n    2 - calendar.format.days\n    3 - calendar.format.today\n    4 - calendar.format.weeks\n    5 - tooltip-format\n   */\n  std::map<int, std::string const> fmtMap_;\n  uint cldMonCols_{3};                 // calendar count month columns\n  int cldWnLen_{3};                    // calendar week number length\n  const int cldMonColLen_{20};         // calendar month column length\n  WS cldWPos_{WS::HIDDEN};             // calendar week side to print\n  date::months cldCurrShift_{0};       // calendar months shift\n  int cldShift_{1};                    // calendar months shift factor\n  date::year_month_day cldYearShift_;  // calendar Year mode. Cached ymd\n  std::string cldYearCached_;          // calendar Year mode. Cached calendar\n  date::year_month cldMonShift_;       // calendar Month mode. Cached ym\n  std::string cldMonCached_;           // calendar Month mode. Cached calendar\n  date::day cldBaseDay_{0};      // calendar Cached day. Is used when today is changing(midnight)\n  std::string cldText_{\"\"};      // calendar text to print\n  bool iso8601Calendar_{false};  // whether the calendar is in ISO8601\n  CldMode cldMode_{CldMode::MONTH};\n  auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd,\n                    const date::time_zone* tz) -> const std::string;\n\n  // get local time zone\n  auto local_zone() -> const date::time_zone*;\n\n  // time zoned time in tooltip\n  const bool tzInTooltip_;                      // if need to print time zones text\n  std::vector<const date::time_zone*> tzList_;  // time zones list\n  int tzCurrIdx_;                               // current time zone index for tzList_\n  std::string tzText_{\"\"};                      // time zones text to print\n  std::string tzTooltipFormat_{\"\"};             // optional timezone tooltip format\n  util::SleeperThread thread_;\n\n  // ordinal date in tooltip\n  const bool ordInTooltip_;\n  std::string ordText_{\"\"};\n  auto get_ordinal_date(const date::year_month_day& today) -> std::string;\n\n  auto getTZtext(date::sys_seconds now) -> std::string;\n  auto first_day_of_week() -> date::weekday;\n  // Module actions\n  void cldModeSwitch();\n  void cldShift_up();\n  void cldShift_down();\n  void cldShift_reset();\n  void tz_up();\n  void tz_down();\n  // Module Action Map\n  static inline std::map<const std::string, void (waybar::modules::Clock::* const)()> actionMap_{\n      {\"mode\", &waybar::modules::Clock::cldModeSwitch},\n      {\"shift_up\", &waybar::modules::Clock::cldShift_up},\n      {\"shift_down\", &waybar::modules::Clock::cldShift_down},\n      {\"shift_reset\", &waybar::modules::Clock::cldShift_reset},\n      {\"tz_up\", &waybar::modules::Clock::tz_up},\n      {\"tz_down\", &waybar::modules::Clock::tz_down}};\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/cpu.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <cstdint>\n#include <fstream>\n#include <numeric>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Cpu : public ALabel {\n public:\n  Cpu(const std::string&, const Json::Value&);\n  virtual ~Cpu() = default;\n  auto update() -> void override;\n\n private:\n  std::vector<std::tuple<size_t, size_t>> prev_times_;\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/cpu_frequency.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <cstdint>\n#include <fstream>\n#include <numeric>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass CpuFrequency : public ALabel {\n public:\n  CpuFrequency(const std::string&, const Json::Value&);\n  virtual ~CpuFrequency() = default;\n  auto update() -> void override;\n\n  // This is a static member because it is also used by the cpu module.\n  static std::tuple<float, float, float> getCpuFrequency();\n\n private:\n  static std::vector<float> parseCpuFrequencies();\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/cpu_usage.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <cstdint>\n#include <fstream>\n#include <numeric>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass CpuUsage : public ALabel {\n public:\n  CpuUsage(const std::string&, const Json::Value&);\n  virtual ~CpuUsage() = default;\n  auto update() -> void override;\n\n  // This is a static member because it is also used by the cpu module.\n  static std::tuple<std::vector<uint16_t>, std::string> getCpuUsage(\n      std::vector<std::tuple<size_t, size_t>>&);\n\n private:\n  static std::vector<std::tuple<size_t, size_t>> parseCpuinfo();\n\n  std::vector<std::tuple<size_t, size_t>> prev_times_;\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/custom.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <csignal>\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"util/command.hpp\"\n#include \"util/json.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Custom : public ALabel {\n public:\n  Custom(const std::string&, const std::string&, const Json::Value&, const std::string&);\n  virtual ~Custom();\n  auto update() -> void override;\n  void refresh(int /*signal*/) override;\n\n private:\n  void delayWorker();\n  void continuousWorker();\n  void waitingWorker();\n  void parseOutputRaw();\n  void parseOutputJson();\n  void handleEvent();\n  bool handleScroll(GdkEventScroll* e) override;\n  bool handleToggle(GdkEventButton* const& e) override;\n\n  const std::string name_;\n  const std::string output_name_;\n  std::string text_;\n  std::string id_;\n  std::string alt_;\n  std::string tooltip_;\n  std::string last_tooltip_markup_;\n  const bool tooltip_format_enabled_;\n  std::vector<std::string> class_;\n  int percentage_;\n  FILE* fp_;\n  int pid_;\n  util::command::res output_;\n  util::JsonParser parser_;\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/disk.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <sys/statvfs.h>\n\n#include <fstream>\n\n#include \"ALabel.hpp\"\n#include \"util/format.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Disk : public ALabel {\n public:\n  Disk(const std::string&, const Json::Value&);\n  virtual ~Disk() = default;\n  auto update() -> void override;\n\n private:\n  util::SleeperThread thread_;\n  std::string path_;\n  std::string unit_;\n\n  float calc_specific_divisor(const std::string& divisor);\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/dwl/tags.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <wayland-client.h>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"dwl-ipc-unstable-v2-client-protocol.h\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::dwl {\n\nclass Tags : public waybar::AModule {\n public:\n  Tags(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Tags();\n\n  // Handlers for wayland events\n  void handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused);\n\n  void handle_primary_clicked(uint32_t tag);\n  bool handle_button_press(GdkEventButton* event_button, uint32_t tag);\n\n  struct zdwl_ipc_manager_v2* status_manager_;\n  struct wl_seat* seat_;\n\n private:\n  const waybar::Bar& bar_;\n  Gtk::Box box_;\n  std::vector<Gtk::Button> buttons_;\n  struct zdwl_ipc_output_v2* output_status_;\n};\n\n} /* namespace waybar::modules::dwl */\n"
  },
  {
    "path": "include/modules/dwl/window.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <string>\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"dwl-ipc-unstable-v2-client-protocol.h\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::dwl {\n\nclass Window : public AAppIconLabel, public sigc::trackable {\n public:\n  Window(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Window();\n\n  void handle_layout(const uint32_t layout);\n  void handle_title(const char* title);\n  void handle_appid(const char* ppid);\n  void handle_layout_symbol(const char* layout_symbol);\n  void handle_frame();\n\n  struct zdwl_ipc_manager_v2* status_manager_;\n\n private:\n  const Bar& bar_;\n\n  std::string title_;\n  std::string appid_;\n  std::string layout_symbol_;\n  uint32_t layout_;\n\n  struct zdwl_ipc_output_v2* output_status_;\n};\n\n}  // namespace waybar::modules::dwl\n"
  },
  {
    "path": "include/modules/ext/workspace_manager.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <gtkmm/button.h>\n#include <gtkmm/image.h>\n#include <gtkmm/label.h>\n\n#include <map>\n#include <memory>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"ext-workspace-v1-client-protocol.h\"\n\nnamespace waybar::modules::ext {\n\nclass WorkspaceGroup;\nclass Workspace;\n\nclass WorkspaceManager final : public AModule {\n public:\n  WorkspaceManager(const std::string& id, const waybar::Bar& bar, const Json::Value& config);\n  ~WorkspaceManager() override;\n  void register_manager(wl_registry* registry, uint32_t name, uint32_t version);\n  void remove_workspace_group(uint32_t id);\n  void remove_workspace(uint32_t id);\n  void set_needs_sorting() { needs_sorting_ = true; }\n\n  // wl events\n  void handle_workspace_group(ext_workspace_group_handle_v1* handle);\n  void handle_workspace(ext_workspace_handle_v1* handle);\n  void handle_done();\n  void handle_finished();\n\n  // wl requests\n  void commit() const;\n\n private:\n  void update() override;\n  bool has_button(const Gtk::Button* button);\n  void sort_workspaces();\n  void clear_buttons();\n  void update_buttons();\n\n  static uint32_t group_global_id;\n  static uint32_t workspace_global_id;\n  uint32_t workspace_name = 0;\n\n  bool sort_by_id_ = false;\n  bool sort_by_name_ = true;\n  bool sort_by_coordinates_ = false;\n  bool all_outputs_ = false;\n\n  const waybar::Bar& bar_;\n  Gtk::Box box_;\n\n  ext_workspace_manager_v1* ext_manager_ = nullptr;\n  std::vector<std::unique_ptr<WorkspaceGroup>> groups_;\n  std::vector<std::unique_ptr<Workspace>> workspaces_;\n\n  bool needs_sorting_ = false;\n};\n\nclass WorkspaceGroup {\n public:\n  WorkspaceGroup(WorkspaceManager& manager, ext_workspace_group_handle_v1* handle, uint32_t id);\n  ~WorkspaceGroup();\n\n  u_int32_t id() const { return id_; }\n  bool has_output(const wl_output* output);\n  bool has_workspace(const ext_workspace_handle_v1* workspace);\n\n  // wl events\n  void handle_capabilities(uint32_t capabilities);\n  void handle_output_enter(wl_output* output);\n  void handle_output_leave(wl_output* output);\n  void handle_workspace_enter(ext_workspace_handle_v1* handle);\n  void handle_workspace_leave(ext_workspace_handle_v1* handle);\n  void handle_removed();\n\n private:\n  WorkspaceManager& workspaces_manager_;\n  ext_workspace_group_handle_v1* ext_handle_;\n  uint32_t id_;\n  std::vector<wl_output*> outputs_;\n  std::vector<ext_workspace_handle_v1*> workspaces_;\n};\n\nclass Workspace {\n public:\n  Workspace(const Json::Value& config, WorkspaceManager& manager, ext_workspace_handle_v1* handle,\n            uint32_t id, const std::string& name);\n  ~Workspace();\n\n  ext_workspace_handle_v1* handle() const { return ext_handle_; }\n  u_int32_t id() const { return id_; }\n  std::string& workspace_id() { return workspace_id_; }\n  std::string& name() { return name_; }\n  std::vector<u_int32_t>& coordinates() { return coordinates_; }\n  Gtk::Button& button() { return button_; }\n  void update();\n\n  // wl events\n  void handle_id(const std::string& id);\n  void handle_name(const std::string& name);\n  void handle_coordinates(const std::vector<uint32_t>& coordinates);\n  void handle_state(uint32_t state);\n  void handle_capabilities(uint32_t capabilities);\n  void handle_removed();\n\n  // gdk events\n  bool handle_clicked(const GdkEventButton* button) const;\n\n private:\n  bool has_state(uint32_t state) const { return (state_ & state) == state; }\n  std::string icon();\n\n  WorkspaceManager& workspace_manager_;\n  ext_workspace_handle_v1* ext_handle_ = nullptr;\n  uint32_t id_;\n  uint32_t state_ = 0;\n  std::string workspace_id_;\n  std::string name_;\n  std::vector<uint32_t> coordinates_;\n\n  bool active_only_ = false;\n  bool ignore_hidden_ = true;\n  std::string format_;\n  bool with_icon_ = false;\n  static std::map<std::string, std::string> icon_map_;\n  std::string on_click_action_;\n  std::string on_click_middle_action_;\n  std::string on_click_right_action_;\n\n  Gtk::Button button_;\n  Gtk::Box content_;\n  Gtk::Label label_;\n};\n\n}  // namespace waybar::modules::ext\n"
  },
  {
    "path": "include/modules/ext/workspace_manager_binding.hpp",
    "content": "#include \"ext-workspace-v1-client-protocol.h\"\n\nnamespace waybar::modules::ext {\nvoid add_registry_listener(void* data);\nvoid add_workspace_listener(ext_workspace_handle_v1* workspace_handle, void* data);\nvoid add_workspace_group_listener(ext_workspace_group_handle_v1* workspace_group_handle,\n                                  void* data);\next_workspace_manager_v1* workspace_manager_bind(wl_registry* registry, uint32_t name,\n                                                 uint32_t version, void* data);\n}  // namespace waybar::modules::ext\n"
  },
  {
    "path": "include/modules/gamemode.hpp",
    "content": "#pragma once\n\n#include <iostream>\n#include <map>\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"giomm/dbusconnection.h\"\n#include \"giomm/dbusproxy.h\"\n#include \"glibconfig.h\"\n#include \"gtkmm/box.h\"\n#include \"gtkmm/image.h\"\n#include \"gtkmm/label.h\"\n#include \"gtkmm/overlay.h\"\n\nnamespace waybar::modules {\n\nclass Gamemode : public AModule {\n public:\n  Gamemode(const std::string&, const Json::Value&);\n  virtual ~Gamemode();\n  auto update() -> void override;\n\n private:\n  const std::string DEFAULT_ICON_NAME = \"input-gaming-symbolic\";\n  const std::string DEFAULT_FORMAT = \"{glyph}\";\n  const std::string DEFAULT_FORMAT_ALT = \"{glyph} {count}\";\n  const std::string DEFAULT_TOOLTIP_FORMAT = \"Games running: {count}\";\n  const std::string DEFAULT_GLYPH = \"󰊴\";\n\n  void appear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name,\n              const Glib::ustring& name_owner);\n  void disappear(const Glib::RefPtr<Gio::DBus::Connection>& connection, const Glib::ustring& name);\n  void prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                          const Glib::ustring& sender_name, const Glib::ustring& object_path,\n                          const Glib::ustring& interface_name, const Glib::ustring& signal_name,\n                          const Glib::VariantContainerBase& parameters);\n  void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                 const Glib::VariantContainerBase& arguments);\n\n  void getData();\n  bool handleToggle(GdkEventButton* const&) override;\n\n  // Config\n  std::string format = DEFAULT_FORMAT;\n  std::string format_alt = DEFAULT_FORMAT_ALT;\n  std::string tooltip_format = DEFAULT_TOOLTIP_FORMAT;\n  std::string glyph = DEFAULT_GLYPH;\n  bool tooltip = true;\n  bool hideNotRunning = true;\n  bool useIcon = true;\n  uint iconSize = 20;\n  uint iconSpacing = 4;\n  std::string iconName = DEFAULT_ICON_NAME;\n\n  Gtk::Box box_;\n  Gtk::Image icon_;\n  Gtk::Label label_;\n\n  const std::string dbus_name = \"com.feralinteractive.GameMode\";\n  const std::string dbus_obj_path = \"/com/feralinteractive/GameMode\";\n  const std::string dbus_interface = \"org.freedesktop.DBus.Properties\";\n  const std::string dbus_get_interface = \"com.feralinteractive.GameMode\";\n\n  uint gameCount = 0;\n\n  std::string lastStatus;\n  bool showAltText = false;\n\n  guint login1_id;\n  Glib::RefPtr<Gio::DBus::Proxy> gamemode_proxy;\n  Glib::RefPtr<Gio::DBus::Connection> system_connection;\n  bool gamemodeRunning;\n  guint gamemodeWatcher_id;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/gps.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <sys/statvfs.h>\n\n#ifdef WANT_RFKILL\n#include \"util/rfkill.hpp\"\n#endif\n\n#include <gps.h>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Gps : public ALabel {\n public:\n  Gps(const std::string&, const Json::Value&);\n  virtual ~Gps();\n  auto update() -> void override;\n\n private:\n#ifdef WANT_RFKILL\n  util::Rfkill rfkill_;\n#endif\n  const std::string getFixModeName() const;\n  const std::string getFixModeString() const;\n\n  const std::string getFixStatusString() const;\n\n  util::SleeperThread thread_, gps_thread_;\n  gps_data_t gps_data_;\n  std::string state_;\n\n  bool hideDisconnected = true;\n  bool hideNoFix = false;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/hyprland/backend.hpp",
    "content": "#pragma once\n\n#include <atomic>\n#include <filesystem>\n#include <list>\n#include <mutex>\n#include <string>\n#include <thread>\n#include <utility>\n\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nclass EventHandler {\n public:\n  virtual void onEvent(const std::string& ev) = 0;\n  virtual ~EventHandler() = default;\n};\n\n/// If you want to use the Hyprland IPC, simply use IPC::inst() to get the singleton instance.\n/// Do not create multiple instances.\nclass IPC {\n protected:\n  IPC();  // use IPC::inst() instead.\n\n public:\n  ~IPC();\n  static IPC& inst();\n\n  void registerForIPC(const std::string& ev, EventHandler* ev_handler);\n  void unregisterForIPC(EventHandler* handler);\n\n  static std::string getSocket1Reply(const std::string& rq);\n  Json::Value getSocket1JsonReply(const std::string& rq);\n  static std::filesystem::path getSocketFolder(const char* instanceSig);\n\n protected:\n  static std::filesystem::path socketFolder_;\n\n private:\n  void socketListener();\n  void parseIPC(const std::string&);\n\n  std::thread ipcThread_;\n  std::mutex callbackMutex_;\n  std::mutex socketMutex_;\n  util::JsonParser parser_;\n  std::list<std::pair<std::string, EventHandler*>> callbacks_;\n  int socketfd_ = -1;  // the hyprland socket file descriptor\n  pid_t socketOwnerPid_ = -1;\n  std::atomic<bool> running_ = true;  // the ipcThread will stop running when this is false\n};\n};  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/language.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nclass Language : public waybar::ALabel, public EventHandler {\n public:\n  Language(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Language();\n\n  auto update() -> void override;\n\n private:\n  void onEvent(const std::string&) override;\n\n  void initLanguage();\n\n  struct Layout {\n    std::string full_name;\n    std::string short_name;\n    std::string variant;\n    std::string short_description;\n  };\n\n  static auto getLayout(const std::string&) -> Layout;\n\n  std::mutex mutex_;\n  const Bar& bar_;\n  util::JsonParser parser_;\n\n  Layout layout_;\n\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/submap.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nclass Submap : public waybar::ALabel, public EventHandler {\n public:\n  Submap(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Submap() override;\n\n  auto update() -> void override;\n\n private:\n  auto parseConfig(const Json::Value&) -> void;\n  void onEvent(const std::string& ev) override;\n\n  std::mutex mutex_;\n  const Bar& bar_;\n  util::JsonParser parser_;\n  std::string submap_;\n  std::string prev_submap_;\n  bool always_on_ = false;\n  std::string default_submap_ = \"Default\";\n\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/window.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <string>\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nclass Window : public waybar::AAppIconLabel, public EventHandler {\n public:\n  Window(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Window() override;\n\n  auto update() -> void override;\n\n private:\n  struct Workspace {\n    int id = 0;\n    int windows = 0;\n    std::string last_window;\n    std::string last_window_title;\n\n    static auto parse(const Json::Value& value) -> Workspace;\n  };\n\n  struct WindowData {\n    bool floating = false;\n    int monitor = -1;\n    std::string class_name;\n    std::string initial_class_name;\n    std::string title;\n    std::string initial_title;\n    bool fullscreen = false;\n    bool grouped = false;\n\n    static auto parse(const Json::Value&) -> WindowData;\n  };\n\n  static auto getActiveWorkspace(const std::string&) -> Workspace;\n  static auto getActiveWorkspace() -> Workspace;\n  void onEvent(const std::string& ev) override;\n  void queryActiveWorkspace();\n  void setClass(const std::string&, bool enable);\n\n  bool separateOutputs_ = false;\n  std::mutex mutex_;\n  const Bar& bar_;\n  util::JsonParser parser_;\n  WindowData windowData_;\n  Workspace workspace_;\n  std::string soloClass_;\n  std::string lastSoloClass_;\n  bool solo_ = false;\n  bool allFloating_ = false;\n  bool swallowing_ = false;\n  bool fullscreen_ = false;\n  bool focused_ = false;\n\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/windowcount.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <string>\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nclass WindowCount : public waybar::AAppIconLabel, public EventHandler {\n public:\n  WindowCount(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~WindowCount() override;\n\n  auto update() -> void override;\n\n private:\n  struct Workspace {\n    int id;\n    int windows;\n    bool hasfullscreen;\n    static auto parse(const Json::Value& value) -> Workspace;\n  };\n\n  auto getActiveWorkspace(const std::string&) -> Workspace;\n  auto getActiveWorkspace() -> Workspace;\n  void onEvent(const std::string& ev) override;\n  void queryActiveWorkspace();\n  void setClass(const std::string&, bool enable);\n\n  bool separateOutputs_;\n  std::mutex mutex_;\n  const Bar& bar_;\n  Workspace workspace_;\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/windowcreationpayload.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <json/value.h>\n\n#include <cstddef>\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <optional>\n#include <regex>\n#include <string>\n#include <variant>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"util/enum.hpp\"\n#include \"util/regex_collection.hpp\"\n\nusing WindowAddress = std::string;\n\nnamespace waybar::modules::hyprland {\n\nclass Workspaces;\n\nstruct WindowRepr {\n  std::string address;\n  std::string window_class;\n  std::string window_title;\n  std::string repr_rewrite;\n  bool isActive = false;\n\n public:\n  bool empty() const { return address.empty(); }\n  void setActive(bool value) { isActive = value; }\n};\n\nclass WindowCreationPayload {\n public:\n  WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,\n                        WindowRepr window_repr);\n  WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address,\n                        const std::string& window_class, const std::string& window_title,\n                        bool is_active);\n  WindowCreationPayload(Json::Value const& client_data);\n\n  int incrementTimeSpentUncreated();\n  bool isEmpty(Workspaces& workspace_manager);\n  bool reprIsReady() const { return std::holds_alternative<Repr>(m_window); }\n  WindowRepr repr(Workspaces& workspace_manager);\n  void setActive(bool value) { m_isActive = value; }\n\n  std::string getWorkspaceName() const { return m_workspaceName; }\n  WindowAddress getAddress() const { return m_windowAddress; }\n\n  void moveToWorkspace(std::string& new_workspace_name);\n\n private:\n  void clearAddr();\n  void clearWorkspaceName();\n\n  using Repr = WindowRepr;\n  using ClassAndTitle = std::pair<std::string, std::string>;\n  std::variant<Repr, ClassAndTitle> m_window;\n\n  WindowAddress m_windowAddress;\n  std::string m_workspaceName;\n  bool m_isActive = false;\n\n  int m_timeSpentUncreated = 0;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/workspace.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <json/value.h>\n\n#include <cstddef>\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <optional>\n#include <regex>\n#include <string>\n#include <variant>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"modules/hyprland/windowcreationpayload.hpp\"\n#include \"util/enum.hpp\"\n#include \"util/regex_collection.hpp\"\n\nusing WindowAddress = std::string;\n\nnamespace waybar::modules::hyprland {\n\nclass Workspaces;\nclass Workspace {\n public:\n  explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,\n                     const Json::Value& clients_data = Json::Value::nullRef);\n  std::string& selectIcon(std::map<std::string, std::string>& icons_map);\n  Gtk::Button& button() { return m_button; };\n\n  int id() const { return m_id; };\n  std::string name() const { return m_name; };\n  std::string output() const { return m_output; };\n  bool isActive() const { return m_isActive; };\n  bool isSpecial() const { return m_isSpecial; };\n  bool isPersistent() const { return m_isPersistentRule || m_isPersistentConfig; };\n  bool isPersistentConfig() const { return m_isPersistentConfig; };\n  bool isPersistentRule() const { return m_isPersistentRule; };\n  bool isVisible() const { return m_isVisible; };\n  bool isUrgent() const { return m_isUrgent; };\n\n  bool handleClicked(GdkEventButton* bt) const;\n  void setActive(bool value = true) { m_isActive = value; };\n  void setPersistentRule(bool value = true) { m_isPersistentRule = value; };\n  void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; };\n  void setUrgent(bool value = true) { m_isUrgent = value; };\n  void setVisible(bool value = true) { m_isVisible = value; };\n  void setWindows(uint value) { m_windows = value; };\n  void setName(std::string const& value) { m_name = value; };\n  void setOutput(std::string const& value) { m_output = value; };\n  bool containsWindow(WindowAddress const& addr) const {\n    return std::ranges::any_of(m_windowMap,\n                               [&addr](const auto& window) { return window.address == addr; });\n  };\n  void insertWindow(WindowCreationPayload create_window_payload);\n  void initializeWindowMap(const Json::Value& clients_data);\n  void setActiveWindow(WindowAddress const& addr);\n\n  bool onWindowOpened(WindowCreationPayload const& create_window_payload);\n  std::optional<WindowRepr> closeWindow(WindowAddress const& addr);\n\n  void update(const std::string& workspace_icon);\n\n private:\n  Workspaces& m_workspaceManager;\n\n  int m_id;\n  std::string m_name;\n  std::string m_output;\n  uint m_windows;\n  bool m_isActive = false;\n  bool m_isSpecial = false;\n  bool m_isPersistentRule = false;    // represents the persistent state in hyprland\n  bool m_isPersistentConfig = false;  // represents the persistent state in the Waybar config\n  bool m_isUrgent = false;\n  bool m_isVisible = false;\n\n  std::vector<WindowRepr> m_windowMap;\n\n  Gtk::Button m_button;\n  Gtk::Box m_content;\n  Gtk::Label m_labelBefore;\n  Gtk::Label m_labelAfter;\n\n  bool isEmpty() const;\n  void updateTaskbar(const std::string& workspace_icon);\n  bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const;\n  bool shouldSkipWindow(const WindowRepr& window_repr) const;\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/hyprland/workspaces.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <gtkmm/enums.h>\n#include <gtkmm/label.h>\n#include <json/value.h>\n#include <sigc++/connection.h>\n\n#include <cstdint>\n#include <map>\n#include <memory>\n#include <optional>\n#include <regex>\n#include <string>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/hyprland/backend.hpp\"\n#include \"modules/hyprland/windowcreationpayload.hpp\"\n#include \"modules/hyprland/workspace.hpp\"\n#include \"util/enum.hpp\"\n#include \"util/icon_loader.hpp\"\n#include \"util/regex_collection.hpp\"\n\nusing WindowAddress = std::string;\n\nnamespace waybar::modules::hyprland {\n\nclass Workspaces;\n\nclass Workspaces : public AModule, public EventHandler {\n public:\n  Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Workspaces() override;\n  void update() override;\n  void init();\n\n  auto allOutputs() const -> bool { return m_allOutputs; }\n  auto showSpecial() const -> bool { return m_showSpecial; }\n  auto activeOnly() const -> bool { return m_activeOnly; }\n  auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; }\n  auto persistentOnly() const -> bool { return m_persistentOnly; }\n  auto moveToMonitor() const -> bool { return m_moveToMonitor; }\n  auto enableTaskbar() const -> bool { return m_enableTaskbar; }\n  auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; }\n  auto barScroll() const -> bool { return m_barScroll; }\n\n  auto getBarOutput() const -> std::string { return m_bar.output->name; }\n  auto formatBefore() const -> std::string { return m_formatBefore; }\n  auto formatAfter() const -> std::string { return m_formatAfter; }\n  auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; }\n  auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; }\n  auto taskbarIconSize() const -> int { return m_taskbarIconSize; }\n  auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; }\n  auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; }\n  auto onClickWindow() const -> std::string { return m_onClickWindow; }\n  auto getIgnoredWindows() const -> std::vector<std::regex> { return m_ignoreWindows; }\n\n  enum class ActiveWindowPosition { NONE, FIRST, LAST };\n  auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; }\n\n  std::string getRewrite(const std::string& window_class, const std::string& window_title);\n  std::string& getWindowSeparator() { return m_formatWindowSeparator; }\n  bool isWorkspaceIgnored(std::string const& workspace_name);\n\n  bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }\n  const IconLoader& iconLoader() const { return m_iconLoader; }\n\n private:\n  void onEvent(const std::string& e) override;\n  void updateWindowCount();\n  void sortSpecialCentered();\n  void sortWorkspaces();\n  void createWorkspace(Json::Value const& workspace_data,\n                       Json::Value const& clients_data = Json::Value::nullRef);\n\n  static Json::Value createMonitorWorkspaceData(std::string const& name,\n                                                std::string const& monitor);\n  void removeWorkspace(std::string const& workspaceString);\n  void setUrgentWorkspace(std::string const& windowaddress);\n\n  // Config\n  void parseConfig(const Json::Value& config);\n  auto populateIconsMap(const Json::Value& formatIcons) -> void;\n  static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member)\n      -> void;\n  auto populateSortByConfig(const Json::Value& config) -> void;\n  auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void;\n  auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void;\n  auto populateWindowRewriteConfig(const Json::Value& config) -> void;\n  auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void;\n\n  void registerIpc();\n\n  // workspace events\n  void onWorkspaceActivated(std::string const& payload);\n  void onSpecialWorkspaceActivated(std::string const& payload);\n  void onWorkspaceDestroyed(std::string const& payload);\n  void onWorkspaceCreated(std::string const& payload,\n                          Json::Value const& clientsData = Json::Value::nullRef);\n  void onWorkspaceMoved(std::string const& payload);\n  void onWorkspaceRenamed(std::string const& payload);\n  static std::optional<int> parseWorkspaceId(std::string const& workspaceIdStr);\n\n  // monitor events\n  void onMonitorFocused(std::string const& payload);\n\n  // window events\n  void onWindowOpened(std::string const& payload);\n  void onWindowClosed(std::string const& addr);\n  void onWindowMoved(std::string const& payload);\n\n  void onWindowTitleEvent(std::string const& payload);\n  void onActiveWindowChanged(WindowAddress const& payload);\n\n  void onConfigReloaded();\n\n  int windowRewritePriorityFunction(std::string const& window_rule);\n\n  // event payload management\n  template <typename... Args>\n  static std::string makePayload(Args const&... args);\n  static std::pair<std::string, std::string> splitDoublePayload(std::string const& payload);\n  static std::tuple<std::string, std::string, std::string> splitTriplePayload(\n      std::string const& payload);\n  // scroll events\n  bool handleScroll(GdkEventScroll* e) override;\n\n  // Update methods\n  void doUpdate();\n  void removeWorkspacesToRemove();\n  void createWorkspacesToCreate();\n  static std::vector<int> getVisibleWorkspaces();\n  void updateWorkspaceStates();\n  bool updateWindowsToCreate();\n\n  void extendOrphans(int workspaceId, Json::Value const& clientsJson);\n  void registerOrphanWindow(WindowCreationPayload create_window_payload);\n\n  void initializeWorkspaces();\n  void setCurrentMonitorId();\n  void loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson);\n  void loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson);\n\n  bool m_allOutputs = false;\n  bool m_showSpecial = false;\n  bool m_activeOnly = false;\n  bool m_specialVisibleOnly = false;\n  bool m_persistentOnly = false;\n  bool m_moveToMonitor = false;\n  bool m_barScroll = false;\n  Json::Value m_persistentWorkspaceConfig;\n\n  // Map for windows stored in workspaces not present in the current bar.\n  // This happens when the user has multiple monitors (hence, multiple bars)\n  // and doesn't share windows across bars (a.k.a `all-outputs` = false)\n  std::map<WindowAddress, WindowRepr, std::less<>> m_orphanWindowMap;\n\n  enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT };\n  util::EnumParser<SortMethod> m_enumParser;\n  SortMethod m_sortBy = SortMethod::DEFAULT;\n  std::map<std::string, SortMethod> m_sortMap = {{\"ID\", SortMethod::ID},\n                                                 {\"NAME\", SortMethod::NAME},\n                                                 {\"NUMBER\", SortMethod::NUMBER},\n                                                 {\"SPECIAL-CENTERED\", SortMethod::SPECIAL_CENTERED},\n                                                 {\"DEFAULT\", SortMethod::DEFAULT}};\n\n  std::string m_formatBefore;\n  std::string m_formatAfter;\n\n  std::map<std::string, std::string> m_iconsMap;\n  util::RegexCollection m_windowRewriteRules;\n  bool m_anyWindowRewriteRuleUsesTitle = false;\n  std::string m_formatWindowSeparator;\n\n  bool m_withIcon;\n  uint64_t m_monitorId;\n  int m_activeWorkspaceId;\n  std::string m_activeSpecialWorkspaceName;\n  std::vector<std::unique_ptr<Workspace>> m_workspaces;\n  std::vector<std::pair<Json::Value, Json::Value>> m_workspacesToCreate;\n  std::vector<std::string> m_workspacesToRemove;\n  std::vector<WindowCreationPayload> m_windowsToCreate;\n\n  IconLoader m_iconLoader;\n  bool m_enableTaskbar = false;\n  bool m_updateActiveWindow = false;\n  bool m_taskbarWithIcon = false;\n  bool m_taskbarWithTitle = false;\n  std::string m_taskbarFormatBefore;\n  std::string m_taskbarFormatAfter;\n  int m_taskbarIconSize = 16;\n  Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL;\n  bool m_taskbarReverseDirection = false;\n  util::EnumParser<ActiveWindowPosition> m_activeWindowEnumParser;\n  ActiveWindowPosition m_activeWindowPosition = ActiveWindowPosition::NONE;\n  std::map<std::string, ActiveWindowPosition> m_activeWindowPositionMap = {\n      {\"NONE\", ActiveWindowPosition::NONE},\n      {\"FIRST\", ActiveWindowPosition::FIRST},\n      {\"LAST\", ActiveWindowPosition::LAST},\n  };\n  std::string m_onClickWindow;\n  std::string m_currentActiveWindowAddress;\n\n  std::vector<std::regex> m_ignoreWorkspaces;\n  std::vector<std::regex> m_ignoreWindows;\n\n  std::mutex m_mutex;\n  const Bar& m_bar;\n  Gtk::Box m_box;\n  sigc::connection m_scrollEventConnection_;\n  IPC& m_ipc;\n};\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "include/modules/idle_inhibitor.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n\nnamespace waybar::modules {\n\nclass IdleInhibitor : public ALabel {\n  sigc::connection timeout_;\n\n public:\n  IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~IdleInhibitor();\n  auto update() -> void override;\n  static std::list<waybar::AModule*> modules;\n  static bool status;\n\n private:\n  bool handleToggle(GdkEventButton* const& e) override;\n  void toggleStatus();\n\n  const Bar& bar_;\n  struct zwp_idle_inhibitor_v1* idle_inhibitor_;\n  int pid_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/image.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <gtkmm/image.h>\n\n#include <csignal>\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"gtkmm/box.h\"\n#include \"util/command.hpp\"\n#include \"util/json.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Image : public AModule {\n public:\n  Image(const std::string&, const Json::Value&);\n  virtual ~Image() = default;\n  auto update() -> void override;\n  void refresh(int /*signal*/) override;\n\n private:\n  void delayWorker();\n  void handleEvent();\n  void parseOutputRaw();\n\n  Gtk::Box box_;\n  Gtk::Image image_;\n  std::string path_;\n  std::string tooltip_;\n  int size_;\n  std::chrono::milliseconds interval_;\n  util::command::res output_;\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/inhibitor.hpp",
    "content": "#pragma once\n\n#include <gio/gio.h>\n\n#include <memory>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n\nnamespace waybar::modules {\n\nclass Inhibitor : public ALabel {\n public:\n  Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Inhibitor();\n  auto update() -> void override;\n  auto activated() -> bool;\n\n private:\n  auto handleToggle(::GdkEventButton* const& e) -> bool override;\n\n  const std::unique_ptr<::GDBusConnection, void (*)(::GDBusConnection*)> dbus_;\n  const std::string inhibitors_;\n  int handle_ = -1;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/jack.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <jack/jack.h>\n#include <jack/thread.h>\n\n#include <fstream>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass JACK : public ALabel {\n public:\n  JACK(const std::string&, const Json::Value&);\n  virtual ~JACK() = default;\n  auto update() -> void override;\n\n  int bufSize(jack_nframes_t size);\n  int sampleRate(jack_nframes_t rate);\n  int xrun();\n  void shutdown();\n\n private:\n  std::string JACKState();\n\n  jack_client_t* client_;\n  jack_nframes_t bufsize_;\n  jack_nframes_t samplerate_;\n  unsigned int xruns_;\n  float load_;\n  bool running_;\n  std::mutex mutex_;\n  std::string state_;\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n\nint bufSizeCallback(jack_nframes_t size, void* obj);\nint sampleRateCallback(jack_nframes_t rate, void* obj);\nint xrunCallback(void* obj);\nvoid shutdownCallback(void* obj);\n"
  },
  {
    "path": "include/modules/keyboard_state.hpp",
    "content": "#pragma once\n\n#include <fmt/chrono.h>\n#include <gtkmm/label.h>\n\n#include <mutex>\n#include <set>\n#include <unordered_map>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nextern \"C\" {\n#include <libevdev/libevdev.h>\n#include <libinput.h>\n}\n\nnamespace waybar::modules {\n\nclass KeyboardState : public AModule {\n public:\n  KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~KeyboardState();\n  auto update() -> void override;\n\n private:\n  auto tryAddDevice(const std::string&) -> void;\n\n  Gtk::Box box_;\n  Gtk::Label numlock_label_;\n  Gtk::Label capslock_label_;\n  Gtk::Label scrolllock_label_;\n\n  std::string numlock_format_;\n  std::string capslock_format_;\n  std::string scrolllock_format_;\n  const std::chrono::seconds interval_;\n  std::string icon_locked_;\n  std::string icon_unlocked_;\n  std::string devices_path_;\n\n  struct libinput* libinput_;\n  std::unordered_map<std::string, struct libinput_device*> libinput_devices_;\n  std::mutex devices_mutex_;  // protects libinput_devices_\n  std::set<int> binding_keys;\n\n  util::SleeperThread libinput_thread_, hotplug_thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/load.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <cstdint>\n#include <fstream>\n#include <numeric>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Load : public ALabel {\n public:\n  Load(const std::string&, const Json::Value&);\n  virtual ~Load() = default;\n  auto update() -> void override;\n\n  // This is a static member because it is also used by the cpu module.\n  static std::tuple<double, double, double> getLoad();\n\n private:\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/memory.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <fstream>\n#include <unordered_map>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Memory : public ALabel {\n public:\n  Memory(const std::string&, const Json::Value&);\n  virtual ~Memory() = default;\n  auto update() -> void override;\n\n private:\n  void parseMeminfo();\n\n  std::unordered_map<std::string, unsigned long> meminfo_;\n\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/mpd/mpd.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <mpd/client.h>\n#include <spdlog/spdlog.h>\n\n#include <condition_variable>\n#include <thread>\n\n#include \"ALabel.hpp\"\n#include \"modules/mpd/state.hpp\"\n\nnamespace waybar::modules {\n\nclass MPD : public ALabel {\n  friend class detail::Context;\n\n  // State machine\n  detail::Context context_{this};\n\n  const std::string module_name_;\n\n  // Not using unique_ptr since we don't manage the pointer\n  // (It's either nullptr, or from the config)\n  const char* server_;\n  const unsigned port_;\n  const std::string password_;\n\n  unsigned timeout_;\n\n  detail::unique_connection connection_;\n\n  detail::unique_status status_;\n  mpd_state state_;\n  detail::unique_song song_;\n\n public:\n  MPD(const std::string&, const Json::Value&);\n  virtual ~MPD() noexcept = default;\n  auto update() -> void override;\n\n private:\n  std::string getTag(mpd_tag_type type, unsigned idx = 0) const;\n  std::string getFilename() const;\n  void setLabel();\n  std::string getStateIcon() const;\n  std::string getOptionIcon(const std::string& optionName, bool activated) const;\n\n  // GUI-side methods\n  bool handlePlayPause(GdkEventButton* const&);\n  void emit() { dp.emit(); }\n\n  // MPD-side, Non-GUI methods.\n  void tryConnect();\n  void checkErrors(mpd_connection* conn);\n  void fetchState();\n  void queryMPD();\n\n  inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }\n  inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }\n  inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }\n};\n\n#if !defined(MPD_NOINLINE)\n#include \"modules/mpd/state.inl.hpp\"\n#endif\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/mpd/state.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <mpd/client.h>\n#include <spdlog/spdlog.h>\n\n#include <condition_variable>\n#include <thread>\n\n#include \"ALabel.hpp\"\n\nnamespace waybar::modules {\nclass MPD;\n}  // namespace waybar::modules\n\nnamespace waybar::modules::detail {\n\nusing unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;\nusing unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;\nusing unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;\n\nclass Context;\n\n/// This state machine loosely follows a non-hierarchical, statechart\n/// pattern, and includes ENTRY and EXIT actions.\n///\n/// The State class is the base class for all other states. The\n/// entry and exit methods are automatically called when entering\n/// into a new state and exiting from the current state. This\n/// includes initially entering (Disconnected class) and exiting\n/// Waybar.\n///\n/// The following nested \"top-level\" states are represented:\n/// 1. Idle - await notification of MPD activity.\n/// 2. All Non-Idle states:\n///    1. Playing - An active song is producing audio output.\n///    2. Paused - The current song is paused.\n///    3. Stopped - No song is actively playing.\n/// 3. Disconnected - periodically attempt MPD (re-)connection.\n///\n/// NOTE: Since this statechart is non-hierarchical, the above\n/// states are flattened into a set.\n\nclass State {\n public:\n  virtual ~State() noexcept = default;\n\n  virtual void entry() noexcept { spdlog::debug(\"mpd: ignore entry action\"); }\n  virtual void exit() noexcept { spdlog::debug(\"mpd: ignore exit action\"); }\n\n  virtual void play() { spdlog::debug(\"mpd: ignore play state transition\"); }\n  virtual void stop() { spdlog::debug(\"mpd: ignore stop state transition\"); }\n  virtual void pause() { spdlog::debug(\"mpd: ignore pause state transition\"); }\n\n  /// Request state update the GUI.\n  virtual void update() noexcept { spdlog::debug(\"mpd: ignoring update method request\"); }\n};\n\nclass Idle : public State {\n  Context* const ctx_;\n  sigc::connection idle_connection_;\n\n public:\n  Idle(Context* const ctx) : ctx_{ctx} {}\n  virtual ~Idle() noexcept { this->exit(); };\n\n  void entry() noexcept override;\n  void exit() noexcept override;\n\n  void play() override;\n  void stop() override;\n  void pause() override;\n  void update() noexcept override;\n\n private:\n  Idle(const Idle&) = delete;\n  Idle& operator=(const Idle&) = delete;\n\n  bool on_io(Glib::IOCondition const&);\n};\n\nclass Playing : public State {\n  Context* const ctx_;\n  sigc::connection timer_connection_;\n\n public:\n  Playing(Context* const ctx) : ctx_{ctx} {}\n  virtual ~Playing() noexcept { this->exit(); }\n\n  void entry() noexcept override;\n  void exit() noexcept override;\n\n  void pause() override;\n  void stop() override;\n  void update() noexcept override;\n\n private:\n  Playing(Playing const&) = delete;\n  Playing& operator=(Playing const&) = delete;\n\n  bool on_timer();\n};\n\nclass Paused : public State {\n  Context* const ctx_;\n  sigc::connection timer_connection_;\n\n public:\n  Paused(Context* const ctx) : ctx_{ctx} {}\n  virtual ~Paused() noexcept { this->exit(); }\n\n  void entry() noexcept override;\n  void exit() noexcept override;\n\n  void play() override;\n  void stop() override;\n  void update() noexcept override;\n\n private:\n  Paused(Paused const&) = delete;\n  Paused& operator=(Paused const&) = delete;\n\n  bool on_timer();\n};\n\nclass Stopped : public State {\n  Context* const ctx_;\n  sigc::connection timer_connection_;\n\n public:\n  Stopped(Context* const ctx) : ctx_{ctx} {}\n  virtual ~Stopped() noexcept { this->exit(); }\n\n  void entry() noexcept override;\n  void exit() noexcept override;\n\n  void play() override;\n  void pause() override;\n  void update() noexcept override;\n\n private:\n  Stopped(Stopped const&) = delete;\n  Stopped& operator=(Stopped const&) = delete;\n\n  bool on_timer();\n};\n\nclass Disconnected : public State {\n  Context* const ctx_;\n  sigc::connection timer_connection_;\n  int last_interval_;\n\n public:\n  Disconnected(Context* const ctx) : ctx_{ctx} {}\n  virtual ~Disconnected() noexcept { this->exit(); }\n\n  void entry() noexcept override;\n  void exit() noexcept override;\n\n  void update() noexcept override;\n\n private:\n  Disconnected(Disconnected const&) = delete;\n  Disconnected& operator=(Disconnected const&) = delete;\n\n  bool arm_timer(int interval) noexcept;\n  void disarm_timer() noexcept;\n\n  bool on_timer();\n};\n\nclass Context {\n  std::unique_ptr<State> state_;\n  waybar::modules::MPD* mpd_module_;\n\n  friend class State;\n  friend class Playing;\n  friend class Paused;\n  friend class Stopped;\n  friend class Disconnected;\n  friend class Idle;\n\n protected:\n  void setState(std::unique_ptr<State>&& new_state) noexcept {\n    if (state_.get() != nullptr) {\n      state_->exit();\n    }\n    state_ = std::move(new_state);\n    state_->entry();\n  }\n\n  bool is_connected() const;\n  bool is_playing() const;\n  bool is_paused() const;\n  bool is_stopped() const;\n  constexpr std::size_t interval() const;\n  void tryConnect() const;\n  void checkErrors(mpd_connection*) const;\n  void do_update();\n  void queryMPD() const;\n  void fetchState() const;\n  constexpr mpd_state state() const;\n  void emit() const;\n  [[nodiscard]] unique_connection& connection();\n\n public:\n  explicit Context(waybar::modules::MPD* const mpd_module)\n      : state_{std::make_unique<Disconnected>(this)}, mpd_module_{mpd_module} {\n    state_->entry();\n  }\n\n  void play() { state_->play(); }\n  void stop() { state_->stop(); }\n  void pause() { state_->pause(); }\n  void update() noexcept { state_->update(); }\n};\n\n}  // namespace waybar::modules::detail\n"
  },
  {
    "path": "include/modules/mpd/state.inl.hpp",
    "content": "#pragma once\n\nnamespace detail {\n\ninline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }\ninline bool Context::is_playing() const { return mpd_module_->playing(); }\ninline bool Context::is_paused() const { return mpd_module_->paused(); }\ninline bool Context::is_stopped() const { return mpd_module_->stopped(); }\n\nconstexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); }\ninline void Context::tryConnect() const { mpd_module_->tryConnect(); }\ninline unique_connection& Context::connection() { return mpd_module_->connection_; }\nconstexpr inline mpd_state Context::state() const { return mpd_module_->state_; }\n\ninline void Context::do_update() { mpd_module_->setLabel(); }\n\ninline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }\ninline void Context::queryMPD() const { mpd_module_->queryMPD(); }\ninline void Context::fetchState() const { mpd_module_->fetchState(); }\ninline void Context::emit() const { mpd_module_->emit(); }\n\n}  // namespace detail\n"
  },
  {
    "path": "include/modules/mpris/mpris.hpp",
    "content": "#pragma once\n\n#include <iostream>\n#include <optional>\n#include <string>\n\n#include \"gtkmm/box.h\"\n#include \"gtkmm/label.h\"\n\nextern \"C\" {\n#include <playerctl/playerctl.h>\n}\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules::mpris {\n\nclass Mpris : public ALabel {\n public:\n  Mpris(const std::string&, const Json::Value&);\n  virtual ~Mpris();\n  auto update() -> void override;\n  bool handleToggle(GdkEventButton* const&) override;\n\n private:\n  static auto onPlayerNameAppeared(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;\n  static auto onPlayerNameVanished(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;\n  static auto onPlayerPlay(PlayerctlPlayer*, gpointer) -> void;\n  static auto onPlayerPause(PlayerctlPlayer*, gpointer) -> void;\n  static auto onPlayerStop(PlayerctlPlayer*, gpointer) -> void;\n  static auto onPlayerMetadata(PlayerctlPlayer*, GVariant*, gpointer) -> void;\n\n  struct PlayerInfo {\n    std::string name;\n    PlayerctlPlaybackStatus status;\n    std::string status_string;\n\n    std::optional<std::string> artist;\n    std::optional<std::string> album;\n    std::optional<std::string> title;\n    std::optional<std::string> length;    // as HH:MM:SS\n    std::optional<std::string> position;  // same format\n  };\n\n  auto getPlayerInfo() -> std::optional<PlayerInfo>;\n  auto getIconFromJson(const Json::Value&, const std::string&) -> std::string;\n  auto getArtistStr(const PlayerInfo&, bool) -> std::string;\n  auto getAlbumStr(const PlayerInfo&, bool) -> std::string;\n  auto getTitleStr(const PlayerInfo&, bool) -> std::string;\n  auto getLengthStr(const PlayerInfo&, bool) -> std::string;\n  auto getPositionStr(const PlayerInfo&, bool) -> std::string;\n  auto getDynamicStr(const PlayerInfo&, bool, bool) -> std::string;\n\n  // config\n  std::string format_playing_;\n  std::string format_paused_;\n  std::string format_stopped_;\n\n  std::string tooltip_;\n  std::string tooltip_playing_;\n  std::string tooltip_paused_;\n  std::string tooltip_stopped_;\n\n  int artist_len_;\n  int album_len_;\n  int title_len_;\n  int dynamic_len_;\n  std::vector<std::string> dynamic_prio_;\n  std::vector<std::string> dynamic_order_;\n  std::string dynamic_separator_;\n  bool truncate_hours_;\n  bool tooltip_len_limits_;\n  std::string ellipsis_;\n\n  std::string player_;\n  std::vector<std::string> ignored_players_;\n\n  PlayerctlPlayerManager* manager;\n  PlayerctlPlayer* player;\n  PlayerctlPlayer* last_active_player_ = nullptr;\n  std::string lastStatus;\n  std::string lastPlayer;\n\n  util::SleeperThread thread_;\n  std::chrono::time_point<std::chrono::system_clock> last_update_;\n};\n\n}  // namespace waybar::modules::mpris\n"
  },
  {
    "path": "include/modules/network.hpp",
    "content": "#pragma once\n\n#include <arpa/inet.h>\n#include <fmt/format.h>\n#include <linux/nl80211.h>\n#include <netlink/genl/ctrl.h>\n#include <netlink/genl/genl.h>\n#include <netlink/netlink.h>\n#include <sys/epoll.h>\n\n#include <optional>\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n#ifdef WANT_RFKILL\n#include \"util/rfkill.hpp\"\n#endif\n\nenum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 };\n\nnamespace waybar::modules {\n\nclass Network : public ALabel {\n public:\n  Network(const std::string&, const Json::Value&);\n  virtual ~Network();\n  auto update() -> void override;\n\n private:\n  static const uint8_t MAX_RETRY{5};\n  static const uint8_t EPOLL_MAX{200};\n\n  static int handleEvents(struct nl_msg*, void*);\n  static int handleEventsDone(struct nl_msg*, void*);\n  static int handleScan(struct nl_msg*, void*);\n\n  void askForStateDump(void);\n\n  void worker();\n  void createInfoSocket();\n  void createEventSocket();\n  void parseEssid(struct nlattr**);\n  void parseSignal(struct nlattr**);\n  void parseFreq(struct nlattr**);\n  void parseBssid(struct nlattr**);\n  bool associatedOrJoined(struct nlattr**);\n  bool matchInterface(const std::string& ifname, const std::vector<std::string>& altnames,\n                      std::string& matched) const;\n  auto getInfo() -> void;\n  const std::string getNetworkState() const;\n  void clearIface();\n  std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();\n\n  int ifid_{-1};\n  ip_addr_pref addr_pref_{ip_addr_pref::IPV4};\n  struct sockaddr_nl nladdr_{0};\n  struct nl_sock* sock_{nullptr};\n  struct nl_sock* ev_sock_{nullptr};\n  int efd_{-1};\n  int ev_fd_{-1};\n  int nl80211_id_{-1};\n  std::mutex mutex_;\n\n  bool want_route_dump_{false};\n  bool want_link_dump_{false};\n  bool want_addr_dump_{false};\n  bool dump_in_progress_{false};\n  bool is_p2p_{false};\n\n  unsigned long long bandwidth_down_total_{0};\n  unsigned long long bandwidth_up_total_{0};\n\n  std::string state_;\n  std::string essid_;\n  std::string bssid_;\n  bool carrier_{false};\n  std::string ifname_;\n  std::string ipaddr_;\n  std::string ipaddr6_;\n  std::string gwaddr_;\n  std::string netmask_;\n  std::string netmask6_;\n  int cidr_{0};\n  int cidr6_{0};\n  int32_t signal_strength_dbm_;\n  uint8_t signal_strength_;\n  std::string signal_strength_app_;\n  uint32_t route_priority;\n\n  util::SleeperThread thread_;\n  util::SleeperThread thread_timer_;\n#ifdef WANT_RFKILL\n  util::Rfkill rfkill_{RFKILL_TYPE_WLAN};\n#endif\n  float frequency_{0};\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/niri/backend.hpp",
    "content": "#pragma once\n\n#include <list>\n#include <mutex>\n#include <string>\n#include <utility>\n\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::niri {\n\nclass EventHandler {\n public:\n  virtual void onEvent(const Json::Value& ev) = 0;\n  virtual ~EventHandler() = default;\n};\n\nclass IPC {\n public:\n  IPC();\n\n  void registerForIPC(const std::string& ev, EventHandler* ev_handler);\n  void unregisterForIPC(EventHandler* handler);\n\n  static Json::Value send(const Json::Value& request);\n\n  // The data members are only safe to access while dataMutex_ is locked.\n  std::lock_guard<std::mutex> lockData() { return std::lock_guard(dataMutex_); }\n  const std::vector<Json::Value>& workspaces() const { return workspaces_; }\n  const std::vector<Json::Value>& windows() const { return windows_; }\n  const std::vector<std::string>& keyboardLayoutNames() const { return keyboardLayoutNames_; }\n  unsigned keyboardLayoutCurrent() const { return keyboardLayoutCurrent_; }\n\n private:\n  void startIPC();\n  static int connectToSocket();\n  void parseIPC(const std::string&);\n\n  std::mutex dataMutex_;\n  std::vector<Json::Value> workspaces_;\n  std::vector<Json::Value> windows_;\n  std::vector<std::string> keyboardLayoutNames_;\n  unsigned keyboardLayoutCurrent_;\n\n  util::JsonParser parser_;\n  std::mutex callbackMutex_;\n  std::list<std::pair<std::string, EventHandler*>> callbacks_;\n};\n\ninline std::unique_ptr<IPC> gIPC;\n\n};  // namespace waybar::modules::niri\n"
  },
  {
    "path": "include/modules/niri/language.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/niri/backend.hpp\"\n\nnamespace waybar::modules::niri {\n\nclass Language : public ALabel, public EventHandler {\n public:\n  Language(const std::string&, const Bar&, const Json::Value&);\n  ~Language() override;\n  void update() override;\n\n private:\n  void updateFromIPC();\n  void onEvent(const Json::Value& ev) override;\n  void doUpdate();\n\n  struct Layout {\n    std::string full_name;\n    std::string short_name;\n    std::string variant;\n    std::string short_description;\n  };\n\n  static Layout getLayout(const std::string& fullName);\n\n  std::mutex mutex_;\n  const Bar& bar_;\n\n  std::vector<Layout> layouts_;\n  unsigned current_idx_;\n  std::string last_short_name_;\n};\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "include/modules/niri/window.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <json/value.h>\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/niri/backend.hpp\"\n\nnamespace waybar::modules::niri {\n\nclass Window : public AAppIconLabel, public EventHandler {\n public:\n  Window(const std::string&, const Bar&, const Json::Value&);\n  ~Window() override;\n  void update() override;\n\n private:\n  void onEvent(const Json::Value& ev) override;\n  void doUpdate();\n  void setClass(const std::string& className, bool enable);\n\n  const Bar& bar_;\n\n  std::string oldAppId_;\n};\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "include/modules/niri/workspaces.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <json/value.h>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/niri/backend.hpp\"\n\nnamespace waybar::modules::niri {\n\nclass Workspaces : public AModule, public EventHandler {\n public:\n  Workspaces(const std::string&, const Bar&, const Json::Value&);\n  ~Workspaces() override;\n  void update() override;\n\n private:\n  void onEvent(const Json::Value& ev) override;\n  void doUpdate();\n  Gtk::Button& addButton(const Json::Value& ws);\n  std::string getIcon(const std::string& value, const Json::Value& ws);\n\n  const Bar& bar_;\n  Gtk::Box box_;\n  // Map from niri workspace id to button.\n  std::unordered_map<uint64_t, Gtk::Button> buttons_;\n};\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "include/modules/power_profiles_daemon.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include \"ALabel.hpp\"\n#include \"giomm/dbusproxy.h\"\n\nnamespace waybar::modules {\n\nstruct Profile {\n  std::string name;\n  std::string driver;\n\n  Profile(std::string n, std::string d) : name(std::move(n)), driver(std::move(d)) {}\n};\n\nclass PowerProfilesDaemon : public ALabel {\n public:\n  PowerProfilesDaemon(const std::string&, const Json::Value&);\n  auto update() -> void override;\n  void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties&,\n                        const std::vector<Glib::ustring>&);\n  void busConnectedCb(Glib::RefPtr<Gio::AsyncResult>& r);\n  void getAllPropsCb(Glib::RefPtr<Gio::AsyncResult>& r);\n  void setPropCb(Glib::RefPtr<Gio::AsyncResult>& r);\n  void populateInitState();\n  bool handleToggle(GdkEventButton* const& e) override;\n\n private:\n  // True if we're connected to the dbug interface. False if we're\n  // not.\n  bool connected_;\n  // Look for a profile name in the list of available profiles and\n  // switch activeProfile_ to it.\n  void switchToProfile(std::string const&);\n  // Used to toggle/display the profiles\n  std::vector<Profile> availableProfiles_;\n  // Points to the active profile in the profiles list\n  std::vector<Profile>::iterator activeProfile_;\n  // Current CSS class applied to the label\n  std::string currentStyle_;\n  // Format string\n  std::string tooltipFormat_;\n  // DBus Proxy used to track the current active profile\n  Glib::RefPtr<Gio::DBus::Proxy> powerProfilesProxy_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/privacy/privacy.hpp",
    "content": "#pragma once\n\n#include <string>\n\n#include \"gtkmm/box.h\"\n#include \"modules/privacy/privacy_item.hpp\"\n#include \"util/pipewire/pipewire_backend.hpp\"\n#include \"util/pipewire/privacy_node_info.hpp\"\n\nusing waybar::util::PipewireBackend::PrivacyNodeInfo;\n\nnamespace waybar::modules::privacy {\n\nclass Privacy : public AModule {\n public:\n  Privacy(const std::string&, const Json::Value&, Gtk::Orientation, const std::string& pos);\n  auto update() -> void override;\n\n  void onPrivacyNodesChanged();\n\n private:\n  std::list<PrivacyNodeInfo*> nodes_screenshare;  // Screen is being shared\n  std::list<PrivacyNodeInfo*> nodes_audio_in;     // Application is using the microphone\n  std::list<PrivacyNodeInfo*> nodes_audio_out;    // Application is outputting audio\n\n  std::mutex mutex_;\n  sigc::connection visibility_conn;\n\n  // Config\n  Gtk::Box box_;\n  uint iconSpacing = 4;\n  uint iconSize = 20;\n  uint transition_duration = 250;\n  std::set<std::pair<PrivacyNodeType, std::string>> ignore;\n  bool ignore_monitor = true;\n\n  std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr;\n};\n\n}  // namespace waybar::modules::privacy\n"
  },
  {
    "path": "include/modules/privacy/privacy_item.hpp",
    "content": "#pragma once\n\n#include <json/value.h>\n\n#include <string>\n\n#include \"gtkmm/box.h\"\n#include \"gtkmm/image.h\"\n#include \"gtkmm/revealer.h\"\n#include \"util/pipewire/privacy_node_info.hpp\"\n\nusing waybar::util::PipewireBackend::PrivacyNodeInfo;\nusing waybar::util::PipewireBackend::PrivacyNodeType;\n\nnamespace waybar::modules::privacy {\n\nclass PrivacyItem : public Gtk::Revealer {\n public:\n  PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_,\n              std::list<PrivacyNodeInfo*>* nodes, Gtk::Orientation orientation,\n              const std::string& pos, const uint icon_size, const uint transition_duration);\n\n  enum PrivacyNodeType privacy_type;\n\n  void set_in_use(bool in_use);\n\n private:\n  std::list<PrivacyNodeInfo*>* nodes;\n\n  sigc::connection signal_conn;\n\n  Gtk::Box tooltip_window;\n\n  bool init = false;\n  bool in_use = false;\n\n  // Config\n  std::string iconName = \"image-missing-symbolic\";\n  bool tooltip = true;\n  uint tooltipIconSize = 24;\n\n  Gtk::Box box_;\n  Gtk::Image icon_;\n\n  void update_tooltip();\n};\n\n}  // namespace waybar::modules::privacy\n"
  },
  {
    "path": "include/modules/pulseaudio.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <algorithm>\n#include <array>\n#include <memory>\n\n#include \"ALabel.hpp\"\n#include \"util/audio_backend.hpp\"\n\nnamespace waybar::modules {\n\nclass Pulseaudio : public ALabel {\n public:\n  Pulseaudio(const std::string&, const Json::Value&);\n  virtual ~Pulseaudio() = default;\n  auto update() -> void override;\n\n private:\n  bool handleScroll(GdkEventScroll* e) override;\n  const std::vector<std::string> getPulseIcon() const;\n\n  std::shared_ptr<util::AudioBackend> backend = nullptr;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/pulseaudio_slider.hpp",
    "content": "#pragma once\n\n#include <memory>\n\n#include \"ASlider.hpp\"\n#include \"util/audio_backend.hpp\"\nnamespace waybar::modules {\n\nenum class PulseaudioSliderTarget {\n  Sink,\n  Source,\n};\n\nclass PulseaudioSlider : public ASlider {\n public:\n  PulseaudioSlider(const std::string&, const Json::Value&);\n  virtual ~PulseaudioSlider() = default;\n\n  void update() override;\n  void onValueChanged() override;\n\n private:\n  std::shared_ptr<util::AudioBackend> backend = nullptr;\n  PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink;\n};\n\n}  // namespace waybar::modules"
  },
  {
    "path": "include/modules/river/layout.hpp",
    "content": "#pragma once\n\n#include <wayland-client.h>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"river-status-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::river {\n\nclass Layout : public waybar::ALabel {\n public:\n  Layout(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Layout();\n\n  // Handlers for wayland events\n  void handle_name(const char* name);\n  void handle_clear();\n  void handle_focused_output(struct wl_output* output);\n  void handle_unfocused_output(struct wl_output* output);\n\n  struct zriver_status_manager_v1* status_manager_;\n  struct wl_seat* seat_;\n\n private:\n  const waybar::Bar& bar_;\n  std::string name_;\n  struct wl_output* output_;          // stores the output this module belongs to\n  struct wl_output* focused_output_;  // stores the currently focused output\n  struct zriver_output_status_v1* output_status_;\n  struct zriver_seat_status_v1* seat_status_;\n};\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "include/modules/river/mode.hpp",
    "content": "#pragma once\n\n#include <wayland-client.h>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"river-status-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::river {\n\nclass Mode : public waybar::ALabel {\n public:\n  Mode(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Mode();\n\n  // Handlers for wayland events\n  void handle_mode(const char* mode);\n\n  struct zriver_status_manager_v1* status_manager_;\n  struct wl_seat* seat_;\n\n private:\n  const waybar::Bar& bar_;\n  std::string mode_;\n  struct zriver_seat_status_v1* seat_status_;\n};\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "include/modules/river/tags.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <wayland-client.h>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"river-control-unstable-v1-client-protocol.h\"\n#include \"river-status-unstable-v1-client-protocol.h\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::river {\n\nclass Tags : public waybar::AModule {\n public:\n  Tags(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Tags();\n\n  // Handlers for wayland events\n  void handle_focused_tags(uint32_t tags);\n  void handle_view_tags(struct wl_array* tags);\n  void handle_urgent_tags(uint32_t tags);\n\n  void handle_show();\n  void handle_primary_clicked(uint32_t tag);\n  bool handle_button_press(GdkEventButton* event_button, uint32_t tag);\n\n  struct zriver_status_manager_v1* status_manager_;\n  struct zriver_control_v1* control_;\n  struct wl_seat* seat_;\n\n private:\n  const waybar::Bar& bar_;\n  Gtk::Box box_;\n  std::vector<Gtk::Button> buttons_;\n  struct zriver_output_status_v1* output_status_;\n};\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "include/modules/river/window.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <wayland-client.h>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"river-status-unstable-v1-client-protocol.h\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::river {\n\nclass Window : public waybar::ALabel {\n public:\n  Window(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Window();\n\n  // Handlers for wayland events\n  void handle_focused_view(const char* title);\n  void handle_focused_output(struct wl_output* output);\n  void handle_unfocused_output(struct wl_output* output);\n\n  struct zriver_status_manager_v1* status_manager_;\n  struct wl_seat* seat_;\n\n private:\n  const waybar::Bar& bar_;\n  struct wl_output* output_;          // stores the output this module belongs to\n  struct wl_output* focused_output_;  // stores the currently focused output\n  struct zriver_seat_status_v1* seat_status_;\n};\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "include/modules/simpleclock.hpp",
    "content": "#pragma once\n\n#include <fmt/chrono.h>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Clock : public ALabel {\n public:\n  Clock(const std::string&, const Json::Value&);\n  virtual ~Clock() = default;\n  auto update() -> void override;\n\n private:\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/sndio.hpp",
    "content": "#pragma once\n\n#include <sndio.h>\n\n#include <vector>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Sndio : public ALabel {\n public:\n  Sndio(const std::string&, const Json::Value&);\n  virtual ~Sndio();\n  auto update() -> void override;\n  auto set_desc(struct sioctl_desc*, unsigned int) -> void;\n  auto put_val(unsigned int, unsigned int) -> void;\n  bool handleScroll(GdkEventScroll*) override;\n  bool handleToggle(GdkEventButton* const&) override;\n\n private:\n  auto connect_to_sndio() -> void;\n  util::SleeperThread thread_;\n  struct sioctl_hdl* hdl_;\n  std::vector<struct pollfd> pfds_;\n  unsigned int addr_;\n  unsigned int volume_, old_volume_, maxval_;\n  bool muted_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/sni/host.hpp",
    "content": "#pragma once\n\n#include <dbus-status-notifier-watcher.h>\n#include <giomm.h>\n#include <glibmm/refptr.h>\n#include <json/json.h>\n\n#include <tuple>\n\n#include \"bar.hpp\"\n#include \"modules/sni/item.hpp\"\n\nnamespace waybar::modules::SNI {\n\nclass Host {\n public:\n  Host(const std::size_t id, const Json::Value&, const Bar&,\n       const std::function<void(std::unique_ptr<Item>&)>&,\n       const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);\n  ~Host();\n\n private:\n  void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);\n  void nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring,\n                    const Glib::ustring&);\n  void nameVanished(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);\n  static void proxyReady(GObject*, GAsyncResult*, gpointer);\n  static void registerHost(GObject*, GAsyncResult*, gpointer);\n  static void itemRegistered(SnWatcher*, const gchar*, gpointer);\n  static void itemUnregistered(SnWatcher*, const gchar*, gpointer);\n  void itemReady(Item&);\n  void itemInvalidated(Item&);\n  void removeItem(std::vector<std::unique_ptr<Item>>::iterator);\n  void clearItems();\n\n  std::tuple<std::string, std::string> getBusNameAndObjectPath(const std::string);\n  void addRegisteredItem(const std::string& service);\n\n  std::vector<std::unique_ptr<Item>> items_;\n  const std::string bus_name_;\n  const std::string object_path_;\n  std::size_t bus_name_id_;\n  std::size_t watcher_id_;\n  GCancellable* cancellable_ = nullptr;\n  SnWatcher* watcher_ = nullptr;\n  const Json::Value& config_;\n  const Bar& bar_;\n  const std::function<void(std::unique_ptr<Item>&)> on_add_;\n  const std::function<void(std::unique_ptr<Item>&)> on_remove_;\n  const std::function<void()> on_update_;\n};\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "include/modules/sni/icon_manager.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n#include <spdlog/spdlog.h>\n\n#include <string>\n#include <unordered_map>\n\nclass IconManager {\n public:\n  static IconManager& instance() {\n    static IconManager instance;\n    return instance;\n  }\n\n  void setIconsConfig(const Json::Value& icons_config) {\n    if (icons_config.isObject()) {\n      for (const auto& key : icons_config.getMemberNames()) {\n        std::string app_name = key;\n        const Json::Value& icon_value = icons_config[key];\n\n        if (icon_value.isString()) {\n          std::string icon_path = icon_value.asString();\n          icons_map_[app_name] = icon_path;\n        }\n      }\n    } else {\n      spdlog::warn(\"Invalid icon config format.\");\n    }\n  }\n\n  std::string getIconForApp(const std::string& app_name) const {\n    auto it = icons_map_.find(app_name);\n    if (it != icons_map_.end()) {\n      return it->second;\n    }\n    return \"\";\n  }\n\n private:\n  IconManager() = default;\n  std::unordered_map<std::string, std::string> icons_map_;\n};\n"
  },
  {
    "path": "include/modules/sni/item.hpp",
    "content": "#pragma once\n\n#include <dbus-status-notifier-item.h>\n#include <giomm/dbusproxy.h>\n#include <glibmm/refptr.h>\n#include <gtkmm/eventbox.h>\n#include <gtkmm/icontheme.h>\n#include <gtkmm/image.h>\n#include <gtkmm/menu.h>\n#include <json/json.h>\n#include <libdbusmenu-gtk/dbusmenu-gtk.h>\n#include <sigc++/trackable.h>\n\n#include <functional>\n#include <set>\n#include <string_view>\n\n#include \"bar.hpp\"\n\nnamespace waybar::modules::SNI {\n\nstruct ToolTip {\n  Glib::ustring icon_name;\n  Glib::ustring text;\n};\n\nclass Item : public sigc::trackable {\n public:\n  Item(const std::string&, const std::string&, const Json::Value&, const Bar&,\n       const std::function<void(Item&)>&, const std::function<void(Item&)>&,\n       const std::function<void()>&);\n  ~Item();\n\n  bool isReady() const;\n\n  std::string bus_name;\n  std::string object_path;\n\n  int icon_size;\n  int effective_icon_size;\n  Gtk::Image image;\n  Gtk::EventBox event_box;\n  std::string category;\n  std::string id;\n\n  std::string title;\n  std::string icon_name;\n  Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;\n  Glib::RefPtr<Gtk::IconTheme> icon_theme;\n  std::string overlay_icon_name;\n  Glib::RefPtr<Gdk::Pixbuf> overlay_icon_pixmap;\n  std::string attention_icon_name;\n  Glib::RefPtr<Gdk::Pixbuf> attention_icon_pixmap;\n  std::string attention_movie_name;\n  std::string icon_theme_path;\n  std::string menu;\n  ToolTip tooltip;\n  DbusmenuGtkMenu* dbus_menu = nullptr;\n  Gtk::Menu* gtk_menu = nullptr;\n  /**\n   * ItemIsMenu flag means that the item only supports the context menu.\n   * Default value is true because libappindicator supports neither ItemIsMenu nor Activate method\n   * while compliant SNI implementation would always reset the flag to desired value.\n   */\n  bool item_is_menu = true;\n\n private:\n  void onConfigure(GdkEventConfigure* ev);\n  void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);\n  void setProperty(const Glib::ustring& name, Glib::VariantBase& value);\n  void setStatus(const Glib::ustring& value);\n  void setReady();\n  void invalidate();\n  void setCustomIcon(const std::string& id);\n  void getUpdatedProperties();\n  void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);\n  void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                const Glib::VariantContainerBase& arguments);\n\n  void updateImage();\n  static Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);\n  Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();\n  Glib::RefPtr<Gdk::Pixbuf> getAttentionIconPixbuf();\n  Glib::RefPtr<Gdk::Pixbuf> getOverlayIconPixbuf();\n  Glib::RefPtr<Gdk::Pixbuf> loadIconFromNameOrFile(const std::string& name, bool log_failure);\n  static Glib::RefPtr<Gdk::Pixbuf> overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>&,\n                                                  const Glib::RefPtr<Gdk::Pixbuf>&);\n  Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);\n  double getScaledIconSize();\n  static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);\n  void makeMenu();\n  bool handleClick(GdkEventButton* const& /*ev*/);\n  bool handleScroll(GdkEventScroll* const&);\n  bool handleMouseEnter(GdkEventCrossing* const&);\n  bool handleMouseLeave(GdkEventCrossing* const&);\n\n  // smooth scrolling threshold\n  gdouble scroll_threshold_ = 0;\n  gdouble distance_scrolled_x_ = 0;\n  gdouble distance_scrolled_y_ = 0;\n  // visibility of items with Status == Passive\n  bool show_passive_ = false;\n  bool ready_ = false;\n  Glib::ustring status_ = \"active\";\n\n  const Bar& bar_;\n  const std::function<void(Item&)> on_ready_;\n  const std::function<void(Item&)> on_invalidate_;\n  const std::function<void()> on_updated_;\n\n  Glib::RefPtr<Gio::DBus::Proxy> proxy_;\n  Glib::RefPtr<Gio::Cancellable> cancellable_;\n  std::set<std::string_view> update_pending_;\n};\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "include/modules/sni/tray.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/sni/host.hpp\"\n#include \"modules/sni/watcher.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::SNI {\n\nclass Tray : public AModule {\n public:\n  Tray(const std::string&, const Bar&, const Json::Value&);\n  virtual ~Tray() = default;\n  auto update() -> void override;\n\n private:\n  void onAdd(std::unique_ptr<Item>& item);\n  void onRemove(std::unique_ptr<Item>& item);\n  void queueUpdate();\n\n  static inline std::size_t nb_hosts_ = 0;\n  bool show_passive_ = false;\n  Gtk::Box box_;\n  SNI::Watcher::singleton watcher_;\n  SNI::Host host_;\n};\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "include/modules/sni/watcher.hpp",
    "content": "#pragma once\n\n#include <dbus-status-notifier-watcher.h>\n#include <giomm.h>\n#include <glibmm/refptr.h>\n\nnamespace waybar::modules::SNI {\n\nclass Watcher {\n private:\n  Watcher();\n\n public:\n  ~Watcher();\n\n  using singleton = std::shared_ptr<Watcher>;\n  static singleton getInstance() {\n    static std::weak_ptr<Watcher> weak;\n\n    std::shared_ptr<Watcher> strong = weak.lock();\n    if (!strong) {\n      strong = std::shared_ptr<Watcher>(new Watcher());\n      weak = strong;\n    }\n    return strong;\n  }\n\n private:\n  typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType;\n\n  typedef struct {\n    GfWatchType type;\n    Watcher* watcher;\n    gchar* service;\n    gchar* bus_name;\n    gchar* object_path;\n    guint watch_id;\n  } GfWatch;\n\n  void busAcquired(const Glib::RefPtr<Gio::DBus::Connection>&, Glib::ustring);\n  static gboolean handleRegisterHost(Watcher*, GDBusMethodInvocation*, const gchar*);\n  static gboolean handleRegisterItem(Watcher*, GDBusMethodInvocation*, const gchar*);\n  static GfWatch* gfWatchFind(GSList* list, const gchar* bus_name, const gchar* object_path);\n  static GfWatch* gfWatchNew(GfWatchType, const gchar*, const gchar*, const gchar*, Watcher*);\n  static void nameVanished(GDBusConnection* connection, const char* name, gpointer data);\n  static void gfWatchFree(gpointer data);\n\n  void updateRegisteredItems(SnWatcher* obj);\n\n  uint32_t bus_name_id_;\n  GSList* hosts_ = nullptr;\n  GSList* items_ = nullptr;\n  SnWatcher* watcher_ = nullptr;\n};\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "include/modules/sway/bar.hpp",
    "content": "#pragma once\n#include <atomic>\n#include <string>\n\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/SafeSignal.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar {\n\nclass Bar;\n\nnamespace modules::sway {\n\n/*\n * Supported subset of i3/sway IPC barconfig object\n */\nstruct swaybar_config {\n  std::string id;\n  std::string mode;\n  std::string hidden_state;\n};\n\n/**\n * swaybar IPC client\n */\nclass BarIpcClient {\n public:\n  BarIpcClient(waybar::Bar& bar);\n\n private:\n  void onInitialConfig(const struct Ipc::ipc_response& res);\n  void onIpcEvent(const struct Ipc::ipc_response&);\n  void onCmd(const struct Ipc::ipc_response&);\n  void onConfigUpdate(const swaybar_config& config);\n  void onVisibilityUpdate(bool visible_by_modifier);\n  void onModeUpdate(bool visible_by_modifier);\n  void onUrgencyUpdate(bool visible_by_urgency);\n  void update();\n  bool isModuleEnabled(const std::string& name);\n\n  Bar& bar_;\n  util::JsonParser parser_;\n  Ipc ipc_;\n\n  swaybar_config bar_config_;\n  std::string modifier_reset_;\n  bool visible_by_mode_ = false;\n  bool visible_by_modifier_ = false;\n  bool visible_by_urgency_ = false;\n  std::atomic<bool> modifier_no_action_ = false;\n\n  SafeSignal<bool> signal_mode_;\n  SafeSignal<bool> signal_visible_;\n  SafeSignal<bool> signal_urgency_;\n  SafeSignal<swaybar_config> signal_config_;\n};\n\n}  // namespace modules::sway\n}  // namespace waybar\n"
  },
  {
    "path": "include/modules/sway/ipc/client.hpp",
    "content": "#pragma once\n\n#include <sigc++/sigc++.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <unistd.h>\n\n#include <cstring>\n#include <memory>\n#include <mutex>\n#include <stdexcept>\n#include <string>\n\n#include \"ipc.hpp\"\n#include \"util/SafeSignal.hpp\"\n#include \"util/scoped_fd.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules::sway {\n\nclass Ipc {\n public:\n  Ipc();\n  ~Ipc();\n\n  struct ipc_response {\n    uint32_t size;\n    uint32_t type;\n    std::string payload;\n  };\n\n  ::waybar::SafeSignal<const struct ipc_response&> signal_event;\n  ::waybar::SafeSignal<const struct ipc_response&> signal_cmd;\n\n  void sendCmd(uint32_t type, const std::string& payload = \"\");\n  void subscribe(const std::string& payload);\n  void handleEvent();\n  void setWorker(std::function<void()>&& func);\n\n protected:\n  static inline const std::string ipc_magic_ = \"i3-ipc\";\n  static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8;\n\n  const std::string getSocketPath() const;\n  int open(const std::string&) const;\n  struct ipc_response send(int fd, uint32_t type, const std::string& payload = \"\");\n  struct ipc_response recv(int fd);\n\n  util::ScopedFd fd_;\n  util::ScopedFd fd_event_;\n  std::mutex mutex_;\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/sway/ipc/ipc.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n\n#define event_mask(ev) (1u << (ev & 0x7F))\n\nenum ipc_command_type : uint32_t {\n  // i3 command types - see i3's I3_REPLY_TYPE constants\n  IPC_COMMAND = 0,\n  IPC_GET_WORKSPACES = 1,\n  IPC_SUBSCRIBE = 2,\n  IPC_GET_OUTPUTS = 3,\n  IPC_GET_TREE = 4,\n  IPC_GET_MARKS = 5,\n  IPC_GET_BAR_CONFIG = 6,\n  IPC_GET_VERSION = 7,\n  IPC_GET_BINDING_MODES = 8,\n  IPC_GET_CONFIG = 9,\n  IPC_SEND_TICK = 10,\n\n  // sway-specific command types\n  IPC_GET_INPUTS = 100,\n  IPC_GET_SEATS = 101,\n\n  // Events sent from sway to clients. Events have the highest bits set.\n  IPC_EVENT_WORKSPACE = ((1U << 31) | 0),\n  IPC_EVENT_OUTPUT = ((1U << 31) | 1),\n  IPC_EVENT_MODE = ((1U << 31) | 2),\n  IPC_EVENT_WINDOW = ((1U << 31) | 3),\n  IPC_EVENT_BARCONFIG_UPDATE = ((1U << 31) | 4),\n  IPC_EVENT_BINDING = ((1U << 31) | 5),\n  IPC_EVENT_SHUTDOWN = ((1U << 31) | 6),\n  IPC_EVENT_TICK = ((1U << 31) | 7),\n\n  // sway-specific event types\n  IPC_EVENT_BAR_STATE_UPDATE = ((1U << 31) | 20),\n  IPC_EVENT_INPUT = ((1U << 31) | 21),\n};\n"
  },
  {
    "path": "include/modules/sway/language.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <xkbcommon/xkbregistry.h>\n\n#include <map>\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::sway {\n\nclass Language : public ALabel, public sigc::trackable {\n public:\n  Language(const std::string& id, const Json::Value& config);\n  virtual ~Language() = default;\n  auto update() -> void override;\n\n private:\n  enum class DisplayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };\n\n  struct Layout {\n    std::string full_name;\n    std::string short_name;\n    std::string variant;\n    std::string short_description;\n    std::string country_flag() const;\n  };\n\n  class XKBContext {\n   public:\n    XKBContext();\n    ~XKBContext();\n    auto next_layout() -> Layout*;\n\n   private:\n    rxkb_context* context_ = nullptr;\n    rxkb_layout* xkb_layout_ = nullptr;\n    Layout* layout_ = nullptr;\n    std::map<std::string, rxkb_layout*> base_layouts_by_name_;\n  };\n\n  void onEvent(const struct Ipc::ipc_response&);\n  void onCmd(const struct Ipc::ipc_response&);\n\n  auto set_current_layout(const std::string& current_layout) -> void;\n  auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;\n\n  const static std::string XKB_LAYOUT_NAMES_KEY;\n  const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;\n\n  Layout layout_;\n  std::string tooltip_format_ = \"\";\n  std::map<std::string, Layout> layouts_map_;\n  bool hide_single_;\n  bool is_variant_displayed;\n  std::byte displayed_short_flag = static_cast<std::byte>(DisplayedShortFlag::None);\n\n  util::JsonParser parser_;\n  std::mutex mutex_;\n  Ipc ipc_;\n};\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/sway/mode.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::sway {\n\nclass Mode : public ALabel, public sigc::trackable {\n public:\n  Mode(const std::string&, const Json::Value&);\n  virtual ~Mode() = default;\n  auto update() -> void override;\n\n private:\n  void onEvent(const struct Ipc::ipc_response&);\n\n  std::string mode_;\n  util::JsonParser parser_;\n  std::mutex mutex_;\n  Ipc ipc_;\n};\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/sway/scratchpad.hpp",
    "content": "#pragma once\n\n#include <gtkmm/label.h>\n\n#include <mutex>\n#include <string>\n\n#include \"ALabel.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::sway {\nclass Scratchpad : public ALabel {\n public:\n  Scratchpad(const std::string&, const Json::Value&);\n  virtual ~Scratchpad() = default;\n  auto update() -> void override;\n\n private:\n  auto getTree() -> void;\n  auto onCmd(const struct Ipc::ipc_response&) -> void;\n  auto onEvent(const struct Ipc::ipc_response&) -> void;\n\n  std::string tooltip_format_;\n  bool show_empty_;\n  bool tooltip_enabled_;\n  std::string tooltip_text_;\n  int count_;\n  std::mutex mutex_;\n  Ipc ipc_;\n  util::JsonParser parser_;\n};\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/sway/window.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <tuple>\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/json.hpp\"\n\nnamespace waybar::modules::sway {\n\nclass Window : public AAppIconLabel, public sigc::trackable {\n public:\n  Window(const std::string&, const waybar::Bar&, const Json::Value&);\n  virtual ~Window() = default;\n  auto update() -> void override;\n\n private:\n  void setClass(const std::string& classname, bool enable);\n  void onEvent(const struct Ipc::ipc_response&);\n  void onCmd(const struct Ipc::ipc_response&);\n  std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,\n             std::string>\n  getFocusedNode(const Json::Value& nodes, std::string& output);\n  void getTree();\n\n  const Bar& bar_;\n  std::string window_;\n  int windowId_;\n  std::string app_id_;\n  std::string app_class_;\n  std::string layout_;\n  std::string old_app_id_;\n  std::size_t app_nb_;\n  std::string shell_;\n  std::string marks_;\n  int floating_count_;\n  util::JsonParser parser_;\n  std::mutex mutex_;\n  Ipc ipc_;\n};\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/sway/workspaces.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n\n#include <string_view>\n#include <unordered_map>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"modules/sway/ipc/client.hpp\"\n#include \"util/json.hpp\"\n#include \"util/regex_collection.hpp\"\n\nnamespace waybar::modules::sway {\n\nclass Workspaces : public AModule, public sigc::trackable {\n public:\n  Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Workspaces() override = default;\n  auto update() -> void override;\n\n private:\n  static constexpr std::string_view workspace_switch_cmd_ = \"workspace {} \\\"{}\\\"\";\n  static constexpr std::string_view persistent_workspace_switch_cmd_ =\n      R\"(workspace {} \"{}\"; move workspace to output \"{}\"; workspace {} \"{}\")\";\n\n  static int convertWorkspaceNameToNum(const std::string& name);\n  static int windowRewritePriorityFunction(std::string const& window_rule);\n\n  void onCmd(const struct Ipc::ipc_response&);\n  void onEvent(const struct Ipc::ipc_response&);\n  bool filterButtons();\n  static bool hasFlag(const Json::Value&, const std::string&);\n  void updateWindows(const Json::Value&, std::string&);\n  Gtk::Button& addButton(const Json::Value&);\n  void onButtonReady(const Json::Value&, Gtk::Button&);\n  std::string getIcon(const std::string&, const Json::Value&);\n  std::string getCycleWorkspace(std::vector<Json::Value>::iterator, bool prev) const;\n  uint16_t getWorkspaceIndex(const std::string& name) const;\n  static std::string trimWorkspaceName(const std::string&);\n  bool handleScroll(GdkEventScroll* /*unused*/) override;\n\n  const Bar& bar_;\n  std::vector<Json::Value> workspaces_;\n  std::vector<std::string> high_priority_named_;\n  std::vector<std::string> workspaces_order_;\n  Gtk::Box box_;\n  std::string m_formatWindowSeparator;\n  util::RegexCollection m_windowRewriteRules;\n  util::JsonParser parser_;\n  std::unordered_map<std::string, Gtk::Button> buttons_;\n  std::mutex mutex_;\n  Ipc ipc_;\n};\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "include/modules/systemd_failed_units.hpp",
    "content": "#pragma once\n\n#include <giomm/dbusproxy.h>\n\n#include <string>\n#include <vector>\n\n#include \"ALabel.hpp\"\n\nnamespace waybar::modules {\n\nclass SystemdFailedUnits : public ALabel {\n public:\n  SystemdFailedUnits(const std::string&, const Json::Value&);\n  virtual ~SystemdFailedUnits() = default;\n  auto update() -> void override;\n\n private:\n  struct FailedUnit {\n    std::string name;\n    std::string description;\n    std::string load_state;\n    std::string active_state;\n    std::string sub_state;\n    std::string scope;\n  };\n\n  bool hide_on_ok_;\n  std::string format_ok_;\n  std::string tooltip_format_;\n  std::string tooltip_format_ok_;\n  std::string tooltip_unit_format_;\n\n  bool update_pending_;\n  std::string system_state_, user_state_, overall_state_;\n  uint32_t nr_failed_system_, nr_failed_user_, nr_failed_;\n  std::string last_status_;\n  Glib::RefPtr<Gio::DBus::Proxy> system_props_proxy_, user_props_proxy_;\n  Glib::RefPtr<Gio::DBus::Proxy> system_manager_proxy_, user_manager_proxy_;\n  std::vector<FailedUnit> failed_units_;\n\n  void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                 const Glib::VariantContainerBase& arguments);\n  void RequestFailedUnits();\n  void RequestFailedUnitsList();\n  void RequestSystemState();\n  std::vector<FailedUnit> LoadFailedUnitsList(const char* kind,\n                                              Glib::RefPtr<Gio::DBus::Proxy>& proxy,\n                                              const std::string& scope);\n  std::string BuildTooltipFailedList() const;\n  void updateData();\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/temperature.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n\n#include <fstream>\n\n#include \"ALabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\n\nclass Temperature : public ALabel {\n public:\n  Temperature(const std::string&, const Json::Value&);\n  virtual ~Temperature() = default;\n  auto update() -> void override;\n\n private:\n  float getTemperature();\n  bool isCritical(uint16_t);\n  bool isWarning(uint16_t);\n\n  std::string file_path_;\n  util::SleeperThread thread_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/upower.hpp",
    "content": "#pragma once\n\n#include <giomm/dbusconnection.h>\n#include <gtkmm/icontheme.h>\n#include <libupower-glib/upower.h>\n\n#include <unordered_map>\n\n#include \"AIconLabel.hpp\"\n\nnamespace waybar::modules {\n\nclass UPower final : public AIconLabel {\n public:\n  UPower(const std::string&, const Json::Value&);\n  virtual ~UPower();\n  auto update() -> void override;\n\n private:\n  const std::string NO_BATTERY{\"battery-missing-symbolic\"};\n\n  // Config\n  bool showIcon_{true};\n  bool hideIfEmpty_{true};\n  int iconSize_{20};\n  int tooltip_spacing_{4};\n  int tooltip_padding_{4};\n  Gtk::Box contentBox_;  // tooltip box\n  std::string tooltipFormat_;\n\n  // UPower device info\n  struct upDevice_output {\n    UpDevice* upDevice{NULL};\n    double percentage{0.0};\n    double temperature{0.0};\n    guint64 time_full{0u};\n    guint64 time_empty{0u};\n    gchar* icon_name{(char*)'\\0'};\n    bool upDeviceValid{false};\n    UpDeviceState state;\n    UpDeviceKind kind;\n    char* nativePath{(char*)'\\0'};\n    char* model{(char*)'\\0'};\n  };\n\n  // Technical variables\n  std::string nativePath_;\n  std::string model_;\n  std::string lastStatus_;\n  Glib::ustring label_markup_;\n  std::mutex mutex_;\n  Glib::RefPtr<Gtk::IconTheme> gtkTheme_;\n  bool sleeping_;\n\n  // Technical functions\n  void addDevice(UpDevice*);\n  void removeDevice(const gchar*);\n  void removeDevices();\n  void resetDevices();\n  void setDisplayDevice();\n  const Glib::ustring getText(const upDevice_output& upDevice_, const std::string& format);\n  bool queryTooltipCb(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&);\n\n  // DBUS variables\n  guint watcherID_;\n  Glib::RefPtr<Gio::DBus::Connection> conn_;\n  guint subscrID_{0u};\n\n  // UPower variables\n  UpClient* upClient_;\n  upDevice_output upDevice_;  // Device to display\n  typedef std::unordered_map<std::string, upDevice_output> Devices;\n  Devices devices_;\n  bool upRunning_{true};\n\n  // DBus callbacks\n  void getConn_cb(Glib::RefPtr<Gio::AsyncResult>& result);\n  void onAppear(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&,\n                const Glib::ustring&);\n  void onVanished(const Glib::RefPtr<Gio::DBus::Connection>&, const Glib::ustring&);\n  void prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                          const Glib::ustring& sender_name, const Glib::ustring& object_path,\n                          const Glib::ustring& interface_name, const Glib::ustring& signal_name,\n                          const Glib::VariantContainerBase& parameters);\n\n  // UPower callbacks\n  static void deviceAdded_cb(UpClient* client, UpDevice* device, gpointer data);\n  static void deviceRemoved_cb(UpClient* client, const gchar* objectPath, gpointer data);\n  static void deviceNotify_cb(UpDevice* device, GParamSpec* pspec, gpointer user_data);\n  // UPower secondary functions\n  void getUpDeviceInfo(upDevice_output& upDevice_);\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/user.hpp",
    "content": "#pragma once\n\n#include <fmt/chrono.h>\n#include <gdkmm/pixbuf.h>\n#include <glibmm/refptr.h>\n\n#include \"AIconLabel.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::modules {\nclass User : public AIconLabel {\n public:\n  User(const std::string&, const Json::Value&);\n  virtual ~User() = default;\n  auto update() -> void override;\n\n  bool handleToggle(GdkEventButton* const& e) override;\n\n private:\n  util::SleeperThread thread_;\n\n  static constexpr inline int defaultUserImageWidth_ = 20;\n  static constexpr inline int defaultUserImageHeight_ = 20;\n\n  long uptime_as_seconds();\n  std::string get_user_login() const;\n  std::string get_user_home_dir() const;\n  std::string get_default_user_avatar_path() const;\n  void init_default_user_avatar(int width, int height);\n  void init_user_avatar(const std::string& path, int width, int height);\n  void init_avatar(const Json::Value& config);\n  void init_update_worker();\n};\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/wayfire/backend.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n#include <unistd.h>\n\n#include <functional>\n#include <list>\n#include <memory>\n#include <mutex>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"util/scoped_fd.hpp\"\n\nnamespace waybar::modules::wayfire {\n\nusing EventHandler = std::function<void(const std::string& event)>;\n\nstruct State {\n  /*\n    ┌───────────┐ ┌───────────┐\n    │ output #1 │ │ output #2 │\n    └─────┬─────┘ └─────┬─────┘\n          └─┐           └─────┐─ ─ ─ ─ ─ ─ ─ ─ ┐\n    ┌───────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐\n    │ wset #1       │ │ wset #2      │ │ wset #3       │\n    │┌────────────┐ │ │┌────────────┐│ │┌────────────┐ │\n    ││ workspaces │ │ ││ workspaces ││ ││ workspaces │ │\n    │└─┬──────────┘ │ │└────────────┘│ │└─┬──────────┘ │\n    │  │ ┌─────────┐│ └──────────────┘ │  │ ┌─────────┐│\n    │  ├─┤ view #1 ││                  │  └─┤ view #3 ││\n    │  │ └─────────┘│                  │    └─────────┘│\n    │  │ ┌─────────┐│                  └───────────────┘\n    │  └─┤ view #2 ││\n    │    └─────────┘│\n    └───────────────┘\n  */\n\n  struct Output {\n    size_t id;\n    size_t w, h;\n    size_t wset_idx;\n  };\n\n  struct Workspace {\n    size_t num_views;\n    size_t num_sticky_views;\n  };\n\n  struct Wset {\n    std::optional<std::reference_wrapper<Output>> output;\n    std::vector<Workspace> wss;\n    size_t ws_w, ws_h, ws_x, ws_y;\n    size_t focused_view_id;\n\n    auto ws_idx() const { return ws_w * ws_y + ws_x; }\n    auto count_ws(const Json::Value& pos) -> Workspace&;\n    auto locate_ws(const Json::Value& geo) -> Workspace&;\n    auto locate_ws(const Json::Value& geo) const -> const Workspace&;\n  };\n\n  std::unordered_map<std::string, Output> outputs;\n  std::unordered_map<size_t, Wset> wsets;\n  std::unordered_map<size_t, Json::Value> views;\n  std::string focused_output_name;\n  size_t maybe_empty_focus_wset_idx = {};\n  size_t vswitch_sticky_view_id = {};\n  bool new_output_detected = {};\n  bool vswitching = {};\n\n  auto update_view(const Json::Value& view) -> void;\n};\n\nusing Sock = util::ScopedFd;\n\nclass IPC : public std::enable_shared_from_this<IPC> {\n  static std::weak_ptr<IPC> instance;\n  Json::CharReaderBuilder reader_builder;\n  Json::StreamWriterBuilder writer_builder;\n  std::list<std::pair<std::string, std::reference_wrapper<const EventHandler>>> handlers;\n  std::mutex handlers_mutex;\n  State state;\n  std::mutex state_mutex;\n\n  IPC() = default;\n\n  static auto connect() -> Sock;\n  auto receive(Sock& sock) -> Json::Value;\n  auto start() -> void;\n  auto root_event_handler(const std::string& event, const Json::Value& data) -> void;\n  auto update_state_handler(const std::string& event, const Json::Value& data) -> void;\n\n public:\n  static auto get_instance() -> std::shared_ptr<IPC>;\n  auto send(const std::string& method, Json::Value&& data) -> Json::Value;\n  auto register_handler(const std::string& event, const EventHandler& handler) -> void;\n  auto unregister_handler(EventHandler& handler) -> void;\n\n  auto lock_state() -> std::lock_guard<std::mutex> { return std::lock_guard{state_mutex}; }\n  auto& get_outputs() const { return state.outputs; }\n  auto& get_wsets() const { return state.wsets; }\n  auto& get_views() const { return state.views; }\n  auto& get_focused_output_name() const { return state.focused_output_name; }\n};\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "include/modules/wayfire/window.hpp",
    "content": "#pragma once\n\n#include \"AAppIconLabel.hpp\"\n#include \"bar.hpp\"\n#include \"modules/wayfire/backend.hpp\"\n\nnamespace waybar::modules::wayfire {\n\nclass Window : public AAppIconLabel {\n  std::shared_ptr<IPC> ipc;\n  EventHandler handler;\n\n  const Bar& bar_;\n  std::string old_app_id_;\n\n public:\n  Window(const std::string& id, const Bar& bar, const Json::Value& config);\n  ~Window() override;\n\n  auto update() -> void override;\n  auto update_icon_label() -> void;\n};\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "include/modules/wayfire/workspaces.hpp",
    "content": "#pragma once\n\n#include <gtkmm/button.h>\n#include <json/json.h>\n\n#include <memory>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"modules/wayfire/backend.hpp\"\n\nnamespace waybar::modules::wayfire {\n\nclass Workspaces : public AModule {\n  std::shared_ptr<IPC> ipc;\n  EventHandler handler;\n\n  const Bar& bar_;\n  Gtk::Box box_;\n  std::vector<Gtk::Button> buttons_;\n\n  auto handleScroll(GdkEventScroll* e) -> bool override;\n  auto update() -> void override;\n  auto update_box() -> void;\n\n public:\n  Workspaces(const std::string& id, const Bar& bar, const Json::Value& config);\n  ~Workspaces() override;\n};\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "include/modules/wireplumber.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <wp/wp.h>\n\n#include <algorithm>\n#include <array>\n\n#include \"ALabel.hpp\"\n\nnamespace waybar::modules {\n\nclass Wireplumber : public ALabel {\n public:\n  Wireplumber(const std::string&, const Json::Value&);\n  virtual ~Wireplumber();\n  auto update() -> void override;\n\n private:\n  void asyncLoadRequiredApiModules();\n  void prepare(waybar::modules::Wireplumber* self);\n  void activatePlugins();\n  static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);\n  static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);\n  static void updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id);\n  static void updateSourceName(waybar::modules::Wireplumber* self, uint32_t id);  // NEW\n  static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);\n  static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,\n                                      waybar::modules::Wireplumber* self);\n  static void onMixerApiLoaded(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);\n  static void onObjectManagerInstalled(waybar::modules::Wireplumber* self);\n  static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id);\n  static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);\n\n  bool handleScroll(GdkEventScroll* e) override;\n\n  static std::list<waybar::modules::Wireplumber*> modules;\n\n  WpCore* wp_core_;\n  GPtrArray* apis_;\n  WpObjectManager* om_;\n  WpPlugin* mixer_api_;\n  WpPlugin* def_nodes_api_;\n  gchar* default_node_name_;\n  uint32_t pending_plugins_;\n  bool muted_;\n  double volume_;\n  double min_step_;\n  uint32_t node_id_{0};\n  std::string node_name_;\n  std::string source_name_;\n  gchar* type_;\n  uint32_t source_node_id_;\n  bool source_muted_;\n  double source_volume_;\n  gchar* default_source_name_;\n};\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "include/modules/wlr/taskbar.hpp",
    "content": "#pragma once\n\n#include <gdk/gdk.h>\n#include <glibmm/refptr.h>\n#include <gtkmm/box.h>\n#include <gtkmm/button.h>\n#include <gtkmm/icontheme.h>\n#include <gtkmm/image.h>\n#include <gtkmm/label.h>\n#include <wayland-client.h>\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <vector>\n\n#include \"AModule.hpp\"\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"giomm/desktopappinfo.h\"\n#include \"util/icon_loader.hpp\"\n#include \"util/json.hpp\"\n#include \"wlr-foreign-toplevel-management-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::wlr {\n\nstruct widget_geometry {\n  int x, y, w, h;\n};\n\nclass Taskbar;\n\nclass Task {\n public:\n  Task(const waybar::Bar&, const Json::Value&, Taskbar*, struct zwlr_foreign_toplevel_handle_v1*,\n       struct wl_seat*);\n  ~Task();\n\n public:\n  enum State {\n    MAXIMIZED = (1 << 0),\n    MINIMIZED = (1 << 1),\n    ACTIVE = (1 << 2),\n    FULLSCREEN = (1 << 3),\n    INVALID = (1 << 4)\n  };\n  // made public so TaskBar can reorder based on configuration.\n  Gtk::Button button;\n  struct widget_geometry minimize_hint;\n\n private:\n  static uint32_t global_id;\n\n private:\n  const waybar::Bar& bar_;\n  const Json::Value& config_;\n  Taskbar* tbar_;\n  struct zwlr_foreign_toplevel_handle_v1* handle_;\n  struct wl_seat* seat_;\n\n  uint32_t id_;\n\n  Gtk::Box content_;\n  Gtk::Image icon_;\n  Gtk::Label text_before_;\n  Gtk::Label text_after_;\n  Glib::RefPtr<Gio::DesktopAppInfo> app_info_;\n  bool button_visible_ = false;\n  bool ignored_ = false;\n\n  bool with_icon_ = false;\n  bool with_name_ = false;\n  std::string format_before_;\n  std::string format_after_;\n\n  std::string format_tooltip_;\n\n  std::string name_;\n  std::string title_;\n  std::string app_id_;\n  uint32_t state_ = 0;\n\n  int32_t drag_start_x;\n  int32_t drag_start_y;\n  int32_t drag_start_button = -1;\n\n private:\n  std::string repr() const;\n  std::string state_string(bool = false) const;\n  void set_minimize_hint();\n  void on_button_size_allocated(Gtk::Allocation& alloc);\n  void hide_if_ignored();\n\n public:\n  /* Getter functions */\n  uint32_t id() const { return id_; }\n  std::string title() const { return title_; }\n  std::string app_id() const { return app_id_; }\n  uint32_t state() const { return state_; }\n  bool maximized() const { return state_ & MAXIMIZED; }\n  bool minimized() const { return state_ & MINIMIZED; }\n  bool active() const { return state_ & ACTIVE; }\n  bool fullscreen() const { return state_ & FULLSCREEN; }\n\n public:\n  /* Callbacks for the wlr protocol */\n  void handle_title(const char*);\n  void handle_app_id(const char*);\n  void handle_output_enter(struct wl_output*);\n  void handle_output_leave(struct wl_output*);\n  void handle_state(struct wl_array*);\n  void handle_done();\n  void handle_closed();\n\n  /* Callbacks for Gtk events */\n  bool handle_clicked(GdkEventButton*);\n  bool handle_button_release(GdkEventButton*);\n  bool handle_motion_notify(GdkEventMotion*);\n  void handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context,\n                            Gtk::SelectionData& selection_data, guint info, guint time);\n  void handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,\n                                 Gtk::SelectionData selection_data, guint info, guint time);\n\n public:\n  bool operator==(const Task&) const;\n  bool operator!=(const Task&) const;\n\n public:\n  void update();\n\n public:\n  /* Interaction with the tasks */\n  void maximize(bool);\n  void minimize(bool);\n  void activate();\n  void fullscreen(bool);\n  void close();\n};\n\nusing TaskPtr = std::unique_ptr<Task>;\n\nclass Taskbar : public waybar::AModule {\n public:\n  Taskbar(const std::string&, const waybar::Bar&, const Json::Value&);\n  ~Taskbar();\n  void update();\n\n private:\n  const waybar::Bar& bar_;\n  Gtk::Box box_;\n  std::vector<TaskPtr> tasks_;\n\n  IconLoader icon_loader_;\n  std::unordered_set<std::string> ignore_list_;\n  std::map<std::string, std::string> app_ids_replace_map_;\n\n  struct zwlr_foreign_toplevel_manager_v1* manager_;\n  struct wl_seat* seat_;\n\n public:\n  /* Callbacks for global registration */\n  void register_manager(struct wl_registry*, uint32_t name, uint32_t version);\n  void register_seat(struct wl_registry*, uint32_t name, uint32_t version);\n\n  /* Callbacks for the wlr protocol */\n  void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*);\n  void handle_finished();\n\n public:\n  void add_button(Gtk::Button&);\n  void move_button(Gtk::Button&, int);\n  void remove_button(Gtk::Button&);\n  void remove_task(uint32_t);\n\n  bool show_output(struct wl_output*) const;\n  bool all_outputs() const;\n\n  const IconLoader& icon_loader() const;\n  const std::unordered_set<std::string>& ignore_list() const;\n  const std::map<std::string, std::string>& app_ids_replace_map() const;\n};\n\n} /* namespace waybar::modules::wlr */\n"
  },
  {
    "path": "include/util/SafeSignal.hpp",
    "content": "#pragma once\n\n#include <glibmm/dispatcher.h>\n#include <sigc++/signal.h>\n\n#include <cstddef>\n#include <functional>\n#include <mutex>\n#include <queue>\n#include <thread>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n\n#ifdef __OpenBSD__\n#define SIGRTMIN SIGUSR1 - 1\n#define SIGRTMAX SIGUSR1 + 1\n#endif\n\nnamespace waybar {\n\n/**\n * Thread-safe signal wrapper.\n * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.\n */\ntemplate <typename... Args>\nstruct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {\n public:\n  SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }\n\n  void set_max_queued_events(std::size_t max_queued_events) {\n    std::unique_lock lock(mutex_);\n    max_queued_events_ = max_queued_events;\n    trim_queue_locked();\n  }\n\n  template <typename... EmitArgs>\n  void emit(EmitArgs&&... args) {\n    if (main_tid_ == std::this_thread::get_id()) {\n      /*\n       * Bypass the queue if the method is called the main thread.\n       * Ensures that events emitted from the main thread are processed synchronously and saves a\n       * few CPU cycles on locking/queuing.\n       * As a downside, this makes main thread events prioritized over the other threads and\n       * disrupts chronological order.\n       */\n      signal_t::emit(std::forward<EmitArgs>(args)...);\n    } else {\n      {\n        std::unique_lock lock(mutex_);\n        if (max_queued_events_ != 0 && queue_.size() >= max_queued_events_) {\n          queue_.pop();\n        }\n        queue_.emplace(std::forward<EmitArgs>(args)...);\n      }\n      dp_.emit();\n    }\n  }\n\n  template <typename... EmitArgs>\n  inline void operator()(EmitArgs&&... args) {\n    emit(std::forward<EmitArgs>(args)...);\n  }\n\n protected:\n  using signal_t = sigc::signal<void(std::decay_t<Args>...)>;\n  using slot_t = decltype(std::declval<signal_t>().make_slot());\n  using arg_tuple_t = std::tuple<std::decay_t<Args>...>;\n  // ensure that unwrapped methods are not accessible\n  using signal_t::emit_reverse;\n  using signal_t::make_slot;\n\n  void trim_queue_locked() {\n    if (max_queued_events_ == 0) {\n      return;\n    }\n    while (queue_.size() > max_queued_events_) {\n      queue_.pop();\n    }\n  }\n\n  void handle_event() {\n    for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {\n      auto args = queue_.front();\n      queue_.pop();\n      lock.unlock();\n      std::apply(cached_fn_, args);\n    }\n  }\n\n  Glib::Dispatcher dp_;\n  std::mutex mutex_;\n  std::queue<arg_tuple_t> queue_;\n  std::size_t max_queued_events_ = 4096;\n  const std::thread::id main_tid_ = std::this_thread::get_id();\n  // cache functor for signal emission to avoid recreating it on each event\n  const slot_t cached_fn_ = make_slot();\n};\n\n}  // namespace waybar\n"
  },
  {
    "path": "include/util/audio_backend.hpp",
    "content": "#pragma once\n\n#include <json/value.h>\n#include <pulse/context.h>\n#include <pulse/introspect.h>\n#include <pulse/thread-mainloop.h>\n#include <pulse/volume.h>\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"util/backend_common.hpp\"\n\nnamespace waybar::util {\n\nclass AudioBackend {\n private:\n  static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);\n  static void contextStateCb(pa_context*, void*);\n  static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);\n  static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);\n  static void serverInfoCb(pa_context*, const pa_server_info*, void*);\n  static void volumeModifyCb(pa_context*, int, void*);\n  void connectContext();\n\n  pa_threaded_mainloop* mainloop_;\n  pa_mainloop_api* mainloop_api_;\n  pa_context* context_;\n  pa_cvolume pa_volume_;\n\n  // SINK\n  uint32_t sink_idx_{0};\n  uint16_t volume_;\n  bool muted_;\n  std::string port_name_;\n  std::string form_factor_;\n  std::string desc_;\n  std::string monitor_;\n  std::string current_sink_name_;\n  std::string default_sink_name;\n  bool default_sink_running_;\n  bool current_sink_running_;\n  // SOURCE\n  uint32_t source_idx_{0};\n  uint16_t source_volume_;\n  bool source_muted_;\n  std::string source_port_name_;\n  std::string source_desc_;\n  std::string default_source_name_;\n\n  std::vector<std::string> ignored_sinks_;\n\n  std::function<void()> on_updated_cb_ = NOOP;\n\n  /* Hack to keep constructor inaccessible but still public.\n   * This is required to be able to use std::make_shared.\n   * It is important to keep this class only accessible via a reference-counted\n   * pointer because the destructor will manually free memory, and this could be\n   * a problem with C++20's copy and move semantics.\n   */\n  struct private_constructor_tag {};\n\n public:\n  static std::shared_ptr<AudioBackend> getInstance(std::function<void()> on_updated_cb = NOOP);\n\n  AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);\n  ~AudioBackend();\n\n  void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100);\n  void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100);\n\n  void setIgnoredSinks(const Json::Value& config);\n\n  std::string getSinkPortName() const { return port_name_; }\n  std::string getFormFactor() const { return form_factor_; }\n  std::string getSinkDesc() const { return desc_; }\n  std::string getMonitor() const { return monitor_; }\n  std::string getCurrentSinkName() const { return current_sink_name_; }\n  bool getCurrentSinkRunning() const { return current_sink_running_; }\n  uint16_t getSinkVolume() const { return volume_; }\n  bool getSinkMuted() const { return muted_; }\n  uint16_t getSourceVolume() const { return source_volume_; }\n  bool getSourceMuted() const { return source_muted_; }\n  std::string getSourcePortName() const { return source_port_name_; }\n  std::string getSourceDesc() const { return source_desc_; }\n  std::string getDefaultSourceName() const { return default_source_name_; }\n\n  void toggleSinkMute();\n  void toggleSinkMute(bool);\n\n  void toggleSourceMute();\n  void toggleSourceMute(bool);\n\n  bool isBluetooth();\n};\n\n}  // namespace waybar::util"
  },
  {
    "path": "include/util/backend_common.hpp",
    "content": "#pragma once\n\n#include \"AModule.hpp\"\n\nnamespace waybar::util {\n\nconst static auto NOOP = []() {};\nenum class ChangeType : char { Increase, Decrease };\n\n}  // namespace waybar::util"
  },
  {
    "path": "include/util/backlight_backend.hpp",
    "content": "#pragma once\n\n#include <libudev.h>\n#include <spdlog/spdlog.h>\n\n#include <chrono>\n#include <mutex>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"giomm/dbusproxy.h\"\n#include \"util/backend_common.hpp\"\n#include \"util/sleeper_thread.hpp\"\n\n#define GET_BEST_DEVICE(varname, backend, preferred_device)          \\\n  decltype((backend).devices_) __devices;                            \\\n  {                                                                  \\\n    std::scoped_lock<std::mutex> lock((backend).udev_thread_mutex_); \\\n    __devices = (backend).devices_;                                  \\\n  }                                                                  \\\n  auto varname = (backend).best_device(__devices, preferred_device);\n\nnamespace waybar::util {\n\nclass BacklightDevice {\n public:\n  BacklightDevice() = default;\n  BacklightDevice(std::string name, int actual, int max, bool powered);\n\n  std::string name() const;\n  int get_actual() const;\n  void set_actual(int actual);\n  int get_max() const;\n  void set_max(int max);\n  bool get_powered() const;\n  void set_powered(bool powered);\n  friend inline bool operator==(const BacklightDevice& lhs, const BacklightDevice& rhs) {\n    return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;\n  }\n\n private:\n  std::string name_;\n  int actual_ = 1;\n  int max_ = 1;\n  bool powered_ = true;\n};\n\nclass BacklightBackend {\n public:\n  BacklightBackend(std::chrono::milliseconds interval, std::function<void()> on_updated_cb = NOOP);\n\n  // const inline BacklightDevice *get_best_device(std::string_view preferred_device);\n  const BacklightDevice* get_previous_best_device();\n\n  void set_previous_best_device(const BacklightDevice* device);\n\n  void set_brightness(const std::string& preferred_device, ChangeType change_type, double step);\n\n  void set_scaled_brightness(const std::string& preferred_device, int brightness);\n  int get_scaled_brightness(const std::string& preferred_device);\n\n  bool is_login_proxy_initialized() const { return static_cast<bool>(login_proxy_); }\n\n  static const BacklightDevice* best_device(const std::vector<BacklightDevice>& devices,\n                                            std::string_view);\n\n  std::vector<BacklightDevice> devices_;\n  std::mutex udev_thread_mutex_;\n\n private:\n  void set_brightness_internal(const std::string& device_name, int brightness, int max_brightness);\n\n  std::function<void()> on_updated_cb_;\n  std::chrono::milliseconds polling_interval_;\n\n  std::optional<BacklightDevice> previous_best_;\n  // thread must destruct before shared data\n  util::SleeperThread udev_thread_;\n\n  Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;\n\n  static constexpr int EPOLL_MAX_EVENTS = 16;\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/clara.hpp",
    "content": "// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// See https://github.com/philsquared/Clara for more details\n\n// Clara v1.1.5\n\n#ifndef CLARA_HPP_INCLUDED\n#define CLARA_HPP_INCLUDED\n\n#ifndef CLARA_CONFIG_CONSOLE_WIDTH\n#define CLARA_CONFIG_CONSOLE_WIDTH 80\n#endif\n\n#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH\n#endif\n\n#ifndef CLARA_CONFIG_OPTIONAL_TYPE\n#ifdef __has_include\n#if __has_include(<optional>) && __cplusplus >= 201703L\n#include <optional>\n#define CLARA_CONFIG_OPTIONAL_TYPE std::optional\n#endif\n#endif\n#endif\n\n// ----------- #included from clara_textflow.hpp -----------\n\n// TextFlowCpp\n//\n// A single-header library for wrapping and laying out basic text, by Phil Nash\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// This project is hosted at https://github.com/philsquared/textflowcpp\n\n#ifndef CLARA_TEXTFLOW_HPP_INCLUDED\n#define CLARA_TEXTFLOW_HPP_INCLUDED\n\n#include <cassert>\n#include <ostream>\n#include <sstream>\n#include <vector>\n\n#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace clara {\nnamespace TextFlow {\n\ninline auto isWhitespace(char c) -> bool {\n  static std::string chars = \" \\t\\n\\r\";\n  return chars.find(c) != std::string::npos;\n}\ninline auto isBreakableBefore(char c) -> bool {\n  static std::string chars = \"[({<|\";\n  return chars.find(c) != std::string::npos;\n}\ninline auto isBreakableAfter(char c) -> bool {\n  static std::string chars = \"])}>.,:;*+-=&/\\\\\";\n  return chars.find(c) != std::string::npos;\n}\n\nclass Columns;\n\nclass Column {\n  std::vector<std::string> m_strings;\n  size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;\n  size_t m_indent = 0;\n  size_t m_initialIndent = std::string::npos;\n\n public:\n  class iterator {\n    friend Column;\n\n    Column const& m_column;\n    size_t m_stringIndex = 0;\n    size_t m_pos = 0;\n\n    size_t m_len = 0;\n    size_t m_end = 0;\n    bool m_suffix = false;\n\n    iterator(Column const& column, size_t stringIndex)\n        : m_column(column), m_stringIndex(stringIndex) {}\n\n    auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }\n\n    auto isBoundary(size_t at) const -> bool {\n      assert(at > 0);\n      assert(at <= line().size());\n\n      return at == line().size() || (isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||\n             isBreakableBefore(line()[at]) || isBreakableAfter(line()[at - 1]);\n    }\n\n    void calcLength() {\n      assert(m_stringIndex < m_column.m_strings.size());\n\n      m_suffix = false;\n      auto width = m_column.m_width - indent();\n      m_end = m_pos;\n      while (m_end < line().size() && line()[m_end] != '\\n') ++m_end;\n\n      if (m_end < m_pos + width) {\n        m_len = m_end - m_pos;\n      } else {\n        size_t len = width;\n        while (len > 0 && !isBoundary(m_pos + len)) --len;\n        while (len > 0 && isWhitespace(line()[m_pos + len - 1])) --len;\n\n        if (len > 0) {\n          m_len = len;\n        } else {\n          m_suffix = true;\n          m_len = width - 1;\n        }\n      }\n    }\n\n    auto indent() const -> size_t {\n      auto initial =\n          m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;\n      return initial == std::string::npos ? m_column.m_indent : initial;\n    }\n\n    auto addIndentAndSuffix(std::string const& plain) const -> std::string {\n      return std::string(indent(), ' ') + (m_suffix ? plain + \"-\" : plain);\n    }\n\n   public:\n    using difference_type = std::ptrdiff_t;\n    using value_type = std::string;\n    using pointer = value_type*;\n    using reference = value_type&;\n    using iterator_category = std::forward_iterator_tag;\n\n    explicit iterator(Column const& column) : m_column(column) {\n      assert(m_column.m_width > m_column.m_indent);\n      assert(m_column.m_initialIndent == std::string::npos ||\n             m_column.m_width > m_column.m_initialIndent);\n      calcLength();\n      if (m_len == 0) m_stringIndex++;  // Empty string\n    }\n\n    auto operator*() const -> std::string {\n      assert(m_stringIndex < m_column.m_strings.size());\n      assert(m_pos <= m_end);\n      return addIndentAndSuffix(line().substr(m_pos, m_len));\n    }\n\n    auto operator++() -> iterator& {\n      m_pos += m_len;\n      if (m_pos < line().size() && line()[m_pos] == '\\n')\n        m_pos += 1;\n      else\n        while (m_pos < line().size() && isWhitespace(line()[m_pos])) ++m_pos;\n\n      if (m_pos == line().size()) {\n        m_pos = 0;\n        ++m_stringIndex;\n      }\n      if (m_stringIndex < m_column.m_strings.size()) calcLength();\n      return *this;\n    }\n    auto operator++(int) -> iterator {\n      iterator prev(*this);\n      operator++();\n      return prev;\n    }\n\n    auto operator==(iterator const& other) const -> bool {\n      return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex &&\n             &m_column == &other.m_column;\n    }\n    auto operator!=(iterator const& other) const -> bool { return !operator==(other); }\n  };\n  using const_iterator = iterator;\n\n  explicit Column(std::string const& text) { m_strings.push_back(text); }\n\n  auto width(size_t newWidth) -> Column& {\n    assert(newWidth > 0);\n    m_width = newWidth;\n    return *this;\n  }\n  auto indent(size_t newIndent) -> Column& {\n    m_indent = newIndent;\n    return *this;\n  }\n  auto initialIndent(size_t newIndent) -> Column& {\n    m_initialIndent = newIndent;\n    return *this;\n  }\n\n  auto width() const -> size_t { return m_width; }\n  auto begin() const -> iterator { return iterator(*this); }\n  auto end() const -> iterator { return {*this, m_strings.size()}; }\n\n  inline friend std::ostream& operator<<(std::ostream& os, Column const& col) {\n    bool first = true;\n    for (auto line : col) {\n      if (first)\n        first = false;\n      else\n        os << \"\\n\";\n      os << line;\n    }\n    return os;\n  }\n\n  auto operator+(Column const& other) -> Columns;\n\n  auto toString() const -> std::string {\n    std::ostringstream oss;\n    oss << *this;\n    return oss.str();\n  }\n};\n\nclass Spacer : public Column {\n public:\n  explicit Spacer(size_t spaceWidth) : Column(\"\") { width(spaceWidth); }\n};\n\nclass Columns {\n  std::vector<Column> m_columns;\n\n public:\n  class iterator {\n    friend Columns;\n    struct EndTag {};\n\n    std::vector<Column> const& m_columns;\n    std::vector<Column::iterator> m_iterators;\n    size_t m_activeIterators;\n\n    iterator(Columns const& columns, EndTag) : m_columns(columns.m_columns), m_activeIterators(0) {\n      m_iterators.reserve(m_columns.size());\n\n      for (auto const& col : m_columns) m_iterators.push_back(col.end());\n    }\n\n   public:\n    using difference_type = std::ptrdiff_t;\n    using value_type = std::string;\n    using pointer = value_type*;\n    using reference = value_type&;\n    using iterator_category = std::forward_iterator_tag;\n\n    explicit iterator(Columns const& columns)\n        : m_columns(columns.m_columns), m_activeIterators(m_columns.size()) {\n      m_iterators.reserve(m_columns.size());\n\n      for (auto const& col : m_columns) m_iterators.push_back(col.begin());\n    }\n\n    auto operator==(iterator const& other) const -> bool {\n      return m_iterators == other.m_iterators;\n    }\n    auto operator!=(iterator const& other) const -> bool {\n      return m_iterators != other.m_iterators;\n    }\n    auto operator*() const -> std::string {\n      std::string row, padding;\n\n      for (size_t i = 0; i < m_columns.size(); ++i) {\n        auto width = m_columns[i].width();\n        if (m_iterators[i] != m_columns[i].end()) {\n          std::string col = *m_iterators[i];\n          row += padding + col;\n          if (col.size() < width)\n            padding = std::string(width - col.size(), ' ');\n          else\n            padding = \"\";\n        } else {\n          padding += std::string(width, ' ');\n        }\n      }\n      return row;\n    }\n    auto operator++() -> iterator& {\n      for (size_t i = 0; i < m_columns.size(); ++i) {\n        if (m_iterators[i] != m_columns[i].end()) ++m_iterators[i];\n      }\n      return *this;\n    }\n    auto operator++(int) -> iterator {\n      iterator prev(*this);\n      operator++();\n      return prev;\n    }\n  };\n  using const_iterator = iterator;\n\n  auto begin() const -> iterator { return iterator(*this); }\n  auto end() const -> iterator { return {*this, iterator::EndTag()}; }\n\n  auto operator+=(Column const& col) -> Columns& {\n    m_columns.push_back(col);\n    return *this;\n  }\n  auto operator+(Column const& col) -> Columns {\n    Columns combined = *this;\n    combined += col;\n    return combined;\n  }\n\n  inline friend std::ostream& operator<<(std::ostream& os, Columns const& cols) {\n    bool first = true;\n    for (auto line : cols) {\n      if (first)\n        first = false;\n      else\n        os << \"\\n\";\n      os << line;\n    }\n    return os;\n  }\n\n  auto toString() const -> std::string {\n    std::ostringstream oss;\n    oss << *this;\n    return oss.str();\n  }\n};\n\ninline auto Column::operator+(Column const& other) -> Columns {\n  Columns cols;\n  cols += *this;\n  cols += other;\n  return cols;\n}\n}  // namespace TextFlow\n}  // namespace clara\n\n#endif  // CLARA_TEXTFLOW_HPP_INCLUDED\n\n// ----------- end of #include from clara_textflow.hpp -----------\n// ........... back in clara.hpp\n\n#include <algorithm>\n#include <memory>\n#include <set>\n\n#if !defined(CLARA_PLATFORM_WINDOWS) && \\\n    (defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER))\n#define CLARA_PLATFORM_WINDOWS\n#endif\n\nnamespace clara {\nnamespace detail {\n\n// Traits for extracting arg and return type of lambdas (for single argument lambdas)\ntemplate <typename L>\nstruct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};\n\ntemplate <typename ClassT, typename ReturnT, typename... Args>\nstruct UnaryLambdaTraits<ReturnT (ClassT::*)(Args...) const> {\n  static const bool isValid = false;\n};\n\ntemplate <typename ClassT, typename ReturnT, typename ArgT>\nstruct UnaryLambdaTraits<ReturnT (ClassT::*)(ArgT) const> {\n  static const bool isValid = true;\n  using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;\n  using ReturnType = ReturnT;\n};\n\nclass TokenStream;\n\n// Transport for raw args (copied from main args, or supplied via init list for testing)\nclass Args {\n  friend TokenStream;\n  std::string m_exeName;\n  std::vector<std::string> m_args;\n\n public:\n  Args(int argc, char const* const* argv) : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {}\n\n  Args(std::initializer_list<std::string> args)\n      : m_exeName(*args.begin()), m_args(args.begin() + 1, args.end()) {}\n\n  auto exeName() const -> std::string { return m_exeName; }\n};\n\n// Wraps a token coming from a token stream. These may not directly correspond to strings as a\n// single string may encode an option + its argument if the : or = form is used\nenum class TokenType { Option, Argument };\nstruct Token {\n  TokenType type;\n  std::string token;\n};\n\ninline auto isOptPrefix(char c) -> bool {\n  return c == '-'\n#ifdef CLARA_PLATFORM_WINDOWS\n         || c == '/'\n#endif\n      ;\n}\n\n// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled\nclass TokenStream {\n  using Iterator = std::vector<std::string>::const_iterator;\n  Iterator it;\n  Iterator itEnd;\n  std::vector<Token> m_tokenBuffer;\n\n  void loadBuffer() {\n    m_tokenBuffer.resize(0);\n\n    // Skip any empty strings\n    while (it != itEnd && it->empty()) ++it;\n\n    if (it != itEnd) {\n      auto const& next = *it;\n      if (isOptPrefix(next[0])) {\n        auto delimiterPos = next.find_first_of(\" :=\");\n        if (delimiterPos != std::string::npos) {\n          m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)});\n          m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)});\n        } else {\n          if (next[1] != '-' && next.size() > 2) {\n            std::string opt = \"- \";\n            for (size_t i = 1; i < next.size(); ++i) {\n              opt[1] = next[i];\n              m_tokenBuffer.push_back({TokenType::Option, opt});\n            }\n          } else {\n            m_tokenBuffer.push_back({TokenType::Option, next});\n          }\n        }\n      } else {\n        m_tokenBuffer.push_back({TokenType::Argument, next});\n      }\n    }\n  }\n\n public:\n  explicit TokenStream(Args const& args) : TokenStream(args.m_args.begin(), args.m_args.end()) {}\n\n  TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { loadBuffer(); }\n\n  explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; }\n\n  auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }\n\n  auto operator*() const -> Token {\n    assert(!m_tokenBuffer.empty());\n    return m_tokenBuffer.front();\n  }\n\n  auto operator->() const -> Token const* {\n    assert(!m_tokenBuffer.empty());\n    return &m_tokenBuffer.front();\n  }\n\n  auto operator++() -> TokenStream& {\n    if (m_tokenBuffer.size() >= 2) {\n      m_tokenBuffer.erase(m_tokenBuffer.begin());\n    } else {\n      if (it != itEnd) ++it;\n      loadBuffer();\n    }\n    return *this;\n  }\n};\n\nclass ResultBase {\n public:\n  enum Type { Ok, LogicError, RuntimeError };\n\n protected:\n  ResultBase(Type type) : m_type(type) {}\n  virtual ~ResultBase() = default;\n\n  virtual void enforceOk() const = 0;\n\n  Type m_type;\n};\n\ntemplate <typename T>\nclass ResultValueBase : public ResultBase {\n public:\n  auto value() const -> T const& {\n    enforceOk();\n    return m_value;\n  }\n\n protected:\n  ResultValueBase(Type type) : ResultBase(type) {}\n\n  ResultValueBase(ResultValueBase const& other) : ResultBase(other) {\n    if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value);\n  }\n\n  ResultValueBase(Type, T const& value) : ResultBase(Ok) { new (&m_value) T(value); }\n\n  auto operator=(ResultValueBase const& other) -> ResultValueBase& {\n    if (m_type == ResultBase::Ok) m_value.~T();\n    ResultBase::operator=(other);\n    if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value);\n    return *this;\n  }\n\n  ~ResultValueBase() override {\n    if (m_type == Ok) m_value.~T();\n  }\n\n  union {\n    T m_value;\n  };\n};\n\ntemplate <>\nclass ResultValueBase<void> : public ResultBase {\n protected:\n  using ResultBase::ResultBase;\n};\n\ntemplate <typename T = void>\nclass BasicResult : public ResultValueBase<T> {\n public:\n  template <typename U>\n  explicit BasicResult(BasicResult<U> const& other)\n      : ResultValueBase<T>(other.type()), m_errorMessage(other.errorMessage()) {\n    assert(type() != ResultBase::Ok);\n  }\n\n  template <typename U>\n  static auto ok(U const& value) -> BasicResult {\n    return {ResultBase::Ok, value};\n  }\n  static auto ok() -> BasicResult { return {ResultBase::Ok}; }\n  static auto logicError(std::string const& message) -> BasicResult {\n    return {ResultBase::LogicError, message};\n  }\n  static auto runtimeError(std::string const& message) -> BasicResult {\n    return {ResultBase::RuntimeError, message};\n  }\n\n  explicit operator bool() const { return m_type == ResultBase::Ok; }\n  auto type() const -> ResultBase::Type { return m_type; }\n  auto errorMessage() const -> std::string { return m_errorMessage; }\n\n protected:\n  void enforceOk() const override {\n    // Errors shouldn't reach this point, but if they do\n    // the actual error message will be in m_errorMessage\n    assert(m_type != ResultBase::LogicError);\n    assert(m_type != ResultBase::RuntimeError);\n    if (m_type != ResultBase::Ok) std::abort();\n  }\n\n  std::string m_errorMessage;  // Only populated if resultType is an error\n\n  BasicResult(ResultBase::Type type, std::string const& message)\n      : ResultValueBase<T>(type), m_errorMessage(message) {\n    assert(m_type != ResultBase::Ok);\n  }\n\n  using ResultValueBase<T>::ResultValueBase;\n  using ResultBase::m_type;\n};\n\nenum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame };\n\nclass ParseState {\n public:\n  ParseState(ParseResultType type, TokenStream const& remainingTokens)\n      : m_type(type), m_remainingTokens(remainingTokens) {}\n\n  auto type() const -> ParseResultType { return m_type; }\n  auto remainingTokens() const -> TokenStream { return m_remainingTokens; }\n\n private:\n  ParseResultType m_type;\n  TokenStream m_remainingTokens;\n};\n\nusing Result = BasicResult<void>;\nusing ParserResult = BasicResult<ParseResultType>;\nusing InternalParseResult = BasicResult<ParseState>;\n\nstruct HelpColumns {\n  std::string left;\n  std::string right;\n};\n\ntemplate <typename T>\ninline auto convertInto(std::string const& source, T& target) -> ParserResult {\n  std::stringstream ss;\n  ss << source;\n  ss >> target;\n  if (ss.fail())\n    return ParserResult::runtimeError(\"Unable to convert '\" + source + \"' to destination type\");\n  else\n    return ParserResult::ok(ParseResultType::Matched);\n}\ninline auto convertInto(std::string const& source, std::string& target) -> ParserResult {\n  target = source;\n  return ParserResult::ok(ParseResultType::Matched);\n}\ninline auto convertInto(std::string const& source, bool& target) -> ParserResult {\n  std::string srcLC = source;\n  std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(),\n                 [](char c) { return static_cast<char>(::tolower(c)); });\n  if (srcLC == \"y\" || srcLC == \"1\" || srcLC == \"true\" || srcLC == \"yes\" || srcLC == \"on\")\n    target = true;\n  else if (srcLC == \"n\" || srcLC == \"0\" || srcLC == \"false\" || srcLC == \"no\" || srcLC == \"off\")\n    target = false;\n  else\n    return ParserResult::runtimeError(\"Expected a boolean value but did not recognise: '\" + source +\n                                      \"'\");\n  return ParserResult::ok(ParseResultType::Matched);\n}\n#ifdef CLARA_CONFIG_OPTIONAL_TYPE\ntemplate <typename T>\ninline auto convertInto(std::string const& source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target)\n    -> ParserResult {\n  T temp;\n  auto result = convertInto(source, temp);\n  if (result) target = std::move(temp);\n  return result;\n}\n#endif  // CLARA_CONFIG_OPTIONAL_TYPE\n\nstruct NonCopyable {\n  NonCopyable() = default;\n  NonCopyable(NonCopyable const&) = delete;\n  NonCopyable(NonCopyable&&) = delete;\n  NonCopyable& operator=(NonCopyable const&) = delete;\n  NonCopyable& operator=(NonCopyable&&) = delete;\n};\n\nstruct BoundRef : NonCopyable {\n  virtual ~BoundRef() = default;\n  virtual auto isContainer() const -> bool { return false; }\n  virtual auto isFlag() const -> bool { return false; }\n};\nstruct BoundValueRefBase : BoundRef {\n  virtual auto setValue(std::string const& arg) -> ParserResult = 0;\n};\nstruct BoundFlagRefBase : BoundRef {\n  virtual auto setFlag(bool flag) -> ParserResult = 0;\n  virtual auto isFlag() const -> bool { return true; }\n};\n\ntemplate <typename T>\nstruct BoundValueRef : BoundValueRefBase {\n  T& m_ref;\n\n  explicit BoundValueRef(T& ref) : m_ref(ref) {}\n\n  auto setValue(std::string const& arg) -> ParserResult override { return convertInto(arg, m_ref); }\n};\n\ntemplate <typename T>\nstruct BoundValueRef<std::vector<T>> : BoundValueRefBase {\n  std::vector<T>& m_ref;\n\n  explicit BoundValueRef(std::vector<T>& ref) : m_ref(ref) {}\n\n  auto isContainer() const -> bool override { return true; }\n\n  auto setValue(std::string const& arg) -> ParserResult override {\n    T temp;\n    auto result = convertInto(arg, temp);\n    if (result) m_ref.push_back(temp);\n    return result;\n  }\n};\n\nstruct BoundFlagRef : BoundFlagRefBase {\n  bool& m_ref;\n\n  explicit BoundFlagRef(bool& ref) : m_ref(ref) {}\n\n  auto setFlag(bool flag) -> ParserResult override {\n    m_ref = flag;\n    return ParserResult::ok(ParseResultType::Matched);\n  }\n};\n\ntemplate <typename ReturnType>\nstruct LambdaInvoker {\n  static_assert(std::is_same<ReturnType, ParserResult>::value,\n                \"Lambda must return void or clara::ParserResult\");\n\n  template <typename L, typename ArgType>\n  static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult {\n    return lambda(arg);\n  }\n};\n\ntemplate <>\nstruct LambdaInvoker<void> {\n  template <typename L, typename ArgType>\n  static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult {\n    lambda(arg);\n    return ParserResult::ok(ParseResultType::Matched);\n  }\n};\n\ntemplate <typename ArgType, typename L>\ninline auto invokeLambda(L const& lambda, std::string const& arg) -> ParserResult {\n  ArgType temp{};\n  auto result = convertInto(arg, temp);\n  return !result ? result\n                 : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);\n}\n\ntemplate <typename L>\nstruct BoundLambda : BoundValueRefBase {\n  L m_lambda;\n\n  static_assert(UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\");\n  explicit BoundLambda(L const& lambda) : m_lambda(lambda) {}\n\n  auto setValue(std::string const& arg) -> ParserResult override {\n    return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>(m_lambda, arg);\n  }\n};\n\ntemplate <typename L>\nstruct BoundFlagLambda : BoundFlagRefBase {\n  L m_lambda;\n\n  static_assert(UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\");\n  static_assert(std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value,\n                \"flags must be boolean\");\n\n  explicit BoundFlagLambda(L const& lambda) : m_lambda(lambda) {}\n\n  auto setFlag(bool flag) -> ParserResult override {\n    return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(m_lambda, flag);\n  }\n};\n\nenum class Optionality { Optional, Required };\n\nstruct Parser;\n\nclass ParserBase {\n public:\n  virtual ~ParserBase() = default;\n  virtual auto validate() const -> Result { return Result::ok(); }\n  virtual auto parse(std::string const& exeName, TokenStream const& tokens) const\n      -> InternalParseResult = 0;\n  virtual auto cardinality() const -> size_t { return 1; }\n\n  auto parse(Args const& args) const -> InternalParseResult {\n    return parse(args.exeName(), TokenStream(args));\n  }\n};\n\ntemplate <typename DerivedT>\nclass ComposableParserImpl : public ParserBase {\n public:\n  template <typename T>\n  auto operator|(T const& other) const -> Parser;\n\n  template <typename T>\n  auto operator+(T const& other) const -> Parser;\n};\n\n// Common code and state for Args and Opts\ntemplate <typename DerivedT>\nclass ParserRefImpl : public ComposableParserImpl<DerivedT> {\n protected:\n  Optionality m_optionality = Optionality::Optional;\n  std::shared_ptr<BoundRef> m_ref;\n  std::string m_hint;\n  std::string m_description;\n\n  explicit ParserRefImpl(std::shared_ptr<BoundRef> const& ref) : m_ref(ref) {}\n\n public:\n  template <typename T>\n  ParserRefImpl(T& ref, std::string const& hint)\n      : m_ref(std::make_shared<BoundValueRef<T>>(ref)), m_hint(hint) {}\n\n  template <typename LambdaT>\n  ParserRefImpl(LambdaT const& ref, std::string const& hint)\n      : m_ref(std::make_shared<BoundLambda<LambdaT>>(ref)), m_hint(hint) {}\n\n  auto operator()(std::string const& description) -> DerivedT& {\n    m_description = description;\n    return static_cast<DerivedT&>(*this);\n  }\n\n  auto optional() -> DerivedT& {\n    m_optionality = Optionality::Optional;\n    return static_cast<DerivedT&>(*this);\n  };\n\n  auto required() -> DerivedT& {\n    m_optionality = Optionality::Required;\n    return static_cast<DerivedT&>(*this);\n  };\n\n  auto isOptional() const -> bool { return m_optionality == Optionality::Optional; }\n\n  auto cardinality() const -> size_t override {\n    if (m_ref->isContainer())\n      return 0;\n    else\n      return 1;\n  }\n\n  auto hint() const -> std::string { return m_hint; }\n};\n\nclass ExeName : public ComposableParserImpl<ExeName> {\n  std::shared_ptr<std::string> m_name;\n  std::shared_ptr<BoundValueRefBase> m_ref;\n\n  template <typename LambdaT>\n  static auto makeRef(LambdaT const& lambda) -> std::shared_ptr<BoundValueRefBase> {\n    return std::make_shared<BoundLambda<LambdaT>>(lambda);\n  }\n\n public:\n  ExeName() : m_name(std::make_shared<std::string>(\"<executable>\")) {}\n\n  explicit ExeName(std::string& ref) : ExeName() {\n    m_ref = std::make_shared<BoundValueRef<std::string>>(ref);\n  }\n\n  template <typename LambdaT>\n  explicit ExeName(LambdaT const& lambda) : ExeName() {\n    m_ref = std::make_shared<BoundLambda<LambdaT>>(lambda);\n  }\n\n  // The exe name is not parsed out of the normal tokens, but is handled specially\n  auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override {\n    return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));\n  }\n\n  auto name() const -> std::string { return *m_name; }\n  auto set(std::string const& newName) -> ParserResult {\n    auto lastSlash = newName.find_last_of(\"\\\\/\");\n    auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1);\n\n    *m_name = filename;\n    if (m_ref)\n      return m_ref->setValue(filename);\n    else\n      return ParserResult::ok(ParseResultType::Matched);\n  }\n};\n\nclass Arg : public ParserRefImpl<Arg> {\n public:\n  using ParserRefImpl::ParserRefImpl;\n\n  auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override {\n    auto validationResult = validate();\n    if (!validationResult) return InternalParseResult(validationResult);\n\n    auto remainingTokens = tokens;\n    auto const& token = *remainingTokens;\n    if (token.type != TokenType::Argument)\n      return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));\n\n    assert(!m_ref->isFlag());\n    auto valueRef = static_cast<detail::BoundValueRefBase*>(m_ref.get());\n\n    auto result = valueRef->setValue(remainingTokens->token);\n    if (!result)\n      return InternalParseResult(result);\n    else\n      return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));\n  }\n};\n\ninline auto normaliseOpt(std::string const& optName) -> std::string {\n#ifdef CLARA_PLATFORM_WINDOWS\n  if (optName[0] == '/')\n    return \"-\" + optName.substr(1);\n  else\n#endif\n    return optName;\n}\n\nclass Opt : public ParserRefImpl<Opt> {\n protected:\n  std::vector<std::string> m_optNames;\n\n public:\n  template <typename LambdaT>\n  explicit Opt(LambdaT const& ref)\n      : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}\n\n  explicit Opt(bool& ref) : ParserRefImpl(std::make_shared<BoundFlagRef>(ref)) {}\n\n  template <typename LambdaT>\n  Opt(LambdaT const& ref, std::string const& hint) : ParserRefImpl(ref, hint) {}\n\n  template <typename T>\n  Opt(T& ref, std::string const& hint) : ParserRefImpl(ref, hint) {}\n\n  auto operator[](std::string const& optName) -> Opt& {\n    m_optNames.push_back(optName);\n    return *this;\n  }\n\n  auto getHelpColumns() const -> std::vector<HelpColumns> {\n    std::ostringstream oss;\n    bool first = true;\n    for (auto const& opt : m_optNames) {\n      if (first)\n        first = false;\n      else\n        oss << \", \";\n      oss << opt;\n    }\n    if (!m_hint.empty()) oss << \" <\" << m_hint << \">\";\n    return {{oss.str(), m_description}};\n  }\n\n  auto isMatch(std::string const& optToken) const -> bool {\n    auto normalisedToken = normaliseOpt(optToken);\n    for (auto const& name : m_optNames) {\n      if (normaliseOpt(name) == normalisedToken) return true;\n    }\n    return false;\n  }\n\n  using ParserBase::parse;\n\n  auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override {\n    auto validationResult = validate();\n    if (!validationResult) return InternalParseResult(validationResult);\n\n    auto remainingTokens = tokens;\n    if (remainingTokens && remainingTokens->type == TokenType::Option) {\n      auto const& token = *remainingTokens;\n      if (isMatch(token.token)) {\n        if (m_ref->isFlag()) {\n          auto flagRef = static_cast<detail::BoundFlagRefBase*>(m_ref.get());\n          auto result = flagRef->setFlag(true);\n          if (!result) return InternalParseResult(result);\n          if (result.value() == ParseResultType::ShortCircuitAll)\n            return InternalParseResult::ok(ParseState(result.value(), remainingTokens));\n        } else {\n          auto valueRef = static_cast<detail::BoundValueRefBase*>(m_ref.get());\n          ++remainingTokens;\n          if (!remainingTokens)\n            return InternalParseResult::runtimeError(\"Expected argument following \" + token.token);\n          auto const& argToken = *remainingTokens;\n          if (argToken.type != TokenType::Argument)\n            return InternalParseResult::runtimeError(\"Expected argument following \" + token.token);\n          auto result = valueRef->setValue(argToken.token);\n          if (!result) return InternalParseResult(result);\n          if (result.value() == ParseResultType::ShortCircuitAll)\n            return InternalParseResult::ok(ParseState(result.value(), remainingTokens));\n        }\n        return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));\n      }\n    }\n    return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens));\n  }\n\n  auto validate() const -> Result override {\n    if (m_optNames.empty()) return Result::logicError(\"No options supplied to Opt\");\n    for (auto const& name : m_optNames) {\n      if (name.empty()) return Result::logicError(\"Option name cannot be empty\");\n#ifdef CLARA_PLATFORM_WINDOWS\n      if (name[0] != '-' && name[0] != '/')\n        return Result::logicError(\"Option name must begin with '-' or '/'\");\n#else\n      if (name[0] != '-') return Result::logicError(\"Option name must begin with '-'\");\n#endif\n    }\n    return ParserRefImpl::validate();\n  }\n};\n\nstruct Help : Opt {\n  Help(bool& showHelpFlag)\n      : Opt([&](bool flag) {\n          showHelpFlag = flag;\n          return ParserResult::ok(ParseResultType::ShortCircuitAll);\n        }) {\n    static_cast<Opt&>(*this)(\"display usage information\")[\"-?\"][\"-h\"][\"--help\"].optional();\n  }\n};\n\nstruct Parser : ParserBase {\n  mutable ExeName m_exeName;\n  std::vector<Opt> m_options;\n  std::vector<Arg> m_args;\n\n  auto operator|=(ExeName const& exeName) -> Parser& {\n    m_exeName = exeName;\n    return *this;\n  }\n\n  auto operator|=(Arg const& arg) -> Parser& {\n    m_args.push_back(arg);\n    return *this;\n  }\n\n  auto operator|=(Opt const& opt) -> Parser& {\n    m_options.push_back(opt);\n    return *this;\n  }\n\n  auto operator|=(Parser const& other) -> Parser& {\n    m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());\n    m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());\n    return *this;\n  }\n\n  template <typename T>\n  auto operator|(T const& other) const -> Parser {\n    return Parser(*this) |= other;\n  }\n\n  // Forward deprecated interface with '+' instead of '|'\n  template <typename T>\n  auto operator+=(T const& other) -> Parser& {\n    return operator|=(other);\n  }\n  template <typename T>\n  auto operator+(T const& other) const -> Parser {\n    return operator|(other);\n  }\n\n  auto getHelpColumns() const -> std::vector<HelpColumns> {\n    std::vector<HelpColumns> cols;\n    for (auto const& o : m_options) {\n      auto childCols = o.getHelpColumns();\n      cols.insert(cols.end(), childCols.begin(), childCols.end());\n    }\n    return cols;\n  }\n\n  void writeToStream(std::ostream& os) const {\n    if (!m_exeName.name().empty()) {\n      os << \"usage:\\n\"\n         << \"  \" << m_exeName.name() << \" \";\n      bool required = true, first = true;\n      for (auto const& arg : m_args) {\n        if (first)\n          first = false;\n        else\n          os << \" \";\n        if (arg.isOptional() && required) {\n          os << \"[\";\n          required = false;\n        }\n        os << \"<\" << arg.hint() << \">\";\n        if (arg.cardinality() == 0) os << \" ... \";\n      }\n      if (!required) os << \"]\";\n      if (!m_options.empty()) os << \" options\";\n      os << \"\\n\\nwhere options are:\" << std::endl;\n    }\n\n    auto rows = getHelpColumns();\n    size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;\n    size_t optWidth = 0;\n    for (auto const& cols : rows) optWidth = (std::max)(optWidth, cols.left.size() + 2);\n\n    optWidth = (std::min)(optWidth, consoleWidth / 2);\n\n    for (auto const& cols : rows) {\n      auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) +\n                 TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth);\n      os << row << std::endl;\n    }\n  }\n\n  friend auto operator<<(std::ostream& os, Parser const& parser) -> std::ostream& {\n    parser.writeToStream(os);\n    return os;\n  }\n\n  auto validate() const -> Result override {\n    for (auto const& opt : m_options) {\n      auto result = opt.validate();\n      if (!result) return result;\n    }\n    for (auto const& arg : m_args) {\n      auto result = arg.validate();\n      if (!result) return result;\n    }\n    return Result::ok();\n  }\n\n  using ParserBase::parse;\n\n  auto parse(std::string const& exeName, TokenStream const& tokens) const\n      -> InternalParseResult override {\n    struct ParserInfo {\n      ParserBase const* parser = nullptr;\n      size_t count = 0;\n    };\n    const size_t totalParsers = m_options.size() + m_args.size();\n    assert(totalParsers < 512);\n    // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do\n    ParserInfo parseInfos[512];\n\n    {\n      size_t i = 0;\n      for (auto const& opt : m_options) parseInfos[i++].parser = &opt;\n      for (auto const& arg : m_args) parseInfos[i++].parser = &arg;\n    }\n\n    m_exeName.set(exeName);\n\n    auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens));\n    while (result.value().remainingTokens()) {\n      bool tokenParsed = false;\n\n      for (size_t i = 0; i < totalParsers; ++i) {\n        auto& parseInfo = parseInfos[i];\n        if (parseInfo.parser->cardinality() == 0 ||\n            parseInfo.count < parseInfo.parser->cardinality()) {\n          result = parseInfo.parser->parse(exeName, result.value().remainingTokens());\n          if (!result) return result;\n          if (result.value().type() != ParseResultType::NoMatch) {\n            tokenParsed = true;\n            ++parseInfo.count;\n            break;\n          }\n        }\n      }\n\n      if (result.value().type() == ParseResultType::ShortCircuitAll) return result;\n      if (!tokenParsed)\n        return InternalParseResult::runtimeError(\"Unrecognised token: \" +\n                                                 result.value().remainingTokens()->token);\n    }\n    // !TBD Check missing required options\n    return result;\n  }\n};\n\ntemplate <typename DerivedT>\ntemplate <typename T>\nauto ComposableParserImpl<DerivedT>::operator|(T const& other) const -> Parser {\n  return Parser() | static_cast<DerivedT const&>(*this) | other;\n}\n}  // namespace detail\n\n// A Combined parser\nusing detail::Parser;\n\n// A parser for options\nusing detail::Opt;\n\n// A parser for arguments\nusing detail::Arg;\n\n// Wrapper for argc, argv from main()\nusing detail::Args;\n\n// Specifies the name of the executable\nusing detail::ExeName;\n\n// Convenience wrapper for option parser that specifies the help option\nusing detail::Help;\n\n// enum of result types from a parse\nusing detail::ParseResultType;\n\n// Result type for parser operation\nusing detail::ParserResult;\n\n}  // namespace clara\n\n#endif  // CLARA_HPP_INCLUDED\n"
  },
  {
    "path": "include/util/command.hpp",
    "content": "#pragma once\n\n#include <fcntl.h>\n#include <giomm.h>\n#include <spdlog/spdlog.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\n#ifdef __linux__\n#include <sys/prctl.h>\n#endif\n#ifdef __FreeBSD__\n#include <sys/procctl.h>\n#endif\n\n#include <array>\n\nextern std::mutex reap_mtx;\nextern std::list<pid_t> reap;\n\nnamespace waybar::util::command {\n\nconstexpr int kExecFailureExitCode = 127;\n\nstruct res {\n  int exit_code;\n  std::string out;\n};\n\ninline std::string read(FILE* fp) {\n  std::array<char, 128> buffer = {0};\n  std::string output;\n  while (feof(fp) == 0) {\n    if (fgets(buffer.data(), 128, fp) != nullptr) {\n      output += buffer.data();\n    }\n  }\n\n  // Remove last newline\n  if (!output.empty() && output[output.length() - 1] == '\\n') {\n    output.erase(output.length() - 1);\n  }\n  return output;\n}\n\ninline int close(FILE* fp, pid_t pid) {\n  int stat = -1;\n  pid_t ret;\n\n  fclose(fp);\n  do {\n    ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED);\n\n    if (WIFEXITED(stat)) {\n      spdlog::debug(\"Cmd exited with code {}\", WEXITSTATUS(stat));\n    } else if (WIFSIGNALED(stat)) {\n      spdlog::debug(\"Cmd killed by {}\", WTERMSIG(stat));\n    } else if (WIFSTOPPED(stat)) {\n      spdlog::debug(\"Cmd stopped by {}\", WSTOPSIG(stat));\n    } else if (WIFCONTINUED(stat)) {\n      spdlog::debug(\"Cmd continued\");\n    } else if (ret == -1) {\n      spdlog::debug(\"waitpid failed: {}\", strerror(errno));\n      break;\n    } else {\n      break;\n    }\n  } while (!WIFEXITED(stat) && !WIFSIGNALED(stat));\n  return stat;\n}\n\ninline FILE* open(const std::string& cmd, int& pid, const std::string& output_name) {\n  if (cmd == \"\") return nullptr;\n  int fd[2];\n  // Open the pipe with the close-on-exec flag set, so it will not be inherited\n  // by any other subprocesses launched by other threads (which could result in\n  // the pipe staying open after this child dies, causing us to hang when trying\n  // to read from it)\n  if (pipe2(fd, O_CLOEXEC) != 0) {\n    spdlog::error(\"Unable to pipe fd\");\n    return nullptr;\n  }\n\n  pid_t child_pid = fork();\n\n  if (child_pid < 0) {\n    spdlog::error(\"Unable to exec cmd {}, error {}\", cmd.c_str(), strerror(errno));\n    ::close(fd[0]);\n    ::close(fd[1]);\n    return nullptr;\n  }\n\n  if (!child_pid) {\n    int err;\n    sigset_t mask;\n    sigfillset(&mask);\n    // Reset sigmask\n    err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);\n    if (err != 0) spdlog::error(\"pthread_sigmask in open failed: {}\", strerror(err));\n    // Kill child if Waybar exits\n    int deathsig = SIGTERM;\n#ifdef __linux__\n    if (prctl(PR_SET_PDEATHSIG, deathsig) != 0) {\n      spdlog::error(\"prctl(PR_SET_PDEATHSIG) in open failed: {}\", strerror(errno));\n    }\n#endif\n#ifdef __FreeBSD__\n    if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, reinterpret_cast<void*>(&deathsig)) == -1) {\n      spdlog::error(\"procctl(PROC_PDEATHSIG_CTL) in open failed: {}\", strerror(errno));\n    }\n#endif\n    ::close(fd[0]);\n    dup2(fd[1], 1);\n    setpgid(child_pid, child_pid);\n    if (!output_name.empty()) {\n      setenv(\"WAYBAR_OUTPUT_NAME\", output_name.c_str(), 1);\n    }\n    execlp(\"/bin/sh\", \"sh\", \"-c\", cmd.c_str(), (char*)0);\n    const int saved_errno = errno;\n    spdlog::error(\"execlp(/bin/sh) failed in open: {}\", strerror(saved_errno));\n    _exit(kExecFailureExitCode);\n  } else {\n    ::close(fd[1]);\n  }\n  pid = child_pid;\n  return fdopen(fd[0], \"r\");\n}\n\ninline struct res exec(const std::string& cmd, const std::string& output_name) {\n  int pid;\n  auto fp = command::open(cmd, pid, output_name);\n  if (!fp) return {-1, \"\"};\n  auto output = command::read(fp);\n  auto stat = command::close(fp, pid);\n  return {WEXITSTATUS(stat), output};\n}\n\ninline struct res execNoRead(const std::string& cmd) {\n  int pid;\n  auto fp = command::open(cmd, pid, \"\");\n  if (!fp) return {-1, \"\"};\n  auto stat = command::close(fp, pid);\n  return {WEXITSTATUS(stat), \"\"};\n}\n\ninline int32_t forkExec(const std::string& cmd, const std::string& output_name) {\n  if (cmd == \"\") return -1;\n\n  pid_t pid = fork();\n\n  if (pid < 0) {\n    spdlog::error(\"Unable to exec cmd {}, error {}\", cmd.c_str(), strerror(errno));\n    return pid;\n  }\n\n  // Child executes the command\n  if (!pid) {\n    int err;\n    sigset_t mask;\n    sigfillset(&mask);\n    // Reset sigmask\n    err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);\n    if (err != 0) spdlog::error(\"pthread_sigmask in forkExec failed: {}\", strerror(err));\n    setpgid(pid, pid);\n    if (!output_name.empty()) {\n      setenv(\"WAYBAR_OUTPUT_NAME\", output_name.c_str(), 1);\n    }\n    execl(\"/bin/sh\", \"sh\", \"-c\", cmd.c_str(), (char*)0);\n    const int saved_errno = errno;\n    spdlog::error(\"execl(/bin/sh) failed in forkExec: {}\", strerror(saved_errno));\n    _exit(kExecFailureExitCode);\n  } else {\n    reap_mtx.lock();\n    reap.push_back(pid);\n    reap_mtx.unlock();\n    spdlog::debug(\"Added child to reap list: {}\", pid);\n  }\n\n  return pid;\n}\n\ninline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, \"\"); }\n\n}  // namespace waybar::util::command\n"
  },
  {
    "path": "include/util/css_reload_helper.hpp",
    "content": "#pragma once\n\n#include <functional>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"giomm/file.h\"\n#include \"giomm/filemonitor.h\"\n#include \"glibmm/refptr.h\"\n\nstruct pollfd;\n\nnamespace waybar {\nclass CssReloadHelper {\n public:\n  CssReloadHelper(std::string cssFile, std::function<void()> callback);\n\n  virtual ~CssReloadHelper() = default;\n\n  virtual void monitorChanges();\n\n protected:\n  std::vector<std::string> parseImports(const std::string& cssFile);\n\n  void parseImports(const std::string& cssFile, std::unordered_map<std::string, bool>& imports);\n\n  void watchFiles(const std::vector<std::string>& files);\n\n  bool handleInotifyEvents(int fd);\n\n  bool watch(int inotifyFd, pollfd* pollFd);\n\n  virtual std::string getFileContents(const std::string& filename);\n\n  virtual std::string findPath(const std::string& filename);\n\n  void handleFileChange(Glib::RefPtr<Gio::File> const& file,\n                        Glib::RefPtr<Gio::File> const& other_type,\n                        Gio::FileMonitorEvent event_type);\n\n private:\n  std::string m_cssFile;\n\n  std::function<void()> m_callback;\n\n  std::vector<std::tuple<Glib::RefPtr<Gio::FileMonitor>>> m_fileMonitors;\n};\n}  // namespace waybar\n"
  },
  {
    "path": "include/util/date.hpp",
    "content": "#pragma once\n\n#include <chrono>\n\n#if HAVE_CHRONO_TIMEZONES\n#include <format>\n#else\n#include <date/tz.h>\n#include <fmt/format.h>\n\n#include <regex>\n#endif\n\n// Date\nnamespace date {\n#if HAVE_CHRONO_TIMEZONES\nusing namespace std::chrono;\nusing std::format;\n#else\n\nusing system_clock = std::chrono::system_clock;\nusing seconds = std::chrono::seconds;\n\ntemplate <typename T>\ninline auto format(const char* spec, const T& arg) {\n  return date::format(std::regex_replace(spec, std::regex(\"\\\\{:L|\\\\}\"), \"\"), arg);\n}\n\ntemplate <typename T>\ninline auto format(const std::locale& loc, const char* spec, const T& arg) {\n  return date::format(loc, std::regex_replace(spec, std::regex(\"\\\\{:L|\\\\}\"), \"\"), arg);\n}\n#endif\n}  // namespace date\n\n// Format\nnamespace waybar::util::date::format {\n#if HAVE_CHRONO_TIMEZONES\nusing namespace std;\n#else\nusing namespace fmt;\n#endif\n}  // namespace waybar::util::date::format\n\n#if not HAVE_CHRONO_TIMEZONES\ntemplate <typename Duration, typename TimeZonePtr>\nstruct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {\n  std::string_view specs;\n\n  template <typename ParseContext>\n  constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {\n    auto it = ctx.begin();\n    if (it != ctx.end() && *it == ':') {\n      ++it;\n    }\n    auto end = it;\n    while (end != ctx.end() && *end != '}') {\n      ++end;\n    }\n    if (end != it) {\n      specs = {it, std::string_view::size_type(end - it)};\n    }\n    return end;\n  }\n\n  template <typename FormatContext>\n  auto format(const date::zoned_time<Duration, TimeZonePtr>& ztime, FormatContext& ctx) const {\n    if (ctx.locale()) {\n      const auto loc = ctx.locale().template get<std::locale>();\n      return fmt::format_to(ctx.out(), \"{}\", date::format(loc, fmt::to_string(specs), ztime));\n    }\n    return fmt::format_to(ctx.out(), \"{}\", date::format(fmt::to_string(specs), ztime));\n  }\n};\n#endif\n"
  },
  {
    "path": "include/util/enum.hpp",
    "content": "#pragma once\n\n#include <map>\n#include <stdexcept>\n#include <string>\n\nnamespace waybar::util {\n\ntemplate <typename EnumType>\nstruct EnumParser {\n public:\n  EnumParser();\n  ~EnumParser();\n\n  EnumType parseStringToEnum(const std::string& str,\n                             const std::map<std::string, EnumType>& enumMap);\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/format.hpp",
    "content": "#pragma once\n\n#include <fmt/format.h>\n#include <glibmm/ustring.h>\n\nclass pow_format {\n public:\n  pow_format(long long val, std::string&& unit, bool binary = false)\n      : val_(val), unit_(unit), binary_(binary) {};\n\n  long long val_;\n  std::string unit_;\n  bool binary_;\n};\n\nnamespace fmt {\ntemplate <>\nstruct formatter<pow_format> {\n  char spec = 0;\n  int width = 0;\n\n  template <typename ParseContext>\n  constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it != end && *it == ':') ++it;\n    if (it && (*it == '>' || *it == '<' || *it == '=')) {\n      spec = *it;\n      ++it;\n    }\n    if (it == end || *it == '}') return it;\n    if ('0' <= *it && *it <= '9') {\n      // We ignore it for now, but keep it for compatibility with\n      // existing configs where the format for pow_format'ed numbers was\n      // 'string' and specifications such as {:>9} were valid.\n      // The rationale for ignoring it is that the only reason to specify\n      // an alignment and a with is to get a fixed width bar, and \">\" is\n      // sufficient in this implementation.\n#if FMT_VERSION < 80000\n      width = parse_nonnegative_int(it, end, ctx);\n#else\n      width = detail::parse_nonnegative_int(it, end, -1);\n#endif\n    }\n    return it;\n  }\n\n  template <class FormatContext>\n  auto format(const pow_format& s, FormatContext& ctx) const -> decltype(ctx.out()) {\n    const char* units[] = {\"\", \"k\", \"M\", \"G\", \"T\", \"P\", nullptr};\n\n    auto base = s.binary_ ? 1024ull : 1000ll;\n    auto fraction = (double)s.val_;\n\n    int pow;\n    for (pow = 0; units[pow + 1] != nullptr && fraction / base >= 1; ++pow) {\n      fraction /= base;\n    }\n\n    auto number_width = 5              // coeff in {:.1f} format\n                        + s.binary_;   // potential 4th digit before the decimal point\n    auto max_width = number_width + 1  // prefix from units array\n                     + s.binary_       // for the 'i' in GiB.\n                     + s.unit_.length();\n\n    const char* format;\n    std::string string;\n    switch (spec) {\n      case '>':\n        return fmt::format_to(ctx.out(), \"{:>{}}\", fmt::format(\"{}\", s), max_width);\n      case '<':\n        return fmt::format_to(ctx.out(), \"{:<{}}\", fmt::format(\"{}\", s), max_width);\n      case '=':\n        format = \"{coefficient:<{number_width}.1f}{padding}{prefix}{unit}\";\n        break;\n      case 0:\n      default:\n        format = \"{coefficient:.1f}{prefix}{unit}\";\n        break;\n    }\n    return fmt::format_to(\n        ctx.out(), fmt::runtime(format), fmt::arg(\"coefficient\", fraction),\n        fmt::arg(\"number_width\", number_width),\n        fmt::arg(\"prefix\", std::string() + units[pow] + ((s.binary_ && pow) ? \"i\" : \"\")),\n        fmt::arg(\"unit\", s.unit_),\n        fmt::arg(\"padding\", pow         ? \"\"\n                            : s.binary_ ? \"  \"\n                                        : \" \"));\n  }\n};\n\n// Glib ustirng support\ntemplate <>\nstruct formatter<Glib::ustring> : formatter<std::string> {\n  template <typename FormatContext>\n  auto format(const Glib::ustring& value, FormatContext& ctx) const {\n    return formatter<std::string>::format(static_cast<std::string>(value), ctx);\n  }\n};\n}  // namespace fmt\n"
  },
  {
    "path": "include/util/gtk_icon.hpp",
    "content": "#pragma once\n#include <gtkmm/icontheme.h>\n\n#include <mutex>\n#include <string>\n\nclass DefaultGtkIconThemeWrapper {\n private:\n  static std::mutex default_theme_mutex;\n\n public:\n  static bool has_icon(const std::string&);\n  static Glib::RefPtr<Gdk::Pixbuf> load_icon(\n      const char*, int, Gtk::IconLookupFlags,\n      Glib::RefPtr<Gtk::StyleContext> style = Glib::RefPtr<Gtk::StyleContext>());\n};\n"
  },
  {
    "path": "include/util/icon_loader.hpp",
    "content": "#pragma once\n\n#include <gdkmm/general.h>\n#include <gio/gdesktopappinfo.h>\n#include <giomm/desktopappinfo.h>\n#include <glibmm/fileutils.h>\n#include <gtkmm/image.h>\n#include <spdlog/spdlog.h>\n\n#include <string>\n#include <vector>\n\n#include \"util/gtk_icon.hpp\"\n\nclass IconLoader {\n private:\n  std::vector<Glib::RefPtr<Gtk::IconTheme>> custom_icon_themes_;\n  Glib::RefPtr<Gtk::IconTheme> default_icon_theme_ = Gtk::IconTheme::get_default();\n  static std::vector<std::string> search_prefix();\n  static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id);\n  static Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string& app_id);\n  static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string const& icon_path, int size);\n  static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,\n                                                   const std::string& app_id);\n  static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,\n                              Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);\n\n public:\n  void add_custom_icon_theme(const std::string& theme_name);\n  bool image_load_icon(Gtk::Image& image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,\n                       int size) const;\n  static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_from_app_id_list(\n      const std::string& app_id_list);\n};\n"
  },
  {
    "path": "include/util/json.hpp",
    "content": "#pragma once\n\n#include <fmt/ostream.h>\n#include <json/json.h>\n\n#include <algorithm>\n#include <codecvt>\n#include <iostream>\n#include <locale>\n#include <regex>\n\n#if (FMT_VERSION >= 90000)\n\ntemplate <>\nstruct fmt::formatter<Json::Value> : ostream_formatter {};\n\n#endif\n\nnamespace waybar::util {\n\nclass JsonParser {\n public:\n  JsonParser() = default;\n\n  Json::Value parse(const std::string& jsonStr) {\n    Json::Value root;\n\n    // replace all occurrences of \"\\x\" with \"\\u00\", because JSON doesn't allow \"\\x\" escape sequences\n    std::string modifiedJsonStr = replaceHexadecimalEscape(jsonStr);\n\n    std::istringstream jsonStream(modifiedJsonStr);\n    std::string errs;\n    // Use local CharReaderBuilder for thread safety - the IPC singleton's\n    // parser can be called concurrently from multiple module threads\n    Json::CharReaderBuilder readerBuilder;\n    if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) {\n      throw std::runtime_error(\"Error parsing JSON: \" + errs);\n    }\n    return root;\n  }\n\n private:\n  static std::string replaceHexadecimalEscape(const std::string& str) {\n    static std::regex re(\"\\\\\\\\x\");\n    return std::regex_replace(str, re, \"\\\\u00\");\n  }\n};\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/kill_signal.hpp",
    "content": "#pragma once\n\n#include <json/value.h>\n\n#include <cstdint>\n\nnamespace waybar::util {\n\nenum class KillSignalAction : std::uint8_t {\n  TOGGLE,\n  RELOAD,\n  SHOW,\n  HIDE,\n  NOOP,\n};\nconst std::map<std::string, KillSignalAction> userKillSignalActions = {\n    {\"TOGGLE\", KillSignalAction::TOGGLE},\n    {\"RELOAD\", KillSignalAction::RELOAD},\n    {\"SHOW\", KillSignalAction::SHOW},\n    {\"HIDE\", KillSignalAction::HIDE},\n    {\"NOOP\", KillSignalAction::NOOP}};\n\nconst KillSignalAction SIGNALACTION_DEFAULT_SIGUSR1 = KillSignalAction::TOGGLE;\nconst KillSignalAction SIGNALACTION_DEFAULT_SIGUSR2 = KillSignalAction::RELOAD;\n\n};  // namespace waybar::util\n"
  },
  {
    "path": "include/util/pipewire/pipewire_backend.hpp",
    "content": "#pragma once\n\n#include <pipewire/pipewire.h>\n\n#include <unordered_map>\n\n#include \"util/backend_common.hpp\"\n#include \"util/pipewire/privacy_node_info.hpp\"\n\nnamespace waybar::util::PipewireBackend {\n\nclass PipewireBackend {\n private:\n  pw_thread_loop* mainloop_;\n  pw_context* context_;\n  pw_core* core_;\n\n  pw_registry* registry_;\n  spa_hook registryListener_;\n\n  /* Hack to keep constructor inaccessible but still public.\n   * This is required to be able to use std::make_shared.\n   * It is important to keep this class only accessible via a reference-counted\n   * pointer because the destructor will manually free memory, and this could be\n   * a problem with C++20's copy and move semantics.\n   */\n  struct PrivateConstructorTag {};\n\n public:\n  sigc::signal<void> privacy_nodes_changed_signal_event;\n\n  std::unordered_map<uint32_t, PrivacyNodeInfo*> privacy_nodes;\n  std::mutex mutex_;\n\n  static std::shared_ptr<PipewireBackend> getInstance();\n\n  // Handlers for PipeWire events\n  void handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char* type,\n                                 uint32_t version, const struct spa_dict* props);\n  void handleRegistryEventGlobalRemove(uint32_t id);\n\n  PipewireBackend(PrivateConstructorTag tag);\n  ~PipewireBackend();\n};\n}  // namespace waybar::util::PipewireBackend\n"
  },
  {
    "path": "include/util/pipewire/privacy_node_info.hpp",
    "content": "#pragma once\n\n#include <pipewire/pipewire.h>\n\n#include <string>\n\n#include \"util/gtk_icon.hpp\"\n\nnamespace waybar::util::PipewireBackend {\n\nenum PrivacyNodeType {\n  PRIVACY_NODE_TYPE_NONE,\n  PRIVACY_NODE_TYPE_VIDEO_INPUT,\n  PRIVACY_NODE_TYPE_AUDIO_INPUT,\n  PRIVACY_NODE_TYPE_AUDIO_OUTPUT\n};\n\nclass PrivacyNodeInfo {\n public:\n  PrivacyNodeType type = PRIVACY_NODE_TYPE_NONE;\n  uint32_t id;\n  uint32_t client_id;\n  enum pw_node_state state = PW_NODE_STATE_IDLE;\n  std::string media_class;\n  std::string media_name;\n  std::string node_name;\n  std::string application_name;\n  bool is_monitor = false;\n\n  std::string pipewire_access_portal_app_id;\n  std::string application_icon_name;\n\n  struct spa_hook object_listener;\n  struct spa_hook proxy_listener;\n\n  void* data;\n\n  std::string getName();\n  std::string getIconName();\n\n  // Handlers for PipeWire events\n  void handleProxyEventDestroy();\n  void handleNodeEventInfo(const struct pw_node_info* info);\n};\n\n}  // namespace waybar::util::PipewireBackend\n"
  },
  {
    "path": "include/util/portal.hpp",
    "content": "#include <giomm/dbusproxy.h>\n\n#include <string>\n\n#include \"fmt/format.h\"\n\nnamespace waybar {\n\nenum class Appearance {\n  UNKNOWN = 0,\n  DARK = 1,\n  LIGHT = 2,\n};\nclass Portal : private Gio::DBus::Proxy {\n public:\n  Portal();\n  void refreshAppearance();\n  Appearance getAppearance();\n\n  typedef sigc::signal<void, Appearance> type_signal_appearance_changed;\n  type_signal_appearance_changed signal_appearance_changed() { return m_signal_appearance_changed; }\n\n private:\n  type_signal_appearance_changed m_signal_appearance_changed;\n  Appearance currentMode;\n  void on_signal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                 const Glib::VariantContainerBase& parameters);\n};\n\n}  // namespace waybar\n\ntemplate <>\nstruct fmt::formatter<waybar::Appearance> : formatter<fmt::string_view> {\n  // parse is inherited from formatter<string_view>.\n  auto format(waybar::Appearance c, format_context& ctx) const;\n};\n"
  },
  {
    "path": "include/util/prepare_for_sleep.h",
    "content": "#pragma once\n\n#include \"SafeSignal.hpp\"\n\nnamespace waybar::util {\n\n// Get a signal emitted with value true when entering sleep, and false when exiting\nSafeSignal<bool>& prepare_for_sleep();\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/regex_collection.hpp",
    "content": "#pragma once\n\n#include <json/json.h>\n\n#include <functional>\n#include <regex>\n#include <string>\n#include <utility>\n\nnamespace waybar::util {\n\nstruct Rule {\n  std::regex rule;\n  std::string repr;\n  int priority;\n\n  // Fix for Clang < 16\n  // See https://en.cppreference.com/w/cpp/compiler_support/20 \"Parenthesized initialization of\n  // aggregates\"\n  Rule(std::regex rule, std::string repr, int priority)\n      : rule(std::move(rule)), repr(std::move(repr)), priority(priority) {}\n};\n\nint default_priority_function(std::string& key);\n\n/* A collection of regexes and strings, with a default string to return if no regexes.\n * When a regex is matched, the corresponding string is returned.\n * All regexes that are matched are cached, so that the regexes are only\n * evaluated once against a given string.\n * Regexes may be given a higher priority than others, so that they are matched\n * first. The priority function is given the regex string, and should return a\n * higher number for higher priority regexes.\n */\nclass RegexCollection {\n private:\n  std::vector<Rule> rules;\n  std::map<std::string, std::string> regex_cache;\n  std::string default_repr;\n\n  std::string find_match(std::string& value, bool& matched_any);\n\n public:\n  RegexCollection() = default;\n  RegexCollection(\n      const Json::Value& map, std::string default_repr = \"\",\n      const std::function<int(std::string&)>& priority_function = default_priority_function);\n  ~RegexCollection() = default;\n\n  std::string& get(std::string& value, bool& matched_any);\n  std::string& get(std::string& value);\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/rewrite_string.hpp",
    "content": "#pragma once\n#include <json/json.h>\n\n#include <string>\n\nnamespace waybar::util {\nstd::string rewriteString(const std::string&, const Json::Value&);\nstd::string rewriteStringOnce(const std::string& value, const Json::Value& rules,\n                              bool& matched_any);\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/rfkill.hpp",
    "content": "#pragma once\n\n#include <glibmm/iochannel.h>\n#include <linux/rfkill.h>\n#include <sigc++/signal.h>\n#include <sigc++/trackable.h>\n\n#include <atomic>\n\nnamespace waybar::util {\n\nclass Rfkill : public sigc::trackable {\n public:\n  Rfkill(enum rfkill_type rfkill_type);\n  ~Rfkill();\n  bool getState() const;\n\n  sigc::signal<void(struct rfkill_event&)> on_update;\n\n private:\n  enum rfkill_type rfkill_type_;\n  std::atomic_bool state_ = false;\n  int fd_ = -1;\n\n  bool on_event(Glib::IOCondition cond);\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/sanitize_str.hpp",
    "content": "#pragma once\n#include <string>\n\nnamespace waybar::util {\nstd::string sanitize_string(std::string str);\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/scope_guard.hpp",
    "content": "#pragma once\n\n#include <utility>\n\nnamespace waybar::util {\n\ntemplate <typename Func>\nclass ScopeGuard {\n public:\n  explicit ScopeGuard(Func&& exit_function) : f{std::forward<Func>(exit_function)} {}\n  ScopeGuard(const ScopeGuard&) = delete;\n  ScopeGuard(ScopeGuard&&) = default;\n  ScopeGuard& operator=(const ScopeGuard&) = delete;\n  ScopeGuard& operator=(ScopeGuard&&) = default;\n  ~ScopeGuard() { f(); }\n\n private:\n  Func f;\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/scoped_fd.hpp",
    "content": "#pragma once\n\n#include <unistd.h>\n\nnamespace waybar::util {\n\nclass ScopedFd {\n public:\n  explicit ScopedFd(int fd = -1) : fd_(fd) {}\n  ~ScopedFd() {\n    if (fd_ != -1) {\n      close(fd_);\n    }\n  }\n\n  // ScopedFd is non-copyable\n  ScopedFd(const ScopedFd&) = delete;\n  ScopedFd& operator=(const ScopedFd&) = delete;\n\n  // ScopedFd is moveable\n  ScopedFd(ScopedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }\n  ScopedFd& operator=(ScopedFd&& other) noexcept {\n    if (this != &other) {\n      if (fd_ != -1) {\n        close(fd_);\n      }\n      fd_ = other.fd_;\n      other.fd_ = -1;\n    }\n    return *this;\n  }\n\n  int get() const { return fd_; }\n\n  operator int() const { return fd_; }\n\n  void reset(int fd = -1) {\n    if (fd_ != -1) {\n      close(fd_);\n    }\n    fd_ = fd;\n  }\n\n  int release() {\n    int fd = fd_;\n    fd_ = -1;\n    return fd;\n  }\n\n private:\n  int fd_;\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/sleeper_thread.hpp",
    "content": "#pragma once\n\n#include <atomic>\n#include <chrono>\n#include <condition_variable>\n#include <ctime>\n#include <functional>\n#include <thread>\n\n#include \"prepare_for_sleep.h\"\n\nnamespace waybar::util {\n\n/**\n * Defer pthread_cancel until the end of a current scope.\n *\n * Required to protect a scope where it's unsafe to raise `__forced_unwind` exception.\n * An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such\n * a method may result in a `std::terminate` call.\n */\nclass CancellationGuard {\n  int oldstate;\n\n public:\n  CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); }\n  ~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); }\n};\n\nclass SleeperThread {\n public:\n  SleeperThread() = default;\n\n  SleeperThread(std::function<void()> func)\n      : thread_{[this, func] {\n          while (do_run_.load(std::memory_order_relaxed)) {\n            signal_.store(false, std::memory_order_relaxed);\n            func();\n          }\n        }} {\n    connection_ = prepare_for_sleep().connect([this](bool sleep) {\n      if (not sleep) wake_up();\n    });\n  }\n\n  SleeperThread& operator=(std::function<void()> func) {\n    if (thread_.joinable()) {\n      stop();\n      thread_.join();\n    }\n    {\n      std::lock_guard<std::mutex> lck(mutex_);\n      do_run_.store(true, std::memory_order_relaxed);\n      signal_.store(false, std::memory_order_relaxed);\n    }\n    thread_ = std::thread([this, func] {\n      while (do_run_.load(std::memory_order_relaxed)) {\n        signal_.store(false, std::memory_order_relaxed);\n        func();\n      }\n    });\n    if (connection_.empty()) {\n      connection_ = prepare_for_sleep().connect([this](bool sleep) {\n        if (not sleep) wake_up();\n      });\n    }\n    return *this;\n  }\n\n  bool isRunning() const { return do_run_.load(std::memory_order_relaxed); }\n\n  auto sleep() {\n    std::unique_lock lk(mutex_);\n    CancellationGuard cancel_lock;\n    return condvar_.wait(lk, [this] {\n      return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);\n    });\n  }\n\n  auto sleep_for(std::chrono::system_clock::duration dur) {\n    std::unique_lock lk(mutex_);\n    CancellationGuard cancel_lock;\n    constexpr auto max_time_point = std::chrono::steady_clock::time_point::max();\n    auto wait_end = max_time_point;\n    auto now = std::chrono::steady_clock::now();\n    if (now < max_time_point - dur) {\n      wait_end = now + dur;\n    }\n    return condvar_.wait_until(lk, wait_end, [this] {\n      return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);\n    });\n  }\n\n  auto sleep_until(\n      std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>\n          time_point) {\n    std::unique_lock lk(mutex_);\n    CancellationGuard cancel_lock;\n    return condvar_.wait_until(lk, time_point, [this] {\n      return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed);\n    });\n  }\n\n  void wake_up() {\n    {\n      std::lock_guard<std::mutex> lck(mutex_);\n      signal_.store(true, std::memory_order_relaxed);\n    }\n    condvar_.notify_all();\n  }\n\n  void stop() {\n    {\n      std::lock_guard<std::mutex> lck(mutex_);\n      signal_.store(true, std::memory_order_relaxed);\n      do_run_.store(false, std::memory_order_relaxed);\n    }\n    condvar_.notify_all();\n    auto handle = thread_.native_handle();\n    if (handle != 0) {\n      // TODO: find a proper way to terminate thread...\n      pthread_cancel(handle);\n    }\n  }\n\n  ~SleeperThread() {\n    connection_.disconnect();\n    stop();\n    if (thread_.joinable()) {\n      thread_.join();\n    }\n  }\n\n private:\n  std::thread thread_;\n  std::condition_variable condvar_;\n  std::mutex mutex_;\n  std::atomic<bool> do_run_ = true;\n  std::atomic<bool> signal_ = false;\n  sigc::connection connection_;\n};\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "include/util/string.hpp",
    "content": "#pragma once\n\n#include <iostream>\n#include <string>\n\nconst std::string WHITESPACE = \" \\n\\r\\t\\f\\v\";\n\ninline std::string ltrim(const std::string& s) {\n  size_t begin = s.find_first_not_of(WHITESPACE);\n  return (begin == std::string::npos) ? \"\" : s.substr(begin);\n}\n\ninline std::string rtrim(const std::string& s) {\n  size_t end = s.find_last_not_of(WHITESPACE);\n  return (end == std::string::npos) ? \"\" : s.substr(0, end + 1);\n}\n\ninline std::string trim(const std::string& s) { return rtrim(ltrim(s)); }\n\ninline std::string capitalize(const std::string& str) {\n  std::string result = str;\n  std::transform(result.begin(), result.end(), result.begin(),\n                 [](unsigned char c) { return std::toupper(c); });\n  return result;\n}\n\ninline std::string toLower(const std::string& str) {\n  std::string result = str;\n  std::transform(result.begin(), result.end(), result.begin(),\n                 [](unsigned char c) { return std::tolower(c); });\n  return result;\n}\n\ninline std::vector<std::string> split(std::string_view s, std::string_view delimiter,\n                                      int max_splits = -1) {\n  std::vector<std::string> result;\n  size_t pos = 0;\n  size_t next_pos = 0;\n  while ((next_pos = s.find(delimiter, pos)) != std::string::npos) {\n    result.push_back(std::string(s.substr(pos, next_pos - pos)));\n    pos = next_pos + delimiter.size();\n    if (max_splits > 0 && result.size() == static_cast<size_t>(max_splits)) {\n      break;\n    }\n  }\n  result.push_back(std::string(s.substr(pos)));\n  return result;\n}\n"
  },
  {
    "path": "include/util/udev_deleter.hpp",
    "content": "#pragma once\n\n#include <libudev.h>\n\nnamespace waybar::util {\nstruct UdevDeleter {\n  void operator()(udev* ptr) const { udev_unref(ptr); }\n};\n\nstruct UdevDeviceDeleter {\n  void operator()(udev_device* ptr) const { udev_device_unref(ptr); }\n};\n\nstruct UdevEnumerateDeleter {\n  void operator()(udev_enumerate* ptr) const { udev_enumerate_unref(ptr); }\n};\n\nstruct UdevMonitorDeleter {\n  void operator()(udev_monitor* ptr) const { udev_monitor_unref(ptr); }\n};\n}  // namespace waybar::util"
  },
  {
    "path": "include/util/ustring_clen.hpp",
    "content": "#pragma once\n#include <glibmm/ustring.h>\n\n// calculate column width of ustring\nint ustring_clen(const Glib::ustring& str);"
  },
  {
    "path": "man/waybar-backlight-slider.5.scd",
    "content": "waybar-backlight-slider(5)\n\n# NAME\n\nwaybar - backlight slider module\n\n# DESCRIPTION\n\nThe *backlight slider* module displays and controls the current brightness of the default or preferred device.\n\nThe brightness can be controlled by dragging the slider across the bar or clicking on a specific position.\n\n# CONFIGURATION\n\n*min*: ++\n    typeof: int ++\n    default: 0 ++\n    The minimum volume value the slider should display and set.\n\n*max*: ++\n    typeof: int ++\n    default: 100 ++\n    The maximum volume value the slider should display and set.\n\n*orientation*: ++\n    typeof: string ++\n    default: horizontal ++\n    The orientation of the slider. Can be either `horizontal` or `vertical`.\n\n*device*: ++\n    typeof: string ++\n    The name of the preferred device to control. If left empty, a device will be chosen automatically.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"modules-right\": [\n    \"backlight/slider\",\n],\n\"backlight/slider\": {\n    \"min\": 0,\n    \"max\": 100,\n    \"orientation\": \"horizontal\",\n    \"device\": \"intel_backlight\"\n}\n```\n\n# STYLE\n\nThe slider is a component with multiple CSS Nodes, of which the following are exposed:\n\n*#backlight-slider*: ++\n    Controls the style of the box *around* the slider and bar.\n\n*#backlight-slider slider*: ++\n    Controls the style of the slider handle.\n\n*#backlight-slider trough*: ++\n    Controls the style of the part of the bar that has not been filled.\n\n*#backlight-slider highlight*: ++\n    Controls the style of the part of the bar that has been filled.\n\n## STYLE EXAMPLE\n\n```\n#backlight-slider slider {\n    min-height: 0px;\n    min-width: 0px;\n    opacity: 0;\n    background-image: none;\n    border: none;\n    box-shadow: none;\n}\n\n#backlight-slider trough {\n    min-height: 80px;\n    min-width: 10px;\n    border-radius: 5px;\n    background-color: black;\n}\n\n#backlight-slider highlight {\n    min-width: 10px;\n    border-radius: 5px;\n    background-color: red;\n}\n```\n"
  },
  {
    "path": "man/waybar-backlight.5.scd",
    "content": "waybar-backlight(5)\n\n# NAME\n\nwaybar - backlight module\n\n# DESCRIPTION\n\nThe *backlight* module displays the current backlight level.\n\n# CONFIGURATION\n\n*interval*: ++\n\ttypeof: integer ++\n\tdefault: 2 ++\n\tThe interval in which information gets polled.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {percent}% ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in characters the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of backlight states which get activated on certain brightness levels. See *waybar-states(5)*.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is clicked.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mouse scroll wheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is right-clicked.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when performing a scroll up on the module. This replaces the default behaviour of brightness control.\n\n*on-scroll-down*: ++\n\ttypeof: string\n\tCommand to execute when performing a scroll down on the module. This replaces the default behaviour of brightness control.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double\n\tThreshold to be used when scrolling.\n\n*reverse-scrolling*: ++\n\ttypeof: bool ++\n\tOption to reverse the scroll direction.\n\n*scroll-step*: ++\n\ttypeof: float ++\n\tdefault: 1.0 ++\n\tThe speed at which to change the brightness when scrolling.\n\n*min-brightness*: ++\n\ttypeof: double ++\n\tdefault: 0.0 ++\n\tThe minimum brightness of the backlight.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLE:\n\n```\n\"backlight\": {\n\t\"device\": \"intel_backlight\",\n\t\"format\": \"{percent}% {icon}\",\n\t\"format-icons\": [\"\", \"\"]\n}\n```\n\n# STYLE\n\n- *#backlight*\n"
  },
  {
    "path": "man/waybar-battery.5.scd",
    "content": "waybar-battery(5)\n\n# NAME\n\nwaybar - battery module\n\n# DESCRIPTION\n\nThe *battery* module displays the current capacity and state (eg. charging) of your battery.\n\n# CONFIGURATION\n\n*bat*: ++\n\ttypeof: string ++\n\tThe battery to monitor, as in /sys/class/power_supply/ instead of auto detect.\n\n*adapter*: ++\n\ttypeof: string ++\n\tThe adapter to monitor, as in /sys/class/power_supply/ instead of auto detect.\n\n*full-at*: ++\n\ttypeof: integer ++\n\tDefine the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%.\n\n*design-capacity*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to use the battery design capacity instead of its current maximal capacity.\n\n*interval*: ++\n\ttypeof: integer ++\n\tdefault: 60 ++\n\tThe interval in which the information gets polled.\n\n*states*: ++\n\ttypeof: object ++\n\tA number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {capacity}% ++\n\tThe format, how information should be displayed.\n\n*format-time*: ++\n\ttypeof: string ++\n\tdefault: {H} h {M} min ++\n\tThe format, how the time should be displayed.\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current capacity, the corresponding icon gets selected. ++\n\tThe order is *low* to *high*. Or by the state if it is an object.\n\n*max-length*: ++\n\ttypeof: integer++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*rotate*: ++\n\ttypeof: integer++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*weighted-average*: ++\n    typeof: bool ++\n    default: true ++\n    Option to combine multiple batteries with different capacities.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*bat-compatibility*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to enable battery compatibility if not detected.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*.\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n*events*: ++\n\ttypeof: object ++\n\tSpecifies commands to be executed on specific battery states. See *EVENTS* section below.\n\n# FORMAT REPLACEMENTS\n\n*{capacity}*: Capacity in percentage\n\n*{power}*: Power in watts\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{time}*: Estimate of time until full or empty. Note that this is based on the power draw at the last refresh time, not an average.\n\n*{cycles}*: Amount of charge cycles the highest-capacity battery has seen. *(Linux only)*\n\n*{health}*: The percentage of the highest-capacity battery's original maximum charge it can still hold.\n\n# TIME FORMAT\n\nThe *battery* module allows you to define how time should be formatted via *format-time*.\n\nThe three arguments are:\n*{H}*: Hours\n*{M}*: Minutes\n*{m}*: Zero-padded minutes\n\n# CUSTOM FORMATS\n\nThe *battery* module allows one to define custom formats based on up to two factors. The best-fitting format will be selected.\n\n*format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery.\n\n*format-<status>*: With the status, a custom format can be set depending on the status in /sys/class/power_supply/<bat>/status (in lowercase).\n\n*format-<status>-<state>*: You can also set a custom format depending on both values.\n\n# STATES\n\n- Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).\n- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.\tEach class gets activated when the current capacity is equal to or below the configured *<value>*.\n- Also each state can have its own *format*. Those can be configured via *format-<name>*. Or if you want to differentiate a bit more even as *format-<status>-<state>*. For more information see *custom-formats*.\n\n# EVENTS\n\nEvery entry in the *events* object consists of a *<event-name>* (typeof: *string*) and a *<command>* (typeof: *string*). ++\n*<event-name>* can be in one of the following formats:\n\n- *on-<status>-<state>*\n- *on-<status>-<capacity>*\n- *on-<status>*\n\nWhere:\n\n- *<status>* is either *charging* or *discharging*,\n- *<state>* is the name of one of the states specified in the *states* object,\n- *<capacity>* is a battery level value (between *0-100*).\n\n\n# EXAMPLES\n\n```\n\"battery\": {\n\t\"bat\": \"BAT2\",\n\t\"interval\": 60,\n\t\"states\": {\n\t\t\"warning\": 30,\n\t\t\"critical\": 15\n\t},\n\t\"events\": {\n\t\t\"on-discharging-warning\": \"notify-send -u normal 'Low Battery'\",\n\t\t\"on-discharging-critical\": \"notify-send -u critical 'Very Low Battery'\",\n\t\t\"on-charging-100\": \"notify-send -u normal 'Battery Full!'\",\n\t\t\"on-discharging\": \"notify-send -u normal 'Power Switch' Discharging\",\n\t\t\"on-charging\": \"notify-send -u normal 'Power Switch' Charging'\"\n\t},\n\t\"format\": \"{capacity}% {icon}\",\n\t\"format-icons\": [\"\", \"\", \"\", \"\", \"\"],\n\t\"max-length\": 25\n}\n```\n\n# STYLE\n\n- *#battery*\n- *#battery.<status>*\n\t- *<status>* is the value of /sys/class/power_supply/<bat>/status in lowercase.\n- *#battery.<state>*\n\t- *<state>* can be defined in the *config*. For more information see *states*.\n- *#battery.<status>.<state>*\n\t- Combination of both *<status>* and *<state>*.\n\nThe following classes are applied to the entire Waybar rather than just the\nbattery widget:\n\n- *window#waybar.battery-<state>*\n\t- *<state>* can be defined in the *config*, as previously mentioned.\n\n"
  },
  {
    "path": "man/waybar-bluetooth.5.scd",
    "content": "waybar-bluetooth(5)\n\n# NAME\n\nwaybar - bluetooth module\n\n# DESCRIPTION\n\nThe *bluetooth* module displays information about a bluetooth controller and its connections.\n\n# CONFIGURATION\n\nAddressed by *bluetooth*\n\n*controller*: ++\n\ttypeof: string ++\n\tUse the controller with the defined alias. Otherwise, a random controller is used. Recommended to define when there is more than 1 controller available to the system.\n\n*format-device-preference*: ++\n\ttypeof: array ++\n\tA ranking of bluetooth devices, addressed by their alias. The order is from *first displayed* to *last displayed*. ++\n\tIf this config option is not defined or none of the devices in the list are connected, it will fall back to showing the last connected device.\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: * {status}* ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-disabled*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is disabled.\n\n*format-off*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is turned off.\n\n*format-on*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is turned on with no devices connected.\n\n*format-connected*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is connected to at least 1 device.\n\n*format-no-controller*: ++\n\ttypeof: string ++\n\tThis format is used when no bluetooth controller can be found\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current battery percentage (see section *EXPERIMENTAL BATTERY PERCENTAGE FEATURE*), the corresponding icon gets selected. ++\n\tThe order is *low* to *high*. Will only show the current battery percentage icon in the *\\*-connected-battery* config options. ++\n\tOr by the state if it is an object. It will fall back to the enabled state if its derivatives are not defined (on, off, connected).\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tThe format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.\n\n*tooltip-format-disabled*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is disabled.\n\n*tooltip-format-off*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is turned off.\n\n*tooltip-format-on*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is turned on with no devices connected.\n\n*tooltip-format-connected*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed controller is connected to at least 1 device.\n\n*tooltip-format-no-controller*: ++\n\ttypeof: string ++\n\tThis format is used when no bluetooth controller can be found\n\n*tooltip-format-enumerate-connected*: ++\n\ttypeof: string ++\n\tThis format is used to define how each connected device should be displayed within the *device_enumerate* format replacement in the tooltip menu.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{status}*: Status of the bluetooth device.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{num_connections}*: Number of connections the displayed controller has.\n\n*{controller_address}*: Address of the displayed controller.\n\n*{controller_address_type}*: Address type of the displayed controller.\n\n*{controller_alias}*: Alias of the displayed controller.\n\n*{device_address}*: Address of the displayed device.\n\n*{device_address_type}*: Address type of the displayed device.\n\n*{device_alias}*: Alias of the displayed device.\n\n*{device_enumerate}*: Show a list of all connected devices, each on a separate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++\nand/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip-related format options.\n\n# EXPERIMENTAL BATTERY PERCENTAGE FEATURE\n\nAt the time of writing, the experimental features of BlueZ need to be turned on, for the battery percentage options listed below to work.\n\n## FORMAT REPLACEMENT\n\n*{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below.\n\n## CONFIGURATION\n\n*format-connected-battery*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed device provides its battery percentage.\n\n*tooltip-format-connected-battery*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed device provides its battery percentage.\n\n*tooltip-format-enumerate-connected-battery*: ++\n\ttypeof: string ++\n\tThis format is used to define how each connected device with a battery should be displayed within the *device_enumerate* format replacement option. ++\n\tWhen this config option is not defined, it will fall back on the *tooltip-format-enumerate-connected* config option.\n\n# EXAMPLES\n\n```\n\"bluetooth\": {\n\t// \"controller\": \"controller1\", // specify the alias of the controller if there are more than 1 on the system\n\t\"format\": \" {status}\",\n\t\"format-disabled\": \"\", // an empty format will hide the module\n\t\"format-connected\": \" {num_connections} connected\",\n\t\"tooltip-format\": \"{controller_alias}\\\\t{controller_address}\",\n\t\"tooltip-format-connected\": \"{controller_alias}\\\\t{controller_address}\\\\n\\\\n{device_enumerate}\",\n\t\"tooltip-format-enumerate-connected\": \"{device_alias}\\\\t{device_address}\"\n}\n```\n\n```\n\"bluetooth\": {\n\t\"format\": \" {status}\",\n\t\"format-connected\": \" {device_alias}\",\n\t\"format-connected-battery\": \" {device_alias} {device_battery_percentage}%\",\n\t// \"format-device-preference\": [ \"device1\", \"device2\" ], // preference list deciding the displayed device\n\t\"tooltip-format\": \"{controller_alias}\\\\t{controller_address}\\\\n\\\\n{num_connections} connected\",\n\t\"tooltip-format-connected\": \"{controller_alias}\\\\t{controller_address}\\\\n\\\\n{num_connections} connected\\\\n\\\\n{device_enumerate}\",\n\t\"tooltip-format-enumerate-connected\": \"{device_alias}\\\\t{device_address}\",\n\t\"tooltip-format-enumerate-connected-battery\": \"{device_alias}\\\\t{device_address}\\\\t{device_battery_percentage}%\"\n}\n```\n\n# STYLE\n\n- *#bluetooth*\n- *#bluetooth.disabled*\n- *#bluetooth.off*\n- *#bluetooth.on*\n- *#bluetooth.connected*\n- *#bluetooth.discoverable*\n- *#bluetooth.discovering*\n- *#bluetooth.pairable*\n"
  },
  {
    "path": "man/waybar-cava.5.scd",
    "content": "waybar-cava(5) \"waybar-cava\" \"User Manual\"\n\n# NAME\n\nwaybar - cava module\n\n# DESCRIPTION\n\n*cava* module for karlstav/cava project. See it on github: https://github.com/karlstav/cava.\n\nModule supports two different frontends starting from the 0.15.0 release. The frontend that\nwill be used is managed by the method parameter in the [output] section of the cava configuration file.\n\n# FILES\n\n$XDG_CONFIG_HOME/waybar/config ++\n\tPer user configuration file\n\n# ADDITIONAL FILES\n\nlibcava lives in:\n\n. /usr/lib/libcava.so or /usr/lib64/libcava.so\n. /usr/lib/pkgconfig/cava.pc or /usr/lib64/pkgconfig/cava.pc\n. /usr/include/cava\n\n# CONFIGURATION\n\n[- *Option*\n:- *Typeof*\n:- *Default*\n:- *Description*\n|[ *cava_config*\n:[ string\n:[\n:< Path where cava configuration file is placed to\n|[ *method* \\[output\\]\n:[ string\n:[\n:< Manages which frontend Waybar cava module should use. Values: raw, sdl_glsl\n|[ *framerate*\n:[ integer\n:[ 30\n:[ Frames per second. Is used as a replacement for *interval*\n|[ *autosens*\n:[ integer\n:[ 1\n:[ Will attempt to decrease sensitivity if the bars peak\n|[ *sensitivity*\n:[ integer\n:[ 100\n:[ Manual sensitivity in %. If autosens is enabled, this will only be the initial value. 200 means double height. Accepts only non-negative values\n|[ *bars*\n:[ integer\n:[ 12\n:[ The number of bars\n|[ *lower_cutoff_freq*\n:[ long integer\n:[ 50\n:[ Lower cutoff frequencies for lowest bars the bandwidth of the visualizer\n|[ *higher_cutoff_freq*\n:[ long integer\n:[ 10000\n:[ Higher cutoff frequencies for highest bars the bandwidth of the visualizer\n|[ *sleep_timer*\n:[ integer\n:[ 5\n:[ Seconds with no input before cava main thread goes to sleep mode\n|[ *hide_on_silence*\n:[ bool\n:[ false\n:[ Hides the widget if no input (after sleep_timer elapsed)\n|[ *format_silent*\n:[ string\n:[\n:[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false)\n|[ *method* \\[input\\]\n:[ string\n:[ pulse\n:[ Audio capturing method. Possible methods are: pipewire, pulse, alsa, fifo, sndio or shmem\n|[ *source*\n:[ string\n:[ auto\n:[ See cava configuration\n|[ *sample_rate*\n:[ long integer\n:[ 44100\n:[ See cava configuration\n|[ *sample_bits*\n:[ integer\n:[ 16\n:[ See cava configuration\n|[ *stereo*\n:[ bool\n:[ true\n:[ Visual channels\n|[ *reverse*\n:[ bool\n:[ false\n:[ Displays frequencies the other way around\n|[ *bar_delimiter*\n:[ integer\n:[ 0\n:[ Each bar is separated by a delimiter. Use decimal value in ascii table(i.e. 59 = \";\"). 0 means no delimiter\n|[ *monstercat*\n:[ bool\n:[ false\n:[ Disables or enables the so-called \"Monstercat smoothing\" with or without \"waves\"\n|[ *waves*\n:[ bool\n:[ false\n:[ Disables or enables the so-called \"Monstercat smoothing\" with or without \"waves\"\n|[ *noise_reduction*\n:[ integer\n:[ 77\n:[ Range between 0 - 100. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 100 will be very slow and smooth, 0 will be fast but noisy\n|[ *input_delay*\n:[ integer\n:[ 2\n:[ Sets the delay before fetching audio source thread start working. On author's machine, Waybar starts much faster than pipewire audio server, and without a little delay cava module fails because pipewire is not ready\n|[ *ascii_max_range*\n:[ integer\n:[ 7\n:[ It's impossible to set it directly. The value is dictated by the number of icons in the array *format-icons*\n|[ *data_format*\n:[ string\n:[ asci\n:[ Raw data format. Can be 'binary' or 'ascii'\n|[ *raw_target*\n:[ string\n:[ /dev/stdout\n:[ Raw output target. A fifo will be created if target does not exist\n|[ *menu*\n:[ string\n:[\n:[ Action that popups the menu.\n|[ *menu-file*\n:[ string\n:[\n:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*\n|[ *menu-actions*\n:[ array\n:[\n:[ The actions corresponding to the buttons of the menu.\n|[ *bar_spacing*\n:[ integer\n:[\n:[ Bars' space between bars in number of characters\n|[ *bar_width*\n:[ integer\n:[\n:[ Bars' width between bars in number of characters\n|[ *bar_height*\n:[ integer\n:[\n:[ Useless. bar_height is only used for output in \"noritake\" format\n|[ *background*\n:[ string\n:[\n:[ GLSL actual. Support hex code colors only. Must be within ''\n|[ *foreground*\n:[ string\n:[\n:[ GLSL actual. Support hex code colors only. Must be within ''\n|[ *gradient*\n:[ integer\n:[ 0\n:[ GLSL actual. Gradient mode(0/1 - on/off)\n|[ *gradient_count*\n:[ integer\n:[ 0\n:[ GLSL actual. The count of colors for the gradient\n|[ *gradient_color_N*\n:[ string\n:[\n:[ GLSL actual. N - the number of the gradient color between 1 and 8. Only hex defined colors are supported. Must be within ''\n|[ *sdl_width*\n:[ integer\n:[\n:[ GLSL actual. Manages the width of the waybar cava GLSL frontend module\n|[ *sdl_height*\n:[ integer\n:[\n:[ GLSL actual. Manages the height of the waybar cava GLSL frontend module\n|[ *continuous_rendering*\n:[ integer\n:[ 0\n:[ GLSL actual. Keep rendering even if no audio. Recommended to set to 1\n\nConfiguration can be provided as:\n- The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped\n- Without cava configuration file. In such case cava should be configured through provided list of the configuration option\n- Mix. When provided both And cava configuration file And configuration options. In such case, waybar applies configuration file first and then overrides particular options by the provided list of configuration options\n\n# ACTIONS\n\n[- *String*\n:- *Action*\n|[ *mode*\n:< Switch main cava thread and fetch audio source thread from/to pause/resume\n\n# DEPENDENCIES\n\n- iniparser\n- fftw3\n- epoxy (GLSL frontend only)\n\n# SOLVING ISSUES\n\n. On start Waybar throws an exception \"error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory\".\n  It might happen when libcava for some reason hasn't been registered in the system. sudo ldconfig should help\n. Waybar is starting but cava module doesn't react to the music\n   1. In such cases at first need to make sure usual cava application is working as well\n   2. If so, need to comment all configuration options. Uncomment cava_config and provide the path to the working cava config\n   3. You might set too huge or too small input_delay. Try to setup to 4 seconds, restart waybar, and check again 4 seconds past. Usual even on weak machines it should be enough\n   4. You might accidentally switch action mode to pause mode\n\n# RISING ISSUES\n\nFor clear understanding: this module is a cava API's consumer. So for any bugs related to cava engine you should contact Cava upstream(https://github.com/karlstav/cava) ++\nwith the one Exception. Cava upstream doesn't provide cava as a shared library. For that, this module author made a fork libcava(https://github.com/LukashonakV/cava). ++\nSo the order is:\n. cava upstream\n. libcava upstream.\nIn case when cava releases new version and you're wanna get it, it should be raised an issue to libcava(https://github.com/LukashonakV/cava) with title ++\n\\[Bump\\]x.x.x where x.x.x is cava release version.\n\n# EXAMPLES\n\n```\n\"cava\": {\n\t//\"cava_config\": \"$XDG_CONFIG_HOME/cava/cava.conf\",\n\t\"framerate\": 30,\n\t\"autosens\": 1,\n\t//\"sensitivity\": 100,\n\t\"bars\": 14,\n\t\"lower_cutoff_freq\": 50,\n\t\"higher_cutoff_freq\": 10000,\n\t\"method\": \"pulse\",\n\t\"source\": \"auto\",\n\t\"stereo\": true,\n\t\"reverse\": false,\n\t\"bar_delimiter\": 0,\n\t\"monstercat\": false,\n\t\"waves\": false,\n\t\"noise_reduction\": 0.77,\n\t\"input_delay\": 2,\n\t\"format-icons\" : [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\" ],\n\t\"actions\": {\n\t\t\"on-click-right\": \"mode\"\n\t}\n},\n```\n# STYLE\n\n- *#cava*\n- *#cava.silent* Applied after no sound has been detected for sleep_timer seconds\n- *#cava.updated* Applied when a new frame is shown\n# FRONTENDS\n\n## RAW\nThe cava raw frontend uses ASCII characters to visualize incoming audio data. Each ASCII symbol position corresponds to the value of the audio power pulse.\n\nUnder the hood:\n```\n. Incoming audio power pulse list is : 12684\n. Configured array of ASCII codes is: [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\" ]. See `format-icons` https://github.com/Alexays/Waybar/wiki/Module:-Cava#example \n```\nAs a result cava frontend will give ▁▂▆█▄\n\nExamples:\n\nwaybar config\n```\n\"cava\": {\n        \"cava_config\": \"$XDG_CONFIG_HOME/cava/waybar_raw.conf\",\n        \"input_delay\": 2,\n        \"format-icons\" : [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\" ],\n        \"actions\": {\n                   \"on-click-right\": \"mode\"\n                   }\n        },\n```\n\nwaybar_raw.conf\n```\n## Configuration file for CAVA.\n# Remove the ; to change parameters.\n\n\n[general]\n\n# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0\n\n# Accepts only non-negative values.\n\n# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off\n# new as of 0.6.0 autosens of low values (dynamic range)\n# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0\n\n# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.\n# 200 means double height. Accepts only non-negative values.\n\n# The number of bars (0-512). 0 sets it to auto (fill up console).\n# Bars' width and space between bars in number of characters.\nbars = 12\n# bar_height is only used for output in \"noritake\" format\n\n# For SDL width and space between bars is in pixels, defaults are:\n\n# sdl_glsl have these default values, they are only used to calculate max number of bars.\n\n\n# Lower and higher cutoff frequencies for lowest and highest bars\n# the bandwidth of the visualizer.\n# Note: there is a minimum total bandwidth of 43Mhz x number of bars.\n# Cava will automatically increase the higher cutoff if a too low band is specified.\n\n# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and\n# only check for input once per second. Cava will wake up once input is detected. 0 = disable.\nsleep_timer = 5\n\n\n[input]\n\n# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'\n# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.\n# On Mac it defaults to 'portaudio' or 'fifo'\n# On windows this is automatic and no input settings are needed.\n#\n# All input methods uses the same config variable 'source'\n# to define where it should get the audio.\n#\n# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink\n# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).\n#\n# For pipewire 'source' will be the object name or object.serial of the device to capture from.\n# Both input and output devices are supported.\n#\n# For alsa 'source' will be the capture device.\n# For fifo 'source' will be the path to fifo-file.\n# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address\n#\n# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.\n# README.md contains further information on how to setup CAVA for sndio.\n#\n# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.\n# README.md contains further information on how to setup CAVA for OSS on FreeBSD.\n#\n# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.\n# README.md contains further information on how to setup CAVA for JACK.\n#\n\n# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:\n#   sample_rate: fifo, pipewire, sndio, oss\n#   sample_bits: fifo, pipewire, sndio, oss\n#   channels:    sndio, oss, jack\n#   autoconnect: jack\n# Other methods ignore these settings.\n#\n# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported\n# by the chosen audio device, the device will use other supported values instead.\n# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it\n# will use 44100, 16 and 1.\n#\n\n\n[output]\n\n# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'\n# or 'sdl_glsl'.\n# 'noncurses' (default) uses a buffer and cursor movements to only print\n# changes from frame to frame in the terminal. Uses less resources and is less\n# prone to tearing (vsync issues) than 'ncurses'.\n#\n# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data\n# stream of the bar heights that can be used to send to other applications.\n# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.\n#\n# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display\n#  in graphic mode. It only support the 3000 series graphical VFDs for now.\n#\n# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.\n# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or\n# use one of the predefined ones.\nmethod = raw\n\n# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or\n# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl\n# and ncruses output. 'horizontal' (bars go up and down from center) is only supported\n# on noncurses output.\n# Note: many fonts have weird or missing glyphs for characters used in orientations\n# other than 'bottom', which can make output not look right.\n\n# Visual channels. Can be 'stereo' or 'mono'.\n# 'stereo' mirrors both channels with low frequencies in center.\n# 'mono' outputs left to right lowest to highest frequencies.\n# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.\n# set 'reverse' to 1 to display frequencies the other way around.\n\n# Raw output target. A fifo will be created if target does not exist.\nraw_target = /dev/stdout\n\n# Raw data format. Can be 'binary' or 'ascii'.\ndata_format = ascii\n\n# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).\n\n# Ascii max value. In 'ascii' mode range will run from 0 to value specified here\n\n# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.\n# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\\n' (line feed)).\nbar_delimiter = 0\n\n# sdl window size and position. -1,-1 is centered.\n\n# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'\n# 'frequency' displays the lower cut off frequency of the bar above.\n# Only supported on ncurses and noncurses output.\n\n# enable synchronized sync. 1 = on, 0 = off\n# removes flickering in alacritty terminal emulator.\n# defaults to off since the behaviour in other terminal emulators is unknown\n\n# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders\n\n; for glsl output mode, keep rendering even if no audio\n\n# disable console blank (screen saver) in tty\n# (Not supported on FreeBSD)\n\n# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off\n\n# show waveform instead of frequency spectrum, 1 = on, 0 = off\n\n[color]\n\n# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.\n# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires\n# a terminal that can change color definitions such as Gnome-terminal or rxvt.\n# default is to keep current terminal color\n\n# SDL and sdl_glsl only support hex code colors, these are the default:\n\n# Gradient mode, only hex defined colors are supported,\n# background must also be defined in hex or remain commented out. 1 = on, 0 = off.\n# You can define as many as 8 different colors. They range from bottom to top of screen\n\n[smoothing]\n\n# Disables or enables the so-called \"Monstercat smoothing\" with or without \"waves\". Set to 0 to disable.\n\n# Noise reduction, int 0 - 100. default 77\n# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth\n# 100 will be very slow and smooth, 0 will be fast but noisy.\n\n\n[eq]\n\n# This one is tricky. You can have as much keys as you want.\n# Remember to uncomment more than one key! More keys = more precision.\n# Look at readme.md on github for further explanations and examples.\n```\n## GLSL\nThe Cava GLSL frontend delegates the visualization of incoming audio data to the GPU via OpenGL.\n\nThere are some mandatory dependencies that need to be satisfied in order for Cava GLSL to be built and function properly:\n\n. epoxy library must be installed on the system\n. Vertex and fragment shaders from the original project must be used. They should be downloaded, and the file paths must be configured correctly in the Waybar Cava configuration:\n   1. cava shaders [cava shaders](https://github.com/karlstav/cava/tree/master/output/shaders)\n   2. libcava shaders [libcava shaders](https://github.com/LukashonakV/cava/tree/master/output/shaders)\n. It is highly recommended to have a separate cava configuration for the Waybar Cava GLSL module and to use this as the cava_config in the Waybar configuration.\n. It is common for cava configurations to be placed in the XDG_CONFIG_HOME directory, including shaders as well. Consider keeping them in the $XDG_CONFIG_HOME/cava/shaders folder.\n\nKey configuration options:\n\n. bars. The more values the parameter has, the more interesting the visualization becomes.\n. method in output section must be set to sdl_glsl\n. sdl_width and sdl_height manage the size of the module. Adjust them according to your needs.\n. Shaders for sdl_glsl, located in $HOME/.config/cava/shaders. Example: \"vertex_shader\" = \"pass_through.vert\" \"fragment_shader\" = \"spectrogram.frag\"\n. Set continuous_rendering to 1 to enable smooth rendering; set it to 0 otherwise. It is recommended to keep it set to 1.\n. background, foreground, and gradient_color_N (where N is a number between 1 and 8) must be defined using hex code\n\nExample:\n\nwaybar config\n```\n\"cava\": {\n        \"cava_config\": \"$XDG_CONFIG_HOME/cava/waybar_cava#3.conf\",\n        \"input_delay\": 2,\n        \"actions\": {\n                   \"on-click-right\": \"mode\"\n                   }\n        },\n```\n\nwaybar_raw.conf\n```\n## Configuration file for CAVA.\n# Remove the ; to change parameters.\n\n\n[general]\n\n# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0\n\n# Accepts only non-negative values.\n\n# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off\n# new as of 0.6.0 autosens of low values (dynamic range)\n# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0\n\n# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.\n# 200 means double height. Accepts only non-negative values.\n\n# The number of bars (0-512). 0 sets it to auto (fill up console).\n# Bars' width and space between bars in number of characters.\nbars = 50\n\n# bar_height is only used for output in \"noritake\" format\n\n# For SDL width and space between bars is in pixels, defaults are:\n\n# sdl_glsl have these default values, they are only used to calculate max number of bars.\n\n# Lower and higher cutoff frequencies for lowest and highest bars\n# the bandwidth of the visualizer.\n# Note: there is a minimum total bandwidth of 43Mhz x number of bars.\n# Cava will automatically increase the higher cutoff if a too low band is specified.\n\n# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and\n# only check for input once per second. Cava will wake up once input is detected. 0 = disable.\nsleep_timer = 5\n\n\n[input]\n\n# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'\n# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.\n# On Mac it defaults to 'portaudio' or 'fifo'\n# On windows this is automatic and no input settings are needed.\n#\n# All input methods uses the same config variable 'source'\n# to define where it should get the audio.\n#\n# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink\n# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).\n#\n# For pipewire 'source' will be the object name or object.serial of the device to capture from.\n# Both input and output devices are supported.\n#\n# For alsa 'source' will be the capture device.\n# For fifo 'source' will be the path to fifo-file.\n# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address\n#\n# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.\n# README.md contains further information on how to setup CAVA for sndio.\n#\n# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.\n# README.md contains further information on how to setup CAVA for OSS on FreeBSD.\n#\n# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.\n# README.md contains further information on how to setup CAVA for JACK.\n#\n\n\n# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:\n#   sample_rate: fifo, pipewire, sndio, oss\n#   sample_bits: fifo, pipewire, sndio, oss\n#   channels:    sndio, oss, jack\n#   autoconnect: jack\n# Other methods ignore these settings.\n#\n# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported\n# by the chosen audio device, the device will use other supported values instead.\n# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it\n# will use 44100, 16 and 1.\n#\n\n\n[output]\n\n# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'\n# or 'sdl_glsl'.\n# 'noncurses' (default) uses a buffer and cursor movements to only print\n# changes from frame to frame in the terminal. Uses less resources and is less\n# prone to tearing (vsync issues) than 'ncurses'.\n#\n# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data\n# stream of the bar heights that can be used to send to other applications.\n# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.\n#\n# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display\n#  in graphic mode. It only support the 3000 series graphical VFDs for now.\n#\n# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.\n# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or\n# use one of the predefined ones.\nmethod = sdl_glsl\n\n# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or\n# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl\n# and ncruses output. 'horizontal' (bars go up and down from center) is only supported\n# on noncurses output.\n# Note: many fonts have weird or missing glyphs for characters used in orientations\n# other than 'bottom', which can make output not look right.\n\n# Visual channels. Can be 'stereo' or 'mono'.\n# 'stereo' mirrors both channels with low frequencies in center.\n# 'mono' outputs left to right lowest to highest frequencies.\n# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.\n# set 'reverse' to 1 to display frequencies the other way around.\n\n# Raw output target. A fifo will be created if target does not exist.\n\n# Raw data format. Can be 'binary' or 'ascii'.\n\n# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).\n\n# Ascii max value. In 'ascii' mode range will run from 0 to value specified here\n\n# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.\n# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\\n' (line feed)).\nbar_delimiter = 0\n\n# sdl window size and position. -1,-1 is centered.\nsdl_width = 150\nsdl_height = 39\n\n# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'\n# 'frequency' displays the lower cut off frequency of the bar above.\n# Only supported on ncurses and noncurses output.\n\n# enable synchronized sync. 1 = on, 0 = off\n# removes flickering in alacritty terminal emulator.\n# defaults to off since the behaviour in other terminal emulators is unknown\n\n# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders\nvertex_shader = pass_through.vert\nfragment_shader = bar_spectrum.frag\n\n; for glsl output mode, keep rendering even if no audio\ncontinuous_rendering = 1;\n\n# disable console blank (screen saver) in tty\n# (Not supported on FreeBSD)\n\n# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off\n\n# show waveform instead of frequency spectrum, 1 = on, 0 = off\n\n[color]\n\n\n# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.\n# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires\n# a terminal that can change color definitions such as Gnome-terminal or rxvt.\n# default is to keep current terminal color\n\n# SDL and sdl_glsl only support hex code colors, these are the default:\nbackground = '#282C34'\n\n# Gradient mode, only hex defined colors are supported,\n# background must also be defined in hex or remain commented out. 1 = on, 0 = off.\n# You can define as many as 8 different colors. They range from bottom to top of screen\ngradient = 1\ngradient_count = 2\ngradient_color_1 = '#282C34'\ngradient_color_2 = '#45475A'\n\n; gradient_color_1 = '#59cc33'\n; gradient_color_2 = '#80cc33'\n gradient_color_3 = '#a6cc33'\n gradient_color_4 = '#cccc33'\n gradient_color_5 = '#cca633'\n gradient_color_6 = '#cc8033'\n gradient_color_7 = '#cc5933'\n gradient_color_8 = '#cc3333'\n\n[smoothing]\n\n# Percentage value for integral smoothing. Takes values from 0 - 100.\n# Higher values means smoother, but less precise. 0 to disable.\n# DEPRECATED as of 0.8.0, use noise_reduction instead\n\n# Disables or enables the so-called \"Monstercat smoothing\" with or without \"waves\". Set to 0 to disable.\n\n# Set gravity percentage for \"drop off\". Higher values means bars will drop faster.\n# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable \"drop off\".\n# DEPRECATED as of 0.8.0, use noise_reduction instead\n\n\n# In bar height, bars that would have been lower that this will not be drawn.\n# DEPRECATED as of 0.8.0\n\n# Noise reduction, int 0 - 100. default 77\n# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth\n# 100 will be very slow and smooth, 0 will be fast but noisy.\n\n[eq]\n\n# This one is tricky. You can have as much keys as you want.\n# Remember to uncomment more than one key! More keys = more precision.\n# Look at readme.md on github for further explanations and examples.\n```\n\nDifferent waybar_cava#N.conf see at [cava GLSL](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL)\n"
  },
  {
    "path": "man/waybar-cffi.5.scd",
    "content": "waybar-cffi(5)\n# NAME\n\nwaybar - cffi module\n\n# DESCRIPTION\n\nThe *cffi* module gives full control of a GTK widget to a third-party dynamic library, to create more complex modules using different programming languages.\n\n# CONFIGURATION\n\nAddressed by *cffi/<name>*\n\n*module_path*: ++\n\ttypeof: string ++\n\tThe path to the dynamic library to load to control the widget.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\nSome additional configuration may be required depending on the cffi dynamic library being used.\n\n\n# EXAMPLES\n\n## C example:\n\nAn example module written in C can be found at https://github.com/Alexays/Waybar/resources/custom_modules/cffi_example/\n\nWaybar config to enable the module:\n```\n\"cffi/c_example\": {\n\t\"module_path\": \".config/waybar/cffi/wb_cffi_example.so\"\n}\n```\n\n\n# STYLE\n\nThe classes and IDs are managed by the cffi dynamic library.\n"
  },
  {
    "path": "man/waybar-clock.5.scd",
    "content": "waybar-clock(5) \"waybar-clock\" \"User Manual\"\n\n# NAME\n\nwaybar - clock module\n\n# DESCRIPTION\n\n*clock* module displays current date and time\n\n# FILES\n\n$XDG_CONFIG_HOME/waybar/config ++\n\tPer user configuration file\n\n# CONFIGURATION\n\n1. Addressed by *clock*\n[- *Option*\n:- *Typeof*\n:- *Default*\n:- *Description*\n|[ *interval*\n:[ integer\n:[ 60\n:[ The interval in which the information gets polled\n|[ *format*\n:[ string\n:[ *{:%H:%M}*\n:[ The format, how the date and time should be displayed. See format options below\n|[ *timezone*\n:[ string\n:[\n:[ The timezone to display the time in, e.g. America/New_York. \"\" represents\n   the system's local timezone. See Wikipedia's unofficial list of timezones <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>\n|[ *timezones*\n:[ list of strings\n:[\n:[ A list of timezones (as in *timezone*) to use for time display, changed using\n   the scroll wheel. Do not specify *timezone* option when *timezones* is specified.\n   \"\" represents the system's local timezone\n|[ *timezone-tooltip-format*\n:[ string\n:[\n:[ Format to use for displaying timezones in the tooltip. When set, this allows showing\n   timezone information (like timezone abbreviations) in the tooltip while keeping the\n   main display clean. Uses the same format options as *format*\n|[ *locale*\n:[ string\n:[\n:[ A locale to be used to display the time. Intended to render times in custom\n   timezones with the proper language and format\n|[ *max-length*\n:[ integer\n:[\n:[ The maximum length in character the module should display\n|[ *rotate*\n:[ integer\n:[\n:[ Positive value to rotate the text label (in 90 degree increments)\n|[ *on-click*\n:[ string\n:[\n:[ Command to execute when clicked on the module\n|[ *on-click-middle*\n:[ string\n:[\n:[ Command to execute when you middle clicked on the module using mousewheel\n|[ *on-click-right*\n:[ string\n:[\n:[ Command to execute when you right-click on the module\n|[ *on-scroll-up*\n:[ string\n:[\n:[ Command to execute when scrolling up on the module\n|[ *on-scroll-down*\n:[ string\n:[\n:[ Command to execute when scrolling down on the module\n|[ *smooth-scrolling-threshold*\n:[ double\n:[\n:[ Threshold to be used when scrolling\n|[ *tooltip*\n:[ bool\n:[ true\n:[ Option to enable tooltip on hover\n|[ *tooltip-format*\n:[ string\n:[ same as format\n:[ Tooltip on hover\n|[ *menu*\n:[ string\n:[\n:[ Action that popups the menu.\n|[ *menu-file*\n:[ string\n:[\n:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*\n|[ *menu-actions*\n:[ array\n:[\n:[ The actions corresponding to the buttons of the menu.\n|[ *expand*:\n:[ bool\n:[ false\n:[ Enables this module to consume all left over space dynamically.\n\nView all valid format options in *strftime(3)* or have a look https://en.cppreference.com/w/cpp/chrono/duration/formatter\n\n2. Addressed by *clock: calendar*\n[- *Option*\n:- *Typeof*\n:- *Default*\n:- *Description*\n|[ *mode*\n:[ string\n:[ month\n:[ Calendar view mode. Possible values: year|month\n|[ *mode-mon-col*\n:[ integer\n:[ 3\n:[ Relevant for *mode=year*. Count of months per row\n|[ *weeks-pos*\n:[ string\n:[\n:[ The position where week numbers should be displayed. Disabled when is empty.\n   Possible values: left|right\n|[ *on-scroll*\n:[ integer\n:[ 1\n:[ Value to scroll months/years forward/backward. Can be negative. Is\n   configured under *on-scroll* option\n|[ *iso8601*\n:[ bool\n:[ false\n:[ When enabled, the calendar follows the ISO 8601 standard: weeks begin on\n   Monday, and the first week of the year is numbered 1. The default week format is\n   '{:%V}'.\n\n3. Addressed by *clock: calendar: format*\n[- *Option*\n:- *Typeof*\n:- *Default*\n:- *Description*\n|[ *months*\n:[ string\n:[\n:[ Format is applied to months header(January, February,...etc.)\n|[ *days*\n:[ string\n:[\n:[ Format is applied to days\n|[ *weeks*\n:[ string\n:[ *{:%U}*\n:[ Format is applied to week numbers. When weekday format is not provided then\n   is used default format: '{:%W}' when week starts with Monday, '{:%U}' otherwise\n|[ *weekdays*\n:[ string\n:[\n:[ Format is applied to weeks header(Su,Mo,...etc.)\n|[ *today*\n:[ string\n:[ *<b><u>{}</u></b>*\n:[ Format is applied to Today\n\n## Actions\n\n[- *String*\n:- *Action*\n|[ *mode*\n:[ Switch calendar mode between year/month\n|[ *tz_up*\n:[ Switch to the next provided time zone\n|[ *tz_down*\n:[ Switch to the previously provided time zone\n|[ *shift_up*\n:[ Switch to the next calendar month/year\n|[ *shift_down*\n:[ Switch to the previous calendar month/year\n\n# FORMAT REPLACEMENTS\n\n- *{calendar}*: Current month calendar\n- *{tz_list}*: List of time in the rest timezones, if more than one timezone is set in the config\n- *{ordinal_date}*: The current day in (English) ordinal form, e.g. 21st\n\n# EXAMPLES\n\n1. General\n\n```\n\"clock\": {\n\t\"interval\": 60,\n\t\"format\": \"{:%H:%M}\",\n\t\"max-length\": 25\n}\n```\n\n2. Calendar\n\n```\n\"clock\": {\n\t\"format\": \"{:%H:%M}  \",\n\t\"format-alt\": \"{:%A, %B %d, %Y (%R)} 󰃰 \",\n\t\"tooltip-format\": \"<tt><small>{calendar}</small></tt>\",\n\t\"calendar\": {\n\t\t\"mode\"          : \"year\",\n\t\t\"mode-mon-col\"  : 3,\n\t\t\"weeks-pos\"     : \"right\",\n\t\t\"on-scroll\"     : 1,\n\t\t\"on-click-right\": \"mode\",\n\t\t\"format\": {\n\t\t\t\"months\":     \"<span color='#ffead3'><b>{}</b></span>\",\n\t\t\t\"days\":       \"<span color='#ecc6d9'><b>{}</b></span>\",\n\t\t\t\"weeks\":      \"<span color='#99ffdd'><b>W{}</b></span>\",\n\t\t\t\"weekdays\":   \"<span color='#ffcc66'><b>{}</b></span>\",\n\t\t\t\"today\":      \"<span color='#ff6699'><b><u>{}</u></b></span>\"\n\t\t}\n\t},\n\t\"actions\": {\n\t\t\"on-click-right\": \"mode\",\n\t\t\"on-click-forward\": \"tz_up\",\n\t\t\"on-click-backward\": \"tz_down\",\n\t\t\"on-scroll-up\": \"shift_up\",\n\t\t\"on-scroll-down\": \"shift_down\"\n\t}\n},\n```\n\n3. Full date on hover\n\n```\n\"clock\": {\n\t\"interval\": 60,\n\t\"tooltip\": true,\n\t\"format\": \"{:%H.%M}\",\n\t\"tooltip-format\": \"{:%Y-%m-%d}\",\n}\n```\n\n4. Show timezone in tooltip only\n\n```\n\"clock\": {\n\t\"interval\": 60,\n\t\"format\": \"{:%H:%M}\",\n\t\"timezone-tooltip-format\": \"{:%H:%M %Z}\",\n\t\"timezones\": [\n\t\t\"\",\n\t\t\"America/Chicago\",\n\t\t\"America/Los_Angeles\",\n\t\t\"Europe/Paris\",\n\t\t\"UTC\"\n\t],\n\t\"tooltip\": true,\n\t\"tooltip-format\": \"{tz_list}\"\n}\n```\n5. Simple calendar tooltip\n\n```\n\"clock\": {\n\"format\": \"{:%H:%M}\",\n\"tooltip-format\": \"<tt>{calendar}</tt>\",\n\"calendar\": {\n\"format\": {\n\"today\": \"<span color='#ffcc66'><b>{}</b></span>\"\n}\n}\n}\n```\n# STYLE\n\n- *#clock*\n\n# Troubleshooting\n\nIf clock module is disabled at startup with locale::facet::\\_S\\_create\\_c\\_locale ++\nname not valid error message try one of the following:\n\n- check if LC_TIME is set properly (glibc)\n- set locale to C in the config file (musl)\n\nThe locale option must be set for {calendar} to use the correct start-of-week, regardless of system locale.\n\n## Calendar in Chinese. Alignment\n\nIn order to have aligned Chinese calendar there are some useful recommendations:\n\n. Use \"WenQuanYi Zen Hei Mono\" which is provided in most Linux distributions\n. Try different font sizes and find best for you. size = 9pt should be fine\n. In case when \"WenQuanYi Zen Hei Mono\" font is used disable monospace font pango tag\n\nExample of working config\n\n```\n\"clock\": {\n\t\"format\": \"{:%H:%M}  \",\n\t\"format-alt\": \"{:%A, %B %d, %Y (%R)} 󰃰 \",\n\t\"tooltip-format\": \"\\n<span size='9pt' font='WenQuanYi Zen Hei Mono'>{calendar}</span>\",\n\t\"calendar\": {\n\t\t\"mode\"          : \"year\",\n\t\t\"mode-mon-col\"  : 3,\n\t\t\"weeks-pos\"     : \"right\",\n\t\t\"on-scroll\"     : 1,\n\t\t\"on-click-right\": \"mode\",\n\t\t\"format\": {\n\t\t\t\"months\":     \"<span color='#ffead3'><b>{}</b></span>\",\n\t\t\t\"days\":       \"<span color='#ecc6d9'><b>{}</b></span>\",\n\t\t\t\"weeks\":      \"<span color='#99ffdd'><b>W{}</b></span>\",\n\t\t\t\"weekdays\":   \"<span color='#ffcc66'><b>{}</b></span>\",\n\t\t\t\"today\":      \"<span color='#ff6699'><b><u>{}</u></b></span>\"\n\t\t}\n\t},\n\t\"actions\": {\n\t\t\"on-click-right\": \"mode\",\n\t\t\"on-click-forward\": \"tz_up\",\n\t\t\"on-click-backward\": \"tz_down\",\n\t\t\"on-scroll-up\": \"shift_up\",\n\t\t\"on-scroll-down\": \"shift_down\"\n\t}\n},\n```\n\n# AUTHOR\n\nAlexis Rouillard <contact@arouillard.fr>\n"
  },
  {
    "path": "man/waybar-cpu.5.scd",
    "content": "waybar-cpu(5)\n\n# NAME\n\nwaybar - cpu module\n\n# DESCRIPTION\n\nThe *cpu* module displays the current CPU utilization.\n\n# CONFIGURATION\n\n*interval*: ++\n\ttypeof: integer or float ++\n\tdefault: 10 ++\n\tThe interval in which the information gets polled. ++\n\tMinimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms.\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {usage}% ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current usage, the corresponding icon gets selected. ++\n\tThe order is *low* to *high*. Or by the state if it is an object.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of CPU usage states which get activated on certain usage levels. See *waybar-states(5)*.\n\n*on-click*: ++\n\ttypeof: string  ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{load}*: Current CPU load.\n\n*{usage}*: Current overall CPU usage.\n\n*{usage*{n}*}*: Current CPU core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.\n\n*{avg_frequency}*: Current CPU average frequency (based on all cores) in GHz.\n\n*{max_frequency}*: Current CPU max frequency (based on the core with the highest frequency) in GHz.\n\n*{min_frequency}*: Current CPU min frequency (based on the core with the lowest frequency) in GHz.\n\n*{icon}*: Icon for overall CPU usage.\n\n*{icon*{n}*}*: Icon for CPU core n usage. Use like {icon0}.\n\n# EXAMPLES\n\nBasic configuration:\n\n```\n\"cpu\": {\n\t\"interval\": 10,\n\t\"format\": \"{}% \",\n\t\"max-length\": 10\n}\n```\n\nCPU usage per core rendered as icons:\n\n```\n\"cpu\": {\n\t\"interval\": 1,\n\t\"format\": \"{icon0}{icon1}{icon2}{icon3} {usage:>2}% \",\n\t\"format-icons\": [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\"],\n},\n```\n\n# STYLE\n\n- *#cpu*\n"
  },
  {
    "path": "man/waybar-custom.5.scd",
    "content": "waybar-custom(5)\n# NAME\n\nwaybar - custom module\n\n# DESCRIPTION\n\nThe *custom* module displays either the output of a script or static text.\nTo display static text, specify only the *format* field.\n\n# CONFIGURATION\n\nAddressed by *custom/<name>*\n\n*exec*: ++\n\ttypeof: string ++\n\tThe path to the script, which should be executed.\n\n*exec-if*: ++\n\ttypeof: string ++\n\tThe path to a script, which determines if the script in *exec* should be executed. ++\n\t*exec* will be executed if the exit code of *exec-if* equals 0.\n\n*hide-empty-text*: ++\n\ttypeof: bool ++\n\tDisables the module when output is empty, but format might contain additional static content.\n\n*exec-on-event*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIf an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after executing the event command.\n\n*return-type*: ++\n\ttypeof: string ++\n\tSee *return-type*\n\n*interval*: ++\n\ttypeof: integer or float ++\n\tThe interval (in seconds) in which the information gets polled. ++\n\tMinimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++\n\tUse *once* if you want to execute the module only on startup. ++\n\tYou can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++\n\tIf a *signal* is defined then the script will run once on startup and will only update with a signal.\n\n*restart-interval*: ++\n\ttypeof: integer or float ++\n\tThe restart interval (in seconds). ++\n\tMinimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++\n\tCan't be used with the *interval* option, so only with continuous scripts. ++\n\tOnce the script exits, it'll be re-executed after the *restart-interval*.\n\n*signal*: ++\n\ttypeof: integer ++\n\tThe signal number used to update the module. ++\n\tThe number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++\n\tIf no interval is defined then a signal will be the only way to update the module.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {text} ++\n\tThe format, how information should be displayed. On {text} data gets inserted.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the set percentage, the corresponding icon gets selected. The order is *low* to *high*.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tThe tooltip format. If specified, overrides any tooltip output from the script in *exec*. ++\n\tUses the same format replacements as *format*.\n\n*escape*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to enable escaping of script output.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# RETURN-TYPE\n\nWhen *return-type* is set to *json*, Waybar expects the *exec*-script  to output its data in JSON format.\nThis should look like this:\n\n```\n{\"text\": \"$text\", \"tooltip\": \"$tooltip\", \"class\": \"$class\", \"percentage\": $percentage }\n```\n\nThe *class* parameter also accepts an array of strings.\n\nIf nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated.\nThis should look like this:\n\n```\n$text\\\\n$tooltip\\\\n$class*\n```\n\n*class* is a CSS class, to apply different styles in *style.css*\n\n# FORMAT REPLACEMENTS\n\n*{text}*: Output of the script.\n\n*{percentage}* Percentage which can be set via a json return type.\n\n*{icon}*: An icon from 'format-icons' according to percentage.\n\n# EXAMPLES\n\n## Spotify:\n\n```\n\"custom/spotify\": {\n\t\"format\": \" {text}\",\n\t\"max-length\": 40,\n\t\"interval\": 30, // Remove this if your script is endless and write in loop\n\t\"exec\": \"$HOME/.config/waybar/mediaplayer.sh 2> /dev/null\", // Script in resources folder\n\t\"exec-if\": \"pgrep spotify\",\n\t\"return-type\": \"json\"\n}\n```\n\n## mpd:\n\n```\n\"custom/mpd\": {\n\t\"format\": \"♪ {text}\",\n\t//\"max-length\": 15,\n\t\"interval\": 10,\n\t\"exec\": \"mpc current\",\n\t\"exec-if\": \"pgrep mpd\",\n\t\"on-click\": \"mpc toggle\",\n\t\"on-click-right\": \"sonata\"\n}\n```\n\n## cmus:\n\n```\n\"custom/cmus\": {\n\t\"format\": \"♪ {text}\",\n\t//\"max-length\": 15,\n\t\"interval\": 10,\n\t\"exec\": \"cmus-remote -C \\\"format_print '%a - %t'\\\"\", // artist - title\n\t\"exec-if\": \"pgrep cmus\",\n\t\"on-click\": \"cmus-remote -u\",                        //toggle pause\n\t\"escape\": true                                       //handle markup entities\n}\n```\n\n## Pacman\n\n```\n\n\"custom/pacman\": {\n\t\"format\": \"{text}  \",\n\t\"interval\": \"once\",\n\t\"exec\": \"pacman_packages\",\n\t\"on-click\": \"update-system\",\n\t\"signal\": 8\n}\n```\n\n## Alternate Pacman\n\n```\n\"custom/pacman\": {\n\t\"format\": \"{text}  \",\n\t\"exec\": \"checkupdates | wc -l\",       // # of updates\n\t\"exec-if\": \"exit 0\",                  // always run; consider advanced run conditions\n\t\"on-click\": \"termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar\", // update system\n\t\"signal\": 8\n}\n```\n\nUnder the premise that interval is not defined, you can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*.\n\n# STYLE\n\n- *#custom-<name>*\n- *#custom-<name>.<class>*\n- *<class>* can be set by the script. For more information see *return-type*\n"
  },
  {
    "path": "man/waybar-disk.5.scd",
    "content": "waybar-disk(5)\n\n# NAME\n\nwaybar - disk module\n\n# DESCRIPTION\n\nThe *disk* module displays the current disk space used.\n\n# CONFIGURATION\n\nAddressed by *disk*\n\n*path*: ++\n\ttypeof: string ++\n\tdefault: \"/\" ++\n\tAny path residing in the filesystem or mountpoint for which the information should be displayed.\n\n*interval*: ++\n\ttypeof: integer++\n\tdefault: 30 ++\n\tThe interval in which the information gets polled.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: \"{percentage_used}%\" ++\n\tThe format, how information should be displayed.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of disk utilization states that get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: \"{used} out of {total} used ({percentage_used}%)\" ++\n\tThe format of the information displayed in the tooltip.\n\n*unit*: ++\n\ttypeof: string ++\n\tUse with specific_free, specific_used, and specific_total to force calculation to always be in a certain unit. Accepts kB, kiB, MB, Mib, GB, GiB, TB, TiB.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{percentage_used}*: Percentage of disk in use.\n\n*{percentage_free}*: Percentage of free disk space\n\n*{total}*: Total amount of space on the disk, partition, or mountpoint. Automatically selects unit based on size remaining.\n\n*{used}*: Amount of used disk space. Automatically selects unit based on size remaining.\n\n*{free}*: Amount of available disk space for normal users. Automatically selects unit based on size remaining.\n\n*{path}*: The path specified in the configuration.\n\n*{specific_total}*: Total amount of space on the disk, partition, or mountpoint in a specific unit. Defaults to bytes.\n\n*{specific_used}*: Amount of used disk space in a specific unit. Defaults to bytes.\n\n*{specific_free}*: Amount of available disk space for normal users in a specific unit. Defaults to bytes.\n\n# EXAMPLES\n\n```\n\"disk\": {\n\t\"interval\": 30,\n\t\"format\": \"{percentage_free}% free on {path}\",\n}\n```\n\n```\n\"disk\": {\n\t\"interval\": 30,\n\t\"format\": \"{specific_free:0.2f} GB out of {specific_total:0.2f} GB available. Alternatively {free} out of {total} available\",\n\t\"unit\": \"GB\"\n\t// 1434.25 GB out of 2000.00 GB available. Alternatively 1.4TiB out of 1.9TiB available.\n}\n```\n\n# STYLE\n\n- *#disk*\n"
  },
  {
    "path": "man/waybar-dwl-tags.5.scd",
    "content": "waybar-dwl-tags(5)\n\n# NAME\n\nwaybar - dwl tags module\n\n# DESCRIPTION\n\nThe *tags* module displays the current state of tags in dwl.\n\n# CONFIGURATION\n\nAddressed by *dwl/tags*\n\n*num-tags*: ++\n\ttypeof: uint ++\n\tdefault: 9 ++\n\tThe number of tags that should be displayed. Max 32.\n\n*tag-labels*: ++\n\ttypeof: array ++\n\tThe label to display for each tag.\n\n*disable-click*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLE\n\n```\n\"dwl/tags\": {\n\t\"num-tags\": 5\n}\n```\n\n# STYLE\n\n- *#tags button*\n- *#tags button.occupied*\n- *#tags button.empty*\n- *#tags button.focused*\n- *#tags button.urgent*\n\nNote that occupied/focused/urgent status may overlap. That is, a tag may be\nboth occupied and focused at the same time.\n\n# SEE ALSO\n\nwaybar(5), dwl(1)\n"
  },
  {
    "path": "man/waybar-dwl-window.5.scd",
    "content": "waybar-dwl-window(5)\n\n# NAME\n\nwaybar - dwl window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in DWL\n\n# CONFIGURATION\n\nAddressed by *dwl/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information should be displayed.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite the module format output. See *rewrite rules*.\n\n*icon*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to hide the application icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tOption to change the size of the application icon.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{title}*: The title of the focused window.\n\n*{app_id}*: The app_id of the focused window.\n\n*{layout}*: The layout of the focused window.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the format output is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"dwl/window\": {\n\t\"format\": \"{}\",\n\t\"max-length\": 50,\n\t\"rewrite\": {\n\t\t\"(.*) - Mozilla Firefox\": \"🌎 $1\",\n\t\t\"(.*) - zsh\": \"> [$1]\"\n\t}\n}\n```\n"
  },
  {
    "path": "man/waybar-ext-workspaces.5.scd",
    "content": "waybar-wlr-workspaces(5)\n\n# NAME\n\nwaybar - wlr workspaces module\n\n# DESCRIPTION\n\nThe *workspaces* module displays the currently used workspaces in wayland compositor.\n\n# CONFIGURATION\n\nAddressed by *ext/workspaces*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {name} ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the workspace name and state, the corresponding icon gets selected. See *icons*.\n\n*sort-by-name*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tShould workspaces be sorted by name. Workspace names will be sorted numerically when all names are numbers.\n\n*sort-by-coordinates*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tShould workspaces be sorted by coordinates. ++\n\tNote that if both  *sort-by-name* and *sort-by-coordinates* are true sort-by name will be first. If both are false - sort by id will be performed.\n\n*sort-by-id*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tShould workspaces be sorted by ID. Workspace ID will be sorted numerically when all ID are numbers. Takes precedence over any other sort-by option.\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false workspaces group will be shown only in assigned output. Otherwise, all workspace groups are shown.\n\n*active-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true only active or urgent workspaces will be shown.\n\n*ignore-hidden*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIf set to false hidden workspaces will be shown.\n\n# FORMAT REPLACEMENTS\n\n*{name}*: Name of workspace assigned by compositor.\n\n*{id}*: ID of workspace assigned by compositor.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n# CLICK ACTIONS\n\n*activate*: Switch to workspace.\n\n*deactivate*: Deactivate the workspace.\n\n*close*: Close the workspace.\n\n# ICONS\n\nIn addition to workspace name matching, the following *format-icons* can be set.\n\n- *default*: Will be shown, when no string match is found.\n- *active*: Will be shown, when workspace is active\n\n# EXAMPLES\n\n```\n\"ext/workspaces\": {\n\t\"format\": \"{name}: {icon}\",\n\t\"on-click\": \"activate\",\n\t\"format-icons\": {\n\t\t\"Workspace 1\": \"\",\n\t\t\"Workspace 2\": \"\",\n\t\t\"Workspace 3\": \"\",\n\t\t\"Workspace 4\": \"\",\n\t\t\"active\": \"\",\n\t\t\"default\": \"\"\n\t},\n\t\"sort-by-id\": true\n}\n```\n\n# Style\n\n- *#workspaces*\n- *#workspaces button*\n- *#workspaces button.active*\n- *#workspaces button.urgent*\n- *#workspaces button.hidden*\n"
  },
  {
    "path": "man/waybar-gamemode.5.scd",
    "content": "waybar-gamemode(5)\n\n# NAME\n\nwaybar - gamemode module\n\n# DESCRIPTION\n\nThe *gamemode* module displays if any game or application is running with ++\nFeral Gamemode optimizations.\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {glyph} ++\n\tThe text format.\n\n*format-alt*: ++\n\ttypeof: string ++\n\tdefault: {glyph} {count} ++\n\tThe text format when toggled.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: Games running: {glyph} ++\n\tThe text format of the tooltip.\n\n*hide-not-running*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tDefines if the module should be hidden if no games are running.\n\n*use-icon*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tDefines if the module should display a GTK icon instead of the specified *glyph*\n\n*glyph*: ++\n\ttypeof: string ++\n\tdefault: 󰊴 ++\n\tThe string icon to display. Only visible if *use-icon* is set to false.\n\n*icon-name*: ++\n\ttypeof: string ++\n\tdefault: input-gaming-symbolic ++\n\tThe GTK icon to display. Only visible if *use-icon* is set to true.\n\n*icon-size*: ++\n\ttypeof: unsigned integer ++\n\tdefault: 20 ++\n\tDefines the size of the icons.\n\n*icon-spacing*: ++\n\ttypeof: unsigned integer ++\n\tdefault: 4 ++\n\tDefines the spacing between the icon and the text.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{glyph}*: The string icon glyph to use instead.\n\n*{count}*: The number of games running with gamemode optimizations.\n\n# TOOLTIP FORMAT REPLACEMENTS\n\n*{count}*: The number of games running with gamemode optimizations.\n\n# EXAMPLES\n\n```\n\"gamemode\": {\n\t\"format\": \"{glyph}\",\n\t\"format-alt\": \"{glyph} {count}\",\n\t\"glyph\": \"󰊴\",\n\t\"hide-not-running\": true,\n\t\"use-icon\": true,\n\t\"icon-name\": \"input-gaming-symbolic\",\n\t\"icon-spacing\": 4,\n\t\"icon-size\": 20,\n\t\"tooltip\": true,\n\t\"tooltip-format\": \"Games running: {count}\"\n}\n\n```\n\n# STYLE\n\n- *#gamemode*\n- *#gamemode.running*\n"
  },
  {
    "path": "man/waybar-gps.5.scd",
    "content": "waybar-gps(5) \"waybar-gps\" \"User Manual\"\n\n# NAME\n\nwaybar - gps module\n\n# DESCRIPTION\n\n*gps* module for gpsd.\n\n\n# FILES\n\n$XDG_CONFIG_HOME/waybar/config ++\n\tPer user configuration file\n\n# ADDITIONAL FILES\n\nlibgps lives in:\n\n. /usr/lib/libgps.so or /usr/lib64/libgps.so\n. /usr/lib/pkgconfig/libgps.pc or /usr/lib64/pkgconfig/libgps.pc\n. /usr/include/gps\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {glyph} ++\n\tThe text format.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: Games running: {glyph} ++\n\tThe text format of the tooltip.\n\n*interval*: ++\n\ttypeof: integer ++\n\tdefault: 5 ++\n\tThe interval in which the GPS information gets polled (e.g. current speed).\n\tSignificant updates (e.g. the current fix mode) are updated immediately.\n\n*hide-disconnected*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tDefines if the module should be hidden if there is no GPS receiver.\n\n*hide-no-fix*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDefines if the module should be hidden if there is no GPS fix.\n\n# FORMAT REPLACEMENTS\n\n*{mode}*: Fix mode\n\n*{status}*: Technology used for GPS fix. Not all GPS receivers report this.\n\n*{latitude}*: Latitude, decimal degrees. Can be NaN.\n\n*{latitude_error}*: Latitude uncertainty, meters. Can be NaN.\n\n*{longitude}*: Longitude, decimal degrees. Can be NaN.\n\n*{longitude_error}*: Longitude uncertainty, meters. Can be NaN.\n\n*{altitude_hae}*: Altitude, height above ellipsoid, meters. Can be NaN.\n\n*{altitude_msl}*: Longitude, MSL, meters. Can be NaN.\n\n*{altitude_error}*: Altitude uncertainty, meters. Can be NaN.\n\n*{speed}*: Speed over ground, meters/sec. Can be NaN.\n\n*{speed_error}*: Speed uncertainty, meters/sec. Can be NaN.\n\n*{climb}*: Vertical speed, meters/sec. Can be NaN.\n\n*{climb_error}*: Vertical speed uncertainty, meters/sec. Can be NaN.\n\n*{satellites_visible}*: Number of satellites visible from the GPS receiver.\n\n*{satellites_used}*: Number of satellites used for the GPS fix.\n\n# EXAMPLES\n\n```\n\"gps\": {\n\t\"format\": \"{mode}\",\n\t\"format-disabled\": \"\", // an empty format will hide the module\n\t\"format-no-fix\": \"No fix\",\n\t\"format-fix-3d\": \"{status}\",\n\t\"tooltip-format\": \"{mode}\",\n\t\"tooltip-format-no-fix\": \"{satellites_visible} satellites visible\",\n\t\"tooltip-format-fix-2d\": \"{satellites_used}/{satellites_visible} satellites used\",\n\t\"tooltip-format-fix-3d\": \"Altitude: {altitude_hae}m\",\n\t\"hide-disconnected\": false\n}\n```\n# STYLE\n\n- *#gps*\n- *#gps.disabled* Applied when GPS is disabled.\n- *#gps.fix-none* Applied when GPS is present, but there is no fix.\n- *#gps.fix-2d* Applied when there is a 2D fix.\n- *#gps.fix-3d* Applied when there is a 3D fix.\n"
  },
  {
    "path": "man/waybar-hyprland-language.5.scd",
    "content": "waybar-hyprland-language(5)\n\n# NAME\n\nwaybar - hyprland language module\n\n# DESCRIPTION\n\nThe *language* module displays the currently selected language.\n\n# CONFIGURATION\n\nAddressed by *hyprland/language*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how information should be displayed.\n\n*format-<lang>* ++\n\ttypeof: string++\n\tProvide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.\n\n*keyboard-name*: ++\n\ttypeof: string ++\n\tSpecifies which keyboard to use from hyprctl devices output. Using the option that begins with \"at-translated-set...\" is recommended.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n\n# FORMAT REPLACEMENTS\n\n*{short}*: Short name of layout (e.g. \"us\"). Equals to {}.\n\n*{shortDescription}*: Short description of layout (e.g. \"en\").\n\n*{long}*: Long name of layout (e.g. \"English (Dvorak)\").\n\n*{variant}*: Variant of layout (e.g. \"dvorak\").\n\n\n# EXAMPLES\n\n```\n\"hyprland/language\": {\n\t\"format\": \"Lang: {long}\",\n\t\"format-en\": \"AMERICA, HELL YEAH!\",\n\t\"format-tr\": \"As bayrakları\",\n\t\"keyboard-name\": \"at-translated-set-2-keyboard\"\n}\n```\n\n# STYLE\n\n- *#language*\n"
  },
  {
    "path": "man/waybar-hyprland-submap.5.scd",
    "content": "waybar-hyprland-submap(5)\n\n# NAME\n\nwaybar - hyprland submap module\n\n# DESCRIPTION\n\nThe *submap* module displays the currently active submap similar to *sway/mode*.\n\n# CONFIGURATION\n\nAddressed by *hyprland/submap*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how information should be displayed. On {} the currently active submap is displayed.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*always-on*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to display the widget even when there's no active submap.\n\n*default-submap* ++\n\ttypeof: string ++\n\tdefault: Default ++\n\tOption to set the submap name to display when not in an active submap.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n\n# EXAMPLES\n\n```\n\"hyprland/submap\": {\n\t\"format\": \"✌️ {}\",\n\t\"max-length\": 8,\n\t\"tooltip\": false\n}\n```\n\n# STYLE\n\n- *#submap*\n- *#submap.<name>*\n"
  },
  {
    "path": "man/waybar-hyprland-window.5.scd",
    "content": "waybar-hyprland-window(5)\n\n# NAME\n\nwaybar - hyprland window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in Hyprland.\n\n# CONFIGURATION\n\nAddressed by *hyprland/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information should be displayed. On {} the current window title is displayed.\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite window title. See *rewrite rules*.\n\n*separate-outputs*: ++\n\ttypeof: bool ++\n\tShow the active window of the monitor the bar belongs to, instead of the focused window.\n\n*icon*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to hide the application icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tOption to change the size of the application icon.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\nSee the output of \"hyprctl clients\" for examples\n\n*{title}*: The current title of the focused window.\n\n*{initialTitle}*: The initial title of the focused window.\n\n*{class}*: The current class of the focused window.\n\n*{initialClass}*: The initial class of the focused window.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the title is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"hyprland/window\": {\n\t\"format\": \"{}\",\n\t\"rewrite\": {\n\t\t\"(.*) - Mozilla Firefox\": \"🌎 $1\",\n\t\t\"(.*) - zsh\": \"> [$1]\"\n\t}\n}\n```\n\n# STYLE\n\n- *#window*\n- *window#waybar.empty #window* When no windows are in the workspace\n\nThe following classes are applied to the entire Waybar rather than just the\nwindow widget:\n\n- *window#waybar.empty* When no windows are in the workspace\n- *window#waybar.solo* When one tiled window is visible in the workspace\n  (floating windows may be present)\n- *window#waybar.<app_id>* Where *<app_id>* is the *class* (e.g. *chromium*) of\n  the solo tiled window in the workspace (use *hyprctl clients* to see classes)\n- *window#waybar.floating* When there are only floating windows in the workspace\n- *window#waybar.fullscreen* When there is a fullscreen window in the workspace;\n  useful with Hyprland's *fullscreen, 1* mode\n- *window#waybar.swallowing* When there is a swallowed window in the workspace\n"
  },
  {
    "path": "man/waybar-hyprland-windowcount.5.scd",
    "content": "waybar-hyprland-windowcount(5)\n\n# NAME\n\nwaybar - hyprland window count module\n\n# DESCRIPTION\n\nThe *windowcount* module displays the number of windows in the current Hyprland workspace.\n\n# CONFIGURATION\n\nAddressed by *hyprland/windowcount*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format for how information should be displayed. On {} the current workspace window count is displayed.\n\n*format-empty*: ++\n\ttypeof: string ++\n\tOverride the format when the workspace contains no windows window\n\n*format-windowed*: ++\n\ttypeof: string ++\n\tOverride the format when the workspace contains no fullscreen windows\n\n*format-fullscreen*: ++\n\ttypeof: string ++\n\tOverride the format when the workspace contains a fullscreen window\n\n*separate-outputs*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tShow the active workspace window count of the monitor the bar belongs to, instead of the focused workspace.\n\n# STYLE\n\n- *#windowcount*\n\nThe following classes are applied to the entire Waybar rather than just the\nwindowcount widget:\n\n- *window#waybar.empty* When no windows are in the workspace\n- *window#waybar.fullscreen* When there is a fullscreen window in the workspace;\n  useful with Hyprland's *fullscreen, 1* mode\n"
  },
  {
    "path": "man/waybar-hyprland-workspaces.5.scd",
    "content": "waybar-hyprland-workspaces(5)\n\n# NAME\n\nwaybar - hyprland workspaces module\n\n# DESCRIPTION\n\nThe *workspaces* module displays the currently used workspaces in hyprland compositor.\n\n# CONFIGURATION\n\nAddressed by *hyprland/workspaces*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {id} ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the workspace ID and state, the corresponding icon gets selected. See *icons*.\n\n*window-rewrite*: ++\n\ttypeof: object ++\n\tRegex rules to map window class to an icon or preferred method of representation for a workspace's window.\n\tKeys are the rules, while the values are the methods of representation. Values may use the placeholders {class} and {title} to use the window's original class and/or title respectively.\n\tRules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching.\n\tYou may assign an empty value to a rule to have it ignored from generating any representation in workspaces. ++\nThis setting is ignored if *workspace-taskbar.enable* is set to true.\n\n*window-rewrite-default*: ++\n\ttypeof: string ++\n\tdefault: \"?\" ++\n\tThe default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. ++\n\tThis setting is ignored if *workspace-taskbar.enable* is set to true.\n\n*format-window-separator*: ++\n\ttypeof: string ++\n\tdefault: \" \" ++\n\tThe separator to be used between windows in a workspace. ++\n\tThis setting is ignored if *workspace-taskbar.enable* is set to true.\n\n*workspace-taskbar*: ++\n\ttypeof: object ++\n\tContains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.\n\t\n\t\t*enable*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables the workspace taskbar mode.\n\n\t\t*update-active-window*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws.\n\n\t\t*reverse-direction*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical).\n\n\t\t*active-window-position*: ++\n\ttypeof: \"none\" | \"first\" | \"last\" ++\n\tdefault: \"none\" ++\n\tIf set to \"first\", the active window will be moved at the beginning of the taskbar. If set to \"last\", it will be moved at the end. It will only work if *update-active-window* is set to true.\n\n\t\t*format*: ++\n\ttypeof: string ++\n\tdefault: {icon} ++\n\tFormat to use for each window in the workspace taskbar. Available placeholders are {icon} and {title}.\n\n\t\t*icon-size*: ++\n\ttypeof: int ++\n\tdefault: 16 ++\n\tSize of the icons in the workspace taskbar.\n\n\t\t*icon-theme*: ++\n\ttypeof: string | array ++\n\tdefault: [] ++\n\tIcon theme to use for the workspace taskbar. If an array is provided, the first theme that is found for a given icon will be used. If no theme is found (or the array is empty), the default icon theme is used.\n\n\t\t*orientation*: ++\n\ttypeof: \"horizontal\" | \"vertical\" ++\n\tdefault: horizontal ++\n\tDirection in which the workspace taskbar is displayed.\n\n\t\t*ignore-list*: ++\n\ttypeof: array ++\n\tdefault: [] ++\n\tRegex patterns to match against window class or window title. If a window's class OR title matches any of the patterns, it will not be shown.\n\n\t\t*on-click-window*: ++\n\ttypeof: string ++\n\tdefault: \"\" ++\n\tCommand to run when a window is clicked. Available placeholders are: ++\n\t  - {address} Hyprland address of the clicked window. ++\n\t  - {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++\n\tSee https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example.\n\n*show-special*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, special workspaces will be shown.\n\n*special-visible-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf this and show-special are to true, special workspaces will be shown only if visible.\n\n*persistent-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, only persistent workspaces will be shown on bar.\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false workspaces group will be shown only in assigned output. Otherwise, all workspace groups are shown.\n\n*active-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, only the active workspace will be shown.\n\n*move-to-monitor*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, open the workspace on the current monitor when clicking on a workspace button.\n\tOtherwise, the workspace will open on the monitor where it was previously assigned.\n\tAnalog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland.\n\n*enable-bar-scroll*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.\n\n*ignore-workspaces*: ++\n\ttypeof: array ++\n\tdefault: [] ++\n\tRegexes to match against workspaces names. If there's a match, the workspace will not be shown. This takes precedence over *show-special*, *all-outputs*, and *active-only*.\n\n*sort-by*: ++\n\ttypeof: string ++\n\tdefault: \"default\" ++\n\tIf set to number, workspaces will sort by number.\n\tIf set to name, workspaces will sort by name.\n\tIf set to id, workspaces will sort by id.\n\tIf set to special-centered, workspaces will sort by default with special workspaces in the center.\n\tIf none of those, workspaces will sort with default behavior.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{id}*: id of workspace assigned by compositor\n\n*{name}*: workspace name assigned by compositor\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n# ICONS\n\nAdditional to workspace name matching, the following *format-icons* can be set.\n\n- *default*: Will be shown, when no string match is found and none of the below conditions have defined icons.\n- *active*: Will be shown, when workspace is active\n- *special*: Will be shown on non-active special workspaces\n- *empty*: Will be shown on non-active, non-special empty persistent workspaces\n- *visible*: Will be shown on workspaces that are visible but not active. For example: this is useful if you want your visible workspaces on other monitors to have the same look as active.\n- *persistent*: Will be shown on non-empty persistent workspaces\n\n# EXAMPLES\n\n```\n\"hyprland/workspaces\": {\n\t\"format\": \"{name}: {icon}\",\n\t\"format-icons\": {\n\t\t\"1\": \"\",\n\t\t\"2\": \"\",\n\t\t\"3\": \"\",\n\t\t\"4\": \"\",\n\t\t\"5\": \"\",\n\t\t\"active\": \"\",\n\t\t\"default\": \"\"\n\t},\n\t\"persistent-workspaces\": {\n\t\t\"*\": 5, // 5 workspaces by default on every monitor\n\t\t\"HDMI-A-1\": 3 // but only three on HDMI-A-1\n\t}\n}\n```\n\n```\n\"hyprland/workspaces\": {\n\t\"format\": \"{name}: {icon}\",\n\t\"format-icons\": {\n\t\t\"1\": \"\",\n\t\t\"2\": \"\",\n\t\t\"3\": \"\",\n\t\t\"4\": \"\",\n\t\t\"5\": \"\",\n\t\t\"active\": \"\",\n\t\t\"default\": \"\"\n\t},\n\t\"persistent-workspaces\": {\n\t\t\"*\": [ 2,3,4,5 ], // 2-5 on every monitor\n\t\t\"HDMI-A-1\": [ 1 ] // but only workspace 1 on HDMI-A-1\n\t}\n}\n```\n\n```\n\"hyprland/workspaces\": {\n\t\"format\": \"{name}\\n{windows}\",\n\t\"format-window-separator\": \"\\n\",\n\t\"window-rewrite-default\": \"\",\n\t\"window-rewrite\": {\n\t\t\"title<.*youtube.*>\": \"\", // Windows whose titles contain \"youtube\"\n\t\t\"class<firefox>\": \"\", // Windows whose classes are \"firefox\"\n\t\t\"class<firefox> title<.*github.*>\": \"\", // Windows whose class is \"firefox\" and title contains \"github\". Note that \"class\" always comes first.\n\t\t\"foot\": \"\", // Windows that contain \"foot\" in either class or title. For optimization reasons, it will only match against a title if at least one other window explicitly matches against a title.\n\t\t\"code\": \"󰨞\",\n\t\t\"title<.* - (.*) - VSCodium>\": \"codium $1\"  // captures part of the window title and formats it into output\n\t}\n}\n```\n\n```\n\"hyprland/workspaces\": {\n\t// Formatting omitted for brevity\n\t\"ignore-workspaces\": [\n\t\t\"(special:)?chrome-sharing-indicator\"\n\t]\n}\n```\n\n# Style\n\n- *#workspaces*\n- *#workspaces button*\n- *#workspaces button.active*\n- *#workspaces button.empty*\n- *#workspaces button.visible*\n- *#workspaces button.persistent*\n- *#workspaces button.special*\n- *#workspaces button.urgent*\n- *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor)\n- *#workspaces .workspace-label*\n- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true)\n- *#workspaces .taskbar-window.active* (applied to the focused window, only if 'workspace-taskbar.update-active-window' is true)\n"
  },
  {
    "path": "man/waybar-idle-inhibitor.5.scd",
    "content": "waybar-idle-inhibitor(5)\n\n# NAME\n\nwaybar - idle_inhibitor module\n\n# DESCRIPTION\n\nThe *idle_inhibitor* module can inhibit the idle behavior such as screen blanking, locking, and\nscreensaver, also known as \"presentation mode\".\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string ++\n\tThe format, how the state should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the current state, the corresponding icon gets selected.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module. A click also toggles the state\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*start-activated*: ++\n\ttypeof: bool ++\n\tdefault: *false* ++\n\tWhether the inhibit should be activated when starting waybar.\n\n*timeout*: ++\n\ttypeof: double ++\n\tThe number of minutes the inhibition should last.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format-activated*: ++\n\ttypeof: string ++\n\tThis format is used when the inhibit is activated.\n\n*tooltip-format-deactivated*: ++\n\ttypeof: string ++\n\tThis format is used when the inhibit is deactivated.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu. Cannot be \"on-click\".\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{status}*: status (*activated* or *deactivated*)\n\n*{icon}*: Icon, as defined in *format-icons*\n\n# EXAMPLES\n\n```\n\"idle_inhibitor\": {\n\t\"format\": \"{icon}\",\n\t\"format-icons\": {\n\t\t\"activated\": \"\",\n\t\t\"deactivated\": \"\"\n\t},\n\t\"timeout\": 30.5\n}\n```\n\n# STYLE\n\n- *#idle_inhibitor*\n- *#idle_inhibitor.activated*\n- *#idle_inhibitor.deactivated*\n"
  },
  {
    "path": "man/waybar-image.5.scd",
    "content": "waybar-image(5)\n\n# NAME\n\nwaybar - image module\n\n# DESCRIPTION\n\nThe *image* module displays an image from a path.\n\n# CONFIGURATION\n\n*path*: ++\n\ttypeof: string ++\n\tThe path to the image.\n\n*exec*: ++\n\ttypeof: string ++\n\tThe path to the script, which should return image path file. ++\n\tIt will only execute if the path is not set\n\n*size*: ++\n\ttypeof: integer ++\n\tThe width/height to render the image.\n\n*interval*: ++\n\ttypeof: integer or float ++\n\tThe interval (in seconds) to re-render the image. ++\n\tIf set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++\n\tZero or negative values are treated as \"once\". ++\n\tThis is useful if the contents of *path* changes. ++\n\tIf no *interval* is defined, the image will only be rendered once.\n\n*signal*: ++\n\ttypeof: integer ++\n\tThe signal number used to update the module. ++\n\tThis can be used instead of *interval* if the file changes irregularly. ++\n\tThe number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to enable tooltip on hover.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# SCRIPT OUTPUT\n\nSimilar to the *custom* module, output values of the script are *newline* separated.\nThe following is the output format:\n\n```\n$path\\\\n$tooltip\n```\n\n# EXAMPLES\n\n```\n\"image#album-art\": {\n\t\"path\": \"/tmp/mpd_art\",\n\t\"size\": 32,\n\t\"interval\": 5,\n\t\"on-click\": \"mpc toggle\"\n}\n```\n\n# STYLE\n\n- *#image*\n- *#image.empty*\n"
  },
  {
    "path": "man/waybar-inhibitor.5.scd",
    "content": "waybar-inhibitor(5)\n\n# NAME\n\nwaybar - inhibitor module\n\n# DESCRIPTION\n\nThe *inhibitor* module allows one to take an inhibitor lock that logind provides.\nSee *systemd-inhibit*(1) for more information.\n\n# CONFIGURATION\n\n*what*: ++\n\ttypeof: string or array ++\n\tThe inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.\n\n*format*: ++\n\ttypeof: string ++\n\tThe format, how the state should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the current state, the corresponding icon gets selected.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module. A click also toggles the state\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu. Cannot be \"on-click\".\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{status}*: status (*activated* or *deactivated*)\n\n*{icon}*: Icon, as defined in *format-icons*\n\n# EXAMPLES\n\n```\n\"inhibitor\": {\n\t\"what\": \"handle-lid-switch\",\n\t\"format\": \"{icon}\",\n\t\"format-icons\": {\n\t\t\"activated\": \"\",\n\t\t\"deactivated\": \"\"\n\t}\n}\n```\n"
  },
  {
    "path": "man/waybar-jack.5.scd",
    "content": "waybar-jack(5)\n\n# NAME\n\nwaybar - JACK module\n\n# DESCRIPTION\n\nThe *jack* module displays the current state of the JACK server.\n\n# CONFIGURATION\n\nAddressed by *jack*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: *{load}%* ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-connected*: ++\n\ttypeof: string ++\n\tThis format is used when the module is connected to the JACK server.\n\n*format-disconnected*: ++\n\ttypeof: string ++\n\tThis format is used when the module is not connected to the JACK server.\n\n*format-xrun*: ++\n\ttypeof: string ++\n\tThis format is used for one polling interval when the JACK server reports an xrun.\n\n*realtime*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to drop real-time privileges for the JACK client opened by Waybar.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: *{bufsize}/{samplerate} {latency}ms* ++\n\tThe format of information displayed in the tooltip.\n\n*interval*: ++\n\ttypeof: integer or float ++\n\tdefault: 1 ++\n\tThe interval in which the information gets polled.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{load}*: The current CPU load estimated by JACK.\n\n*{bufsize}*: The size of the JACK buffer.\n\n*{samplerate}*: The sample rate at which the JACK server is running.\n\n*{latency}*: The duration, in ms, of the current buffer size.\n\n*{xruns}*: The number of xruns reported by the JACK server since starting Waybar.\n\n# EXAMPLES\n\n```\n\"jack\": {\n\t\"format\": \"DSP {}%\",\n\t\"format-xrun\": \"{xruns} xruns\",\n\t\"format-disconnected\": \"DSP off\",\n\t\"realtime\": true\n}\n```\n\n# STYLE\n\n- *#jack*\n- *#jack.connected*\n- *#jack.disconnected*\n- *#jack.xrun*\n"
  },
  {
    "path": "man/waybar-keyboard-state.5.scd",
    "content": "waybar-keyboard-state(5)\n\n# NAME\n\nwaybar - keyboard-state module\n\n# DESCRIPTION\n\nThe *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.\n\nYou must be a member of the input group to use this module.\n\n# CONFIGURATION\n\n*interval*: ++\n\tDeprecated, this module uses event loop now, the interval has no effect.\n\ttypeof: integer ++\n\tdefault: 1 ++\n\tThe interval, in seconds, to poll the keyboard state.\n\n*format*: ++\n\ttypeof: string|object ++\n\tdefault: {name} {icon} ++\n\tThe format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields \"numlock\", \"capslock\", and \"scrolllock\" each specify the format for the corresponding state. Any unspecified states use the default format.\n\n*format-icons*: ++\n\ttypeof: object ++\n\tdefault: {\"locked\": \"locked\", \"unlocked\": \"unlocked\"} ++\n\tBased on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.\n\n*numlock*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDisplay the number lock state.\n\n*capslock*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDisplay the caps lock state.\n\n*scrolllock*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDisplay the scroll lock state.\n\n*device-path*: ++\n\ttypeof: string ++\n\tdefault: chooses first valid input device ++\n\tWhich libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events.\n\n*binding-keys*: ++\n\ttypeof: array ++\n\tdefault: [58, 69, 70] ++\n\tCustomize the key to trigger this module, the key number can be found in /usr/include/linux/input-event-codes.h or running sudo libinput debug-events --show-keycodes.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{name}*: Caps, Num, or Scroll.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n# ICONS\n\nThe following *format-icons* can be set.\n\n- *locked*: Will be shown when the keyboard state is locked. Default \"locked\".\n- *unlocked*: Will be shown when the keyboard state is not locked. Default \"unlocked\"\n\n# EXAMPLE:\n\n```\n\"keyboard-state\": {\n\t\"numlock\": true,\n\t\"capslock\": true,\n\t\"format\": \"{name} {icon}\",\n\t\"format-icons\": {\n\t\t\"locked\": \"\",\n\t\t\"unlocked\": \"\"\n\t}\n}\n```\n\n# STYLE\n\n- *#keyboard-state*\n- *#keyboard-state label*\n- *#keyboard-state label.locked*\n- *#keyboard-state label.numlock*\n- *#keyboard-state label.numlock.locked*\n- *#keyboard-state label.capslock*\n- *#keyboard-state label.capslock.locked*\n- *#keyboard-state label.scrolllock*\n- *#keyboard-state label.scrolllock.locked*\n"
  },
  {
    "path": "man/waybar-memory.5.scd",
    "content": "waybar-memory(5)\n\n# NAME\n\nwaybar - memory module\n\n# DESCRIPTION\n\nThe *memory* module displays the current memory utilization.\n\n# CONFIGURATION\n\nAddressed by *memory*\n\n*interval*: ++\n\ttypeof: integer++\n\tdefault: 30 ++\n\tThe interval in which the information gets polled.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {percentage}% ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current percentage, the corresponding icon gets selected. ++\n\tThe order is *low* to *high*. Or by the state if it is an object.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{percentage}*: Percentage of memory in use.\n\n*{swapPercentage}*: Percentage of swap in use.\n\n*{total}*: Amount of total memory available in GiB.\n\n*{swapTotal}*: Amount of total swap available in GiB.\n\n*{used}*: Amount of used memory in GiB.\n\n*{swapUsed}*: Amount of used swap in GiB.\n\n*{avail}*: Amount of available memory in GiB.\n\n*{swapAvail}*: Amount of available swap in GiB.\n\n*{swapState}*: Signals if swap is activated or not\n\n# EXAMPLES\n\n```\n\"memory\": {\n\t\"interval\": 30,\n\t\"format\": \"{}% \",\n\t\"max-length\": 10\n}\n```\n\n## FORMATTED MEMORY VALUES\n\n```\n\"memory\": {\n\t\"interval\": 30,\n\t\"format\": \"{used:0.1f}G/{total:0.1f}G \"\n}\n```\n\n# STYLE\n\n- *#memory*\n"
  },
  {
    "path": "man/waybar-menu.5.scd",
    "content": "waybar-menu(5)\n\n# NAME\n\nwaybar - menu property\n\n# OVERVIEW\n\n\nSome modules support a 'menu', which allows to have a popup menu when a defined\nclick is done over the module.\n\n# PROPERTIES\n\nA module that implements a 'menu' needs 3 properties defined in its config :\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu. The possibles actions are :\n\n[- *Option*\n:- *Description*\n|[ *on-click*\n:< When you left-click on the module\n|[ *on-click-release*\n:< When you release left button on the module\n|[ *on-double-click*\n:< When you double left click on the module\n|[ *on-triple-click*\n:< When you triple left click on the module\n|[ *on-click-middle*\n:< When you middle click on the module using mousewheel\n|[ *on-click-middle-release*\n:< When you release mousewheel button on the module\n|[ *on-double-click-middle*\n:< When you double middle click on the module\n|[ *on-triple-click-middle*\n:< When you triple middle click on the module\n|[ *on-click-right*\n:< When you right click on the module using\n|[ *on-click-right-release*\n:< When you release right button on the module\n|[ *on-double-click-right*\n:< When you double right click on the module\n|[ *on-triple-click-right*\n:< When you triple middle click on the module\n|[ *on-click-backward*\n:< When you  click on the module using mouse backward button\n|[ *on-click-backward-release*\n:< When you release mouse backward button on the module\n|[ *on-double-click-backward*\n:< When you double click on the module using mouse backward button\n|[ *on-triple-click-backward*\n:< When you triple click on the module using mouse backawrd button\n|[ *on-click-forward*\n:< When you  click on the module using mouse forward button\n|[ *on-click-forward-release*\n:< When you release mouse forward button on the module\n|[ *on-double-click-forward*\n:< When you double click on the module using mouse forward button\n|[ *on-triple-click-forward*\n:< When you triple click on the module using mouse forward button\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*.\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu. The identifiers of\n\teach actions needs to exists as an id in the 'menu-file' for it to be linked\n\tproperly.\n\n# MENU-FILE\n\nThe menu-file is an `.xml` file representing a GtkBuilder. Documentation for it\ncan be found here : https://docs.gtk.org/gtk4/class.Builder.html\n\nHere, it needs to have an element of type GtkMenu with id \"menu\". Eeach actions\nin *menu-actions* are linked to elements in the *menu-file* file by the id of\nthe elements.\n\n# EXAMPLE\n\nModule config :\n```\n\"custom/power\": {\n\t\"format\" : \"⏻ \",\n\t\"tooltip\": false,\n\t\"menu\": \"on-click\",\n\t\"menu-file\": \"~/.config/waybar/power_menu.xml\",\n\t\"menu-actions\": {\n\t\t\"shutdown\": \"shutdown\",\n\t\t\"reboot\": \"reboot\",\n\t\t\"suspend\": \"systemctl suspend\",\n\t\t\"hibernate\": \"systemctl hibernate\",\n\t},\n},\n```\n\n~/.config/waybar/power_menu.xml :\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<interface>\n  <object class=\"GtkMenu\" id=\"menu\">\n\t\t<child>\n\t\t\t<object class=\"GtkMenuItem\" id=\"suspend\">\n\t\t\t\t<property name=\"label\">Suspend</property>\n\t\t\t</object>\n\t\t</child>\n\t\t<child>\n\t\t\t<object class=\"GtkMenuItem\" id=\"hibernate\">\n\t\t\t\t<property name=\"label\">Hibernate</property>\n\t\t\t</object>\n\t\t</child>\n    <child>\n\t\t\t<object class=\"GtkMenuItem\" id=\"shutdown\">\n\t\t\t\t<property name=\"label\">Shutdown</property>\n\t\t\t</object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"delimiter1\"/>\n    </child>\n    <child>\n\t\t\t<object class=\"GtkMenuItem\" id=\"reboot\">\n\t\t\t\t<property name=\"label\">Reboot</property>\n\t\t\t</object>\n    </child>\n  </object>\n</interface>\n```\n\n# STYLING MENUS\n\n- *menu*\n  Style for the menu\n\n- *menuitem*\n  Style for items in the menu\n\n# EXAMPLE:\n\n```\nmenu {\n\tborder-radius: 15px;\n\tbackground: #161320;\n\tcolor: #B5E8E0;\n}\nmenuitem {\n\tborder-radius: 15px;\n}\n```\n"
  },
  {
    "path": "man/waybar-mpd.5.scd",
    "content": "waybar-mpd(5)\n\n# NAME\n\nwaybar - mpd module\n\n# DESCRIPTION\n\nThe *mpd* module displays information about a running \"Music Player Daemon\" instance.\n\n# CONFIGURATION\n\nAddressed by *mpd*\n\n*server*: ++\n\ttypeof: string ++\n\tThe network address or Unix socket path of the MPD server. If empty, connect to the default host.\n\n*port*: ++\n\ttypeof: integer ++\n\tThe port MPD listens to. If empty, use the default port.\n\n*password*: ++\n\ttypeof: string ++\n\tThe password required to connect to the MPD server. If empty, no password is sent to MPD.\n\n*interval*: ++\n\ttypeof: integer++\n\tdefault: 5 ++\n\tThe interval in which the connection to the MPD server is retried\n\n*timeout*: ++\n\ttypeof: integer++\n\tdefault: 30 ++\n\tThe timeout for the connection. Change this if your MPD server has a low `connection_timeout` setting\n\n*unknown-tag*: ++\n\ttypeof: string ++\n\tdefault: \"N/A\" ++\n\tThe text to display when a tag is not present in the current song, but used in `format`\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: \"{album} - {artist} - {title}\" ++\n\tInformation displayed when a song is playing.\n\n*format-stopped*: ++\n\ttypeof: string ++\n\tdefault: \"stopped\" ++\n\tInformation displayed when the player is stopped.\n\n*format-paused*: ++\n\ttypeof: string ++\n\tThis format is used when a song is paused.\n\n*format-disconnected*: ++\n\ttypeof: string ++\n\tdefault: \"disconnected\" ++\n\tInformation displayed when the MPD server can't be reached.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: \"MPD (connected)\" ++\n\tTooltip information displayed when connected to MPD.\n\n*tooltip-format-disconnected*: ++\n\ttypeof: string ++\n\tdefault: \"MPD (disconnected)\" ++\n\tTooltip information displayed when the MPD server can't be reached.\n\n*artist-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Artist tag.\n\n*album-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Album tag.\n\n*album-artist-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Album Artist tag.\n\n*title-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Title tag.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*state-icons*: ++\n\ttypeof: object ++\n\tdefault: {} ++\n\tIcon to show depending on the play/pause state of the player (*{ \"playing\": \"...\", \"paused\": \"...\" }*)\n\n*consume-icons*: ++\n\ttypeof: object ++\n\tdefault: {} ++\n\tIcon to show depending on the \"consume\" option (*{ \"on\": \"...\", \"off\": \"...\" }*)\n\n*random-icons*: ++\n\ttypeof: object ++\n\tdefault: {} ++\n\tIcon to show depending on the \"random\" option (*{ \"on\": \"...\", \"off\": \"...\" }*)\n\n*repeat-icons*: ++\n\ttypeof: object ++\n\tdefault: {} ++\n\tIcon to show depending on the \"repeat\" option (*{ \"on\": \"...\", \"off\": \"...\" }*)\n\n*single-icons*: ++\n\ttypeof: object ++\n\tdefault: {} ++\n\tIcon to show depending on the \"single\" option (*{ \"on\": \"...\", \"off\": \"...\" }*)\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n## WHEN PLAYING/PAUSED\n\n*{artist}*: The artist of the current song\n\n*{albumArtist}*: The artist of the current album\n\n*{album}*: The album of the current song\n\n*{title}*: The title of the current song\n\n*{date}*: The date of the current song\n\n*{volume}*: The current volume in percent\n\n*{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration)\n\n*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)\n\n*{songPosition}*: The position of the current song.\n\n*{queueLength}*: The length of the current queue.\n\n*{uri}*: The URI of the song relative to the MPD music directory.\n\n*{filename}* The last part of the URI.\n\n*{stateIcon}*: The icon corresponding to the playing or paused status of the player (see *state-icons* option)\n\n*{consumeIcon}*: The icon corresponding the \"consume\" option (see *consume-icons* option)\n\n*{randomIcon}*: The icon corresponding the \"random\" option (see *random-icons* option)\n\n*{repeatIcon}*: The icon corresponding the \"repeat\" option (see *repeat-icons* option)\n\n*{singleIcon}*: The icon corresponding the \"single\" option (see *single-icons* option)\n\n\n## WHEN STOPPED\n\n*{consumeIcon}*: The icon corresponding the \"consume\" option (see *consume-icons* option)\n\n*{randomIcon}*: The icon corresponding the \"random\" option (see *random-icons* option)\n\n*{repeatIcon}*: The icon corresponding the \"repeat\" option (see *repeat-icons* option)\n\n*{singleIcon}*: The icon corresponding the \"single\" option (see *single-icons* option)\n\n## WHEN DISCONNECTED\n\nCurrently, no format replacements when disconnected.\n\n# EXAMPLES\n\n```\n\"mpd\": {\n\t\"format\": \"{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) \",\n\t\"format-disconnected\": \"Disconnected \",\n\t\"format-stopped\": \"{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped \",\n\t\"interval\": 2,\n\t\"consume-icons\": {\n\t\t\"on\": \" \" // Icon shows only when \"consume\" is on\n\t},\n\t\"random-icons\": {\n\t\t\"off\": \"<span color=\\\"#f53c3c\\\"></span> \", // Icon grayed out when \"random\" is off\n\t\t\"on\": \" \"\n\t},\n\t\"repeat-icons\": {\n\t\t\"on\": \" \"\n\t},\n\t\"single-icons\": {\n\t\t\"on\": \"1 \"\n\t},\n\t\"state-icons\": {\n\t\t\"paused\": \"\",\n\t\t\"playing\": \"\"\n\t},\n\t\"tooltip-format\": \"MPD (connected)\",\n\t\"tooltip-format-disconnected\": \"MPD (disconnected)\"\n}\n```\n\n# STYLE\n\n- *#mpd*\n- *#mpd.disconnected*\n- *#mpd.stopped*\n- *#mpd.playing*\n- *#mpd.paused*\n"
  },
  {
    "path": "man/waybar-mpris.5.scd",
    "content": "waybar-mpris(5)\n\n# NAME\n\nwaybar - MPRIS module\n\n# DESCRIPTION\n\nThe *mpris* module displays currently playing media via libplayerctl.\n\n# CONFIGURATION\n\n*player*: ++\n\ttypeof: string ++\n\tdefault: playerctld ++\n\tName of the MPRIS player to attach to. Using the default value always follows the currently active player.\n\n*ignored-players*: ++\n\ttypeof: []string ++\n\tIgnore updates of the listed players, when using playerctld.\n\n*interval*: ++\n\ttypeof: integer ++\n\tdefault: 0 ++\n\tRefresh MPRIS information on a timer.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {player} ({status}) {dynamic} ++\n\tThe text format.\n\n*format-[status]*: ++\n\ttypeof: string ++\n\tThe status-specific text format.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: {player} ({status}) {dynamic} ++\n\tThe tooltip text format.\n\n*tooltip-format-[status]*: ++\n\ttypeof: string ++\n\tThe status-specific tooltip format.\n\n*artist-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Artist tag (Wide/Fullwidth Unicode characters count as two). Set to zero to hide the artist in `{dynamic}` tag.\n\n*album-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Album tag (Wide/Fullwidth Unicode characters count as two). Set to zero to hide the album in `{dynamic}` tag.\n\n*title-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Title tag (Wide/Fullwidth Unicode characters count as two). Set to zero to hide the title in `{dynamic}` tag.\n\n*dynamic-len*: ++\n\ttypeof: integer ++\n\tMaximum length of the Dynamic tag (Wide/Fullwidth Unicode characters ++\n\tcount as two). The dynamic tag will not truncate any tags beyond their ++\n\tset length limits, instead, it will attempt to fit as much of the ++\n\tavailable tags as possible. It is recommended you set title-len to ++\n\tsomething less than or equal to this value, so the title will always be ++\n\tdisplayed.\n\n*dynamic-order*: ++\n\ttypeof: []string ++\n\tdefault: [\"title\", \"artist\", \"album\", \"position\", \"length\"] ++\n\tOrder of the tags shown by Dynamic tag. The position and length tags ++\n\twill always be combined in the format [{position}/{length}]. The order ++\n\tof these tags in relation to other tags will be determined based on the ++\n\tdeclaration of the first among the two tags. Absence in this list means ++\n\tforce exclusion.\n\n*dynamic-importance-order*: ++\n\ttypeof: []string ++\n\tdefault: [\"title\", \"artist\", \"album\", \"position\", \"length\"] ++\n\tPriority of the tags when truncating the Dynamic tag. The final ones ++\n\twill be the first to be truncated. Absence in this list means force ++\n\tinclusion.\n\n*dynamic-separator*: ++\n\ttypeof: string ++\n\tdefault: \" - \" ++\n\tThese characters will be used to separate two different tags, except ++\n\twhen one of these tags is position and length.\n\n*truncate-hours*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tWhether to hide hours when media duration is less than an hour long.\n\n*enable-tooltip-len-limits*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to enable the length limits for the tooltip as well. By default, the tooltip ignores all length limits.\n\n*ellipsis*: ++\n\ttypeof: string ++\n\tdefault: \"…\" ++\n\tThis character will be used when any of the tags exceed their maximum length. If you don't want to use an ellipsis, set this to empty string.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tdefault: play-pause ++\n\tOverwrite default action toggles.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tdefault: previous track ++\n\tOverwrite default action toggles.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tdefault: next track ++\n\tOverwrite default action toggles.\n\n*player-icons*: ++\n\ttypeof: map[string]string ++\n\tAllows setting _{player_icon}_ based on player-name property.\n\n*status-icons*: ++\n\ttypeof: map[string]string ++\n\tAllows setting _{status_icon}_ based on player status (playing, paused, stopped).\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n\n# FORMAT REPLACEMENTS\n\n*{player}*: The name of the current media player\n\n*{status}*: The current status (playing, paused, stopped)\n\n*{artist}*: The artist of the current track\n\n*{album}*: The album title of the current track\n\n*{title}*: The title of the current track\n\n*{length}*: Length of the track, formatted as HH:MM:SS\n\n*{dynamic}*: Use _{artist}_, _{album}_, _{title}_ and _{length}_, automatically omit++\n\tempty values\n\n*{player_icon}*: Chooses an icon from _player-icons_ based on _{player}_\n\n*{status_icon}*: Chooses an icon from _status-icons_ based on _{status}_\n\n# EXAMPLES\n\n```\n\"mpris\": {\n\t\"format\": \"{player_icon} {dynamic}\",\n\t\"format-paused\": \"{status_icon} <i>{dynamic}</i>\",\n\t\"player-icons\": {\n\t\t\"default\": \"▶\",\n\t\t\"mpv\": \"🎵\"\n\t},\n\t\"status-icons\": {\n\t\t\"paused\": \"⏸\"\n\t},\n\t// \"ignored-players\": [\"firefox\"]\n}\n```\n\n# STYLE\n\n- *#mpris*\n- *#mpris.${status}*\n- *#mpris.${player}*\n"
  },
  {
    "path": "man/waybar-network.5.scd",
    "content": "waybar-network(5)\n\n# NAME\n\nwaybar - network module\n\n# DESCRIPTION\n\nThe *network* module displays information about the current network connections.\n\n# CONFIGURATION\n\nAddressed by *network*\n\n*interface*: ++\n\ttypeof: string ++\n\tUse the defined interface instead of auto-detection. Accepts wildcard.\n\n*rfkill*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIf enabled, the *disabled* format will be used when rfkill is blocking wlan interfaces.\n\n*interval*: ++\n\ttypeof: integer ++\n\tdefault: 60 ++\n\tThe interval in which the network information gets polled (e.g. signal strength).\n\n*family*: ++\n\ttypeof: string ++\n\tdefault: *ipv4* ++\n\tThe address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both.\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: *{ifname}* ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-ethernet*: ++\n\ttypeof: string ++\n\tThis format is used when an ethernet interface is displayed.\n\n*format-wifi*: ++\n\ttypeof: string ++\n\tThis format is used when a wireless interface is displayed.\n\n*format-linked*: ++\n\ttypeof: string ++\n\tThis format is used when a linked interface with no IP address is displayed.\n\n*format-disconnected*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed interface is disconnected.\n\n*format-disabled*: ++\n\ttypeof: string ++\n\tThis format is used when rfkill is blocking wlan interfaces.\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current signal strength, the corresponding icon gets selected. ++\n\tThe order is *low* to *high*. Or by the state if it is an object.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tThe format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.\n\n*tooltip-format-ethernet*: ++\n\ttypeof: string ++\n\tThis format is used when an ethernet interface is displayed.\n\n*tooltip-format-wifi*: ++\n\ttypeof: string ++\n\tThis format is used when a wireless interface is displayed.\n\n*tooltip-format-disconnected*: ++\n\ttypeof: string ++\n\tThis format is used when the displayed interface is disconnected.\n\n*tooltip-format-disabled*: ++\n\ttypeof: string ++\n\tThis format is used when rfkill is blocking wlan interfaces.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{ifname}*: Name of the network interface.\n\n*{ipaddr}*: The first IP of the interface.\n\n*{gwaddr}*: The default gateway for the interface\n\n*{netmask}*: The subnetmask corresponding to the IP(V4).\n\n*{netmask6}*: The subnetmask corresponding to the IP(V6).\n\n*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation.\n\n*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation.\n\n*{essid}*: Name (SSID) of the wireless network.\n\n*{bssid}*: MAC address (BSSID) of the wireless access point.\n\n*{signalStrength}*: Signal strength of the wireless network.\n\n*{signaldBm}*: Signal strength of the wireless network in dBm.\n\n*{frequency}*: Frequency of the wireless network in GHz.\n\n*{bandwidthUpBits}*: Instant up speed in bits/seconds.\n\n*{bandwidthDownBits}*: Instant down speed in bits/seconds.\n\n*{bandwidthTotalBits}*: Instant total speed in bits/seconds.\n\n*{bandwidthUpOctets}*: Instant up speed in octets/seconds.\n\n*{bandwidthDownOctets}*: Instant down speed in octets/seconds.\n\n*{bandwidthTotalOctets}*: Instant total speed in octets/seconds.\n\n*{bandwidthUpBytes}*: Instant up speed in bytes/seconds.\n\n*{bandwidthDownBytes}*: Instant down speed in bytes/seconds.\n\n*{bandwidthTotalBytes}*: Instant total speed in bytes/seconds.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n# EXAMPLES\n\n```\n\"network\": {\n\t\"interface\": \"wlp2s0\",\n\t\"format\": \"{ifname}\",\n\t\"format-wifi\": \"{essid} ({signalStrength}%) \",\n\t\"format-ethernet\": \"{ifname} \",\n\t\"format-disconnected\": \"\", //An empty format will hide the module.\n\t\"format-disconnected\": \"\",\n\t\"tooltip-format\": \"{ifname}\",\n\t\"tooltip-format-wifi\": \"{essid} ({signalStrength}%) \",\n\t\"tooltip-format-ethernet\": \"{ifname} \",\n\t\"tooltip-format-disconnected\": \"Disconnected\",\n\t\"max-length\": 50\n}\n```\n\n# STYLE\n\n- *#network*\n- *#network.disconnected*\n- *#network.disabled*\n- *#network.linked*\n- *#network.ethernet*\n- *#network.wifi*\n"
  },
  {
    "path": "man/waybar-niri-language.5.scd",
    "content": "waybar-niri-language(5)\n\n# NAME\n\nwaybar - niri language module\n\n# DESCRIPTION\n\nThe *language* module displays the currently selected language in niri.\n\n# CONFIGURATION\n\nAddressed by *niri/language*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how information should be displayed.\n\n*format-<lang>* ++\n\ttypeof: string++\n\tProvide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{short}*: Short name of layout (e.g. \"us\"). Equals to {}.\n\n*{shortDescription}*: Short description of layout (e.g. \"en\").\n\n*{long}*: Long name of layout (e.g. \"English (Dvorak)\").\n\n*{variant}*: Variant of layout (e.g. \"dvorak\").\n\n# EXAMPLES\n\n```\n\"niri/language\": {\n\t\"format\": \"Lang: {long}\",\n\t\"format-en\": \"AMERICA, HELL YEAH!\",\n\t\"format-tr\": \"As bayrakları\"\n}\n```\n\n# STYLE\n\n- *#language*\n\nAdditionally, a CSS class matching the current layout's short name is added to the widget. This\nallows per-language styling, for example:\n\n```\n#language.us { color: #00ff00; }\n#language.de { color: #ff0000; }\n#language.fr { color: #0000ff; }\n```\n"
  },
  {
    "path": "man/waybar-niri-window.5.scd",
    "content": "waybar-niri-window(5)\n\n# NAME\n\nwaybar - niri window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in niri.\n\n# CONFIGURATION\n\nAddressed by *niri/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information should be displayed. On {} the current window title is displayed.\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite window title. See *rewrite rules*.\n\n*separate-outputs*: ++\n\ttypeof: bool ++\n\tShow the active window of the monitor the bar belongs to, instead of the focused window.\n\n*icon*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to hide the application icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tOption to change the size of the application icon.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\nSee the output of \"niri msg windows\" for examples\n\n*{title}*: The current title of the focused window.\n\n*{app_id}*: The current app ID of the focused window.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the title is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"niri/window\": {\n\t\"format\": \"{}\",\n\t\"rewrite\": {\n\t\t\"(.*) - Mozilla Firefox\": \"🌎 $1\",\n\t\t\"(.*) - zsh\": \"> [$1]\"\n\t}\n}\n```\n\n# STYLE\n\n- *#window*\n- *window#waybar.empty #window* When no windows are on the workspace\n\nThe following classes are applied to the entire Waybar rather than just the\nwindow widget:\n\n- *window#waybar.empty* When no windows are in the workspace\n- *window#waybar.solo* When only one window is on the workspace\n- *window#waybar.<app-id>* Where *app-id* is the app ID of the only window on\n  the workspace\n"
  },
  {
    "path": "man/waybar-niri-workspaces.5.scd",
    "content": "waybar-niri-workspaces(5)\n\n# NAME\n\nwaybar - niri workspaces module\n\n# DESCRIPTION\n\nThe *workspaces* module displays the currently used workspaces in niri.\n\n# CONFIGURATION\n\nAddressed by *niri/workspaces*\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {value} ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the workspace name, index and state, the corresponding icon gets selected. See *icons*.\n\n*disable-click*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can click to change workspace. If set to true this behaviour is disabled.\n\n*disable-markup*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, button label will escape pango markup.\n\n*current-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, only the active or focused workspace will be shown.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{value}*: Name of the workspace, or index for unnamed workspaces,\nas defined by niri.\n\n*{name}*: Name of the workspace for named workspaces.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{index}*: Index of the workspace on its output.\n\n*{output}*: Output where the workspace is located.\n\n# ICONS\n\nAdditional to workspace name matching, the following *format-icons* can be set.\n\n- *default*: Will be shown, when no string matches are found.\n- *focused*: Will be shown, when workspace is focused.\n- *active*: Will be shown, when workspace is active on its output.\n- *urgent*: Will be shown, when workspace has urgent windows.\n- *empty*: Will be shown, when workspace is empty.\n\n# EXAMPLES\n\n```\n\"niri/workspaces\": {\n\t\"format\": \"{icon}\",\n\t\"format-icons\": {\n\t\t// Named workspaces\n\t\t// (you need to configure them in niri)\n\t\t\"browser\": \"\",\n\t\t\"discord\": \"\",\n\t\t\"chat\": \"<b></b>\",\n\n\t\t// Icons by state\n\t\t\"active\": \"\",\n\t\t\"default\": \"\"\n\t}\n}\n```\n\n# Style\n\n- *#workspaces button*\n- *#workspaces button.focused*: The single focused workspace.\n- *#workspaces button.active*: The workspace is active (visible) on its output.\n- *#workspaces button.urgent*: The workspace has one or more urgent windows.\n- *#workspaces button.empty*: The workspace is empty.\n- *#workspaces button.current_output*: The workspace is from the same output as\n  the bar that it is displayed on.\n- *#workspaces button#niri-workspace-<name>*: Workspaces named this, or index\n  for unnamed workspaces.\n"
  },
  {
    "path": "man/waybar-power-profiles-daemon.5.scd",
    "content": "waybar-power-profiles-daemon(5)\n\n# NAME\n\nwaybar - power-profiles-daemon module\n\n# DESCRIPTION\n\nThe *power-profiles-daemon* module displays the active power-profiles-daemon profile and cycle through the available profiles on click.\n\n# FILES\n\n$XDG_CONFIG_HOME/waybar/config\n\n# CONFIGURATION\n\n\n[- *Option*\n:- *Typeof*\n:- *Default*\n:= *Description*\n|[ *format*\n:[ string\n:[ \"{icon}\"\n:[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.\n|[ *tooltip-format*\n:[ string\n:[ \"Power profile: {profile}\\\\nDriver: {driver}\"\n:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name.\n|[ *tooltip*\n:[ bool\n:[ true\n:[ Display the tooltip.\n|[ *format-icons*\n:[ object\n:[ See default value in the example below.\n:[ Icons used to represent the various power-profile. *Note*: the default configuration uses the font-awesome icons. You may want to override it if you don't have this font installed on your system.\n|[ *expand*:\n:[ bool\n:[ false\n:[ Enables this module to consume all left over space dynamically.\n\n\n\n\n\n# CONFIGURATION EXAMPLES\n\nCompact display (default config):\n\n```\n\"power-profiles-daemon\": {\n  \"format\": \"{icon}\",\n  \"tooltip-format\": \"Power profile: {profile}\\nDriver: {driver}\",\n  \"tooltip\": true,\n  \"format-icons\": {\n    \"default\": \"\",\n    \"performance\": \"\",\n    \"balanced\": \"\",\n    \"power-saver\": \"\"\n  }\n}\n```\n\nDisplay the full profile name:\n\n```\n\"power-profiles-daemon\": {\n  \"format\": \"{icon}   {profile}\",\n  \"tooltip-format\": \"Power profile: {profile}\\nDriver: {driver}\",\n  \"tooltip\": true,\n  \"format-icons\": {\n    \"default\": \"\",\n    \"performance\": \"\",\n    \"balanced\": \"\",\n    \"power-saver\": \"\"\n  }\n}\n```\n"
  },
  {
    "path": "man/waybar-privacy.5.scd",
    "content": "waybar-privacy(5)\n\n# NAME\n\nwaybar - privacy module\n\n# DESCRIPTION\n\nThe *privacy* module displays if any application is capturing audio, sharing ++\nthe screen or playing audio.\n\n# CONFIGURATION\n\n*icon-spacing*: ++\n\ttypeof: integer ++\n\tdefault: 4 ++\n\tThe spacing between each privacy icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 20 ++\n\tThe size of each privacy icon.\n\n*transition-duration*: ++\n\ttypeof: integer ++\n\tdefault: 250 ++\n\tOption to disable tooltip on hover.\n\n*modules* ++\n\ttypeof: array of objects ++\n\tdefault: [{\"type\": \"screenshare\"}, {\"type\": \"audio-in\"}] ++\n\tWhich privacy modules to monitor. See *MODULES CONFIGURATION* for++\n\tmore information.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n*ignore-monitor* ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIgnore streams with *stream.monitor* property.\n\n*ignore* ++\n\ttypeof: array of objects ++\n\tdefault: [] ++\n\tAdditional streams to be ignored. See *IGNORE CONFIGURATION* for++\n\tmore information.\n\n# MODULES CONFIGURATION\n\n*type*: ++\n\ttypeof: string ++\n\tvalues: \"screenshare\", \"audio-in\", \"audio-out\" ++\n\tSpecifies which module to use and configure.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tThe size of each icon in the tooltip.\n\n# IGNORE CONFIGURATION\n\n*type*: ++\n\ttypeof: string\n\n*name*: ++\n\ttypeof: string\n\n# EXAMPLES\n\n```\n\"privacy\": {\n\t\"icon-spacing\": 4,\n\t\"icon-size\": 18,\n\t\"transition-duration\": 250,\n\t\"modules\": [\n\t\t{\n\t\t\t\"type\": \"screenshare\",\n\t\t\t\"tooltip\": true,\n\t\t\t\"tooltip-icon-size\": 24\n\t\t},\n\t\t{\n\t\t\t\"type\": \"audio-out\",\n\t\t\t\"tooltip\": true,\n\t\t\t\"tooltip-icon-size\": 24\n\t\t},\n\t\t{\n\t\t\t\"type\": \"audio-in\",\n\t\t\t\"tooltip\": true,\n\t\t\t\"tooltip-icon-size\": 24\n\t\t}\n\t],\n\t\"ignore-monitor\": true,\n\t\"ignore\": [\n\t\t{\n\t\t\t\"type\": \"audio-in\",\n\t\t\t\"name\": \"cava\"\n\t\t},\n\t\t{\n\t\t\t\"type\": \"screenshare\",\n\t\t\t\"name\": \"obs\"\n\t\t}\n\t]\n},\n```\n\n# STYLE\n\n- *#privacy*\n- *#privacy-item*\n- *#privacy-item.screenshare*\n- *#privacy-item.audio-in*\n- *#privacy-item.audio-out*\n"
  },
  {
    "path": "man/waybar-pulseaudio-slider.5.scd",
    "content": "waybar-pulseaudio-slider(5)\n\n# NAME\n\nwaybar - pulseaudio slider module\n\n# DESCRIPTION\n\nThe *pulseaudio slider* module displays and controls the current volume of the default sink or source as a bar.\n\nThe volume can be controlled by dragging the slider across the bar or clicking on a specific position.\n\n# CONFIGURATION\n\n*min*: ++\n    typeof: int ++\n    default: 0 ++\n    The minimum volume value the slider should display and set.\n\n*max*: ++\n    typeof: int ++\n    default: 100 ++\n    The maximum volume value the slider should display and set.\n\n*orientation*: ++\n    typeof: string ++\n    default: horizontal ++\n    The orientation of the slider. Can be either `horizontal` or `vertical`.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"modules-right\": [\n    \"pulseaudio/slider\",\n],\n\"pulseaudio/slider\": {\n    \"min\": 0,\n    \"max\": 100,\n    \"orientation\": \"horizontal\"\n}\n```\n\n# STYLE\n\nThe slider is a component with multiple CSS Nodes, of which the following are exposed:\n\n*#pulseaudio-slider*: ++\n    Controls the style of the box *around* the slider and bar.\n\n*#pulseaudio-slider slider*: ++\n    Controls the style of the slider handle.\n\n*#pulseaudio-slider trough*: ++\n    Controls the style of the part of the bar that has not been filled.\n\n*#pulseaudio-slider highlight*: ++\n    Controls the style of the part of the bar that has been filled.\n\n## STYLE EXAMPLE\n\n```\n#pulseaudio-slider slider {\n    min-height: 0px;\n    min-width: 0px;\n    opacity: 0;\n    background-image: none;\n    border: none;\n    box-shadow: none;\n}\n\n#pulseaudio-slider trough {\n    min-height: 80px;\n    min-width: 10px;\n    border-radius: 5px;\n    background: black;\n}\n\n#pulseaudio-slider highlight {\n    min-width: 10px;\n    border-radius: 5px;\n    background: green;\n}\n```\n"
  },
  {
    "path": "man/waybar-pulseaudio.5.scd",
    "content": "waybar-pulseaudio(5)\n\n# NAME\n\nwaybar - pulseaudio module\n\n# DESCRIPTION\n\nThe *pulseaudio* module displays the current volume reported by PulseAudio.\n\nAdditionally, you can control the volume by scrolling *up* or *down* while the cursor is over the module.\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {volume}% ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-bluetooth*: ++\n\ttypeof: string ++\n\tThis format is used when using bluetooth speakers.\n\n*format-muted*: ++\n\ttypeof: string ++\n\tThis format is used when the sound is muted.\n\n*format-source*: ++\n\ttypeof: string ++\n\tdefault: {volume}% ++\n\tThis format used for the source.\n\n*format-source-muted*: ++\n\ttypeof: string ++\n\tThis format is used when the source is muted.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the current port name and volume, the corresponding icon gets selected. The order is *low* to *high*. See *Icons*.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of volume states which get activated on certain volume levels. See *waybar-states(5)*.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*scroll-step*: ++\n\ttypeof: float ++\n\tdefault: 1.0 ++\n\tThe speed at which to change the volume when scrolling.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module. This replaces the default behaviour of volume control.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module. This replaces the default behaviour of volume control.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*reverse-scrolling*: ++\n\ttypeof: bool ++\n\tOption to reverse the scroll direction for touchpads.\n\n*reverse-mouse-scrolling*: ++\n\ttypeof: bool ++\n\tOption to reverse the scroll direction for mice.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*max-volume*: ++\n\ttypeof: integer ++\n\tdefault: 100 ++\n\tThe maximum volume that can be set, in percentage.\n\n*ignored-sinks*: ++\n\ttypeof: array ++\n\tSinks in this list will not be shown as active sink by Waybar. Entries should be the sink's description field.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.\n\n*{volume}*: Volume in percentage.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{format_source}*: Source format, *format-source*, *format-source-muted*.\n\n# ICONS:\n\nThe following strings for *format-icons* are supported.\n\n- the device name\n\nIf they are found in the current PulseAudio port name, the corresponding icons will be selected.\n\n- *default* (Shown, when no other port is found)\n- *headphone*\n- *speaker*\n- *hdmi*\n- *headset*\n- *hands-free*\n- *portable*\n- *car*\n- *hifi*\n- *phone*\n\nAdditionally, suffixing a device name or port with *-muted* will cause the icon\nto be selected when the corresponding audio device is muted. This applies to *default* as well.\n\n# EXAMPLES\n\n```\n\"pulseaudio\": {\n\t\"format\": \"{volume}% {icon}\",\n\t\"format-bluetooth\": \"{volume}% {icon}\",\n\t\"format-muted\": \"\",\n\t\"format-icons\": {\n\t\t\"alsa_output.pci-0000_00_1f.3.analog-stereo\": \"\",\n\t\t\"alsa_output.pci-0000_00_1f.3.analog-stereo-muted\": \"\",\n\t\t\"headphone\": \"\",\n\t\t\"hands-free\": \"󰂑\",\n\t\t\"headset\": \"󰂑\",\n\t\t\"phone\": \"\",\n\t\t\"phone-muted\": \"\",\n\t\t\"portable\": \"\",\n\t\t\"car\": \"\",\n\t\t\"default\": [\"\", \"\"]\n\t},\n\t\"scroll-step\": 1,\n\t\"on-click\": \"pavucontrol\"\n}\n```\n\n# STYLE\n\n- *#pulseaudio*\n- *#pulseaudio.bluetooth*\n- *#pulseaudio.muted*\n"
  },
  {
    "path": "man/waybar-river-layout.5.scd",
    "content": "waybar-river-layout(5)\n\n# NAME\n\nwaybar - river layout module\n\n# DESCRIPTION\n\nThe *layout* module displays the current layout in river.\n\nIt may not be set until a layout is first applied.\n\n# CONFIGURATION\n\nAddressed by *river/layout*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLE\n\n```\n\"river/layout\": {\n\t\"format\": \"{}\",\n\t\"min-length\": 4,\n\t\"align\": \"right\"\n}\n```\n\n# STYLE\n\n- *#layout*\n- *#layout.focused* Applied when the output this module's bar belongs to is focused.\n- *#layout.<layout>* Applied when the output this module's bar belongs uses this layout.\n\n# SEE ALSO\n\nwaybar(5), river(1)\n"
  },
  {
    "path": "man/waybar-river-mode.5.scd",
    "content": "waybar-river-mode(5)\n\n# NAME\n\nwaybar - river mode module\n\n# DESCRIPTION\n\nThe *mode* module displays the current mapping mode of river.\n\n# CONFIGURATION\n\nAddressed by *river/mode*\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {} ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"river/mode\": {\n\t\"format\": \" {}\"\n}\n```\n\n# STYLE\n\n- *#mode*\n- *#mode.<mode>*\n"
  },
  {
    "path": "man/waybar-river-tags.5.scd",
    "content": "waybar-river-tags(5)\n\n# NAME\n\nwaybar - river tags module\n\n# DESCRIPTION\n\nThe *tags* module displays the current state of tags in river.\n\n# CONFIGURATION\n\nAddressed by *river/tags*\n\n*num-tags*: ++\n\ttypeof: uint ++\n\tdefault: 9 ++\n\tThe number of tags that should be displayed. Max 32.\n\n*tag-labels*: ++\n\ttypeof: array ++\n\tThe label to display for each tag.\n\n*disable-click*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n*hide-vacant*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOnly show relevant tags: tags that are either focused or have a window on them.\n\n# EXAMPLE\n\n```\n\"river/tags\": {\n\t\"num-tags\": 5\n}\n```\n\n# STYLE\n\n- *#tags button*\n- *#tags button.occupied*\n- *#tags button.focused*\n- *#tags button.urgent*\n\nNote that occupied/focused/urgent status may overlap. That is, a tag may be\nboth occupied and focused at the same time.\n\n# SEE ALSO\n\nwaybar(5), river(1)\n"
  },
  {
    "path": "man/waybar-river-window.5.scd",
    "content": "waybar-river-window(5)\n\n# NAME\n\nwaybar - river window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in river\n\n# CONFIGURATION\n\nAddressed by *river/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"river/window\": {\n\t\"format\": \"{}\"\n}\n```\n\n# STYLE\n\n- *#window*\n- *#window.focused* Applied when the output this module's bar belongs to is focused.\n"
  },
  {
    "path": "man/waybar-sndio.5.scd",
    "content": "waybar-sndio(5)\n\n# NAME\n\nwaybar - sndio module\n\n# DESCRIPTION\n\nThe *sndio* module displays the current volume reported by sndio(7).\n\nAdditionally, you can control the volume by scrolling *up* or *down* while the\ncursor is over the module, and clicking on the module toggles mute.\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {volume}% ++\n\tThe format for how information should be displayed.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*scroll-step*: ++\n\ttypeof: int ++\n\tdefault: 5 ++\n\tThe speed at which to change the volume when scrolling.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\tThis replaces the default behaviour of toggling mute.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module. ++\n\tThis replaces the default behaviour of volume control.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module. ++\n\tThis replaces the default behaviour of volume control.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{volume}*: Volume in percentage.\n\n*{raw_value}*: Volume as value reported by sndio.\n\n# EXAMPLES\n\n```\n\"sndio\": {\n\t\"format\": \"{raw_value} 🎜\",\n\t\"scroll-step\": 3\n}\n```\n\n# STYLE\n\n- *#sndio*\n- *#sndio.muted*\n"
  },
  {
    "path": "man/waybar-states.5.scd",
    "content": "waybar-states(5)\n\n# NAME\n\nwaybar - states property\n\n# OVERVIEW\n\nSome modules support 'states' which allows percentage values to be used as styling triggers to\napply a class when the value matches the declared state value.\n\n# STATES\n\nEvery entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).\n\n- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.\n  Each class gets activated when the current value is equal to or less than the configured *<value>* for the *battery* module, or equal to or greater than the configured *<value>* for all other modules.\n\n- Also, each state can have its own *format*.\n  Those can be configured via *format-<name>*, or if you want to differentiate a bit more, as *format-<status>-<state>*.\n\n# EXAMPLE\n\n```\n\"battery\": {\n\t\"bat\": \"BAT2\",\n\t\"interval\": 60,\n\t\"states\": {\n\t\t\"warning\": 30,\n\t\t\"critical\": 15\n\t},\n\t\"format\": \"{capacity}% {icon}\",\n\t\"format-icons\": [\"\", \"\", \"\", \"\", \"\"],\n\t\"max-length\": 25\n}\n```\n\n# STYLING STATES\n\n- *#battery.<state>*\n  - *<state>* can be defined in the *config*.\n\n# EXAMPLE:\n\n- *#battery.warning: { background: orange; }*\n- *#battery.critical: { background: red; }*\n"
  },
  {
    "path": "man/waybar-styles.5.scd.in",
    "content": "waybar-styles(5)\n\n# NAME\n\nwaybar-styles - using stylesheets for waybar\n\n# DESCRIPTION\n\nWaybar uses Cascading Style Sheets (CSS) to configure its appearance.\n\nIt uses the first file found in this search order:\n\n- *$XDG_CONFIG_HOME/waybar/style.css*\n- *~/.config/waybar/style.css*\n- *~/waybar/style.css*\n- */etc/xdg/waybar/style.css*\n- *@sysconfdir@/xdg/waybar/style.css*\n\n# EXAMPLE\n\nAn example user-controlled stylesheet that just changes the color of the clock to be green on black, while keeping the rest of the system config the same would be:\n\n```\n@import url(\"file:///etc/xdg/waybar/style.css\")\n\n#clock {\n  background: #000000;\n  color: #00ff00;\n}\n```\n\n## Hover-effect\n\nYou can apply special styling to any module for when the cursor hovers it.\n\n```\n#clock:hover {\n  background-color: #ffffff;\n}\n```\n\n## Setting cursor style\n\nMost, if not all, module types support setting the `cursor` option. This is\nconfigured in your `config.jsonc`. If set to `false`, when hovering the module a\n\"pointer\"(as commonly known from web CSS styling `cursor: pointer`) style cursor\nwill not be shown. Default behavior is to indicate an interaction event is\navailable.\n\nThere are more cursor types to choose from by setting the `cursor` option to\na number, see Gdk3 official docs for all possible cursor types:\nhttps://docs.gtk.org/gdk3/enum.CursorType.html.\nHowever, note that not all cursor options listed may be available on\nyour system. If you attempt to use a cursor which is not available, the\napplication will crash.\n\nExample of disabling pointer(`Gdk::Hand2`) cursor type on a custom module:\n\n```\n\"custom/my-custom-module\": {\n    ...\n    \"cursor\": false,\n}\n```\n\nExample of setting cursor type to `Gdk::Boat`(according to\nhttps://docs.gtk.org/gdk3/enum.CursorType.html#boat):\n\n```\n\"custom/my-custom-module\": {\n    ...\n    \"cursor\": 8,\n}\n```\n\n# SEE ALSO\n\n- *waybar(5)*\n"
  },
  {
    "path": "man/waybar-sway-language.5.scd",
    "content": "waybar-sway-language(5)\n\n# NAME\n\nwaybar - sway language module\n\n# DESCRIPTION\n\nThe *language* module displays the current keyboard layout in Sway\n\n# CONFIGURATION\n\nAddressed by *sway/language*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how layout should be displayed.\n\n*hide-single-layout*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDefines visibility of the module if a single layout is configured\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: {} ++\n\tThe format, how layout should be displayed in tooltip.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{short}*: Short name of layout (e.g. \"us\"). Equals to {}.\n\n*{shortDescription}*: Short description of layout (e.g. \"en\").\n\n*{long}*: Long name of layout (e.g. \"English (Dvorak)\").\n\n*{variant}*: Variant of layout (e.g. \"dvorak\").\n\n*{flag}*: Country flag of layout.\n\n# EXAMPLES\n\n```\n\"sway/language\": {\n\t\"format\": \"{}\",\n},\n\n\"sway/language\": {\n\t\"format\": \"{short} {variant}\",\n}\n```\n\n# STYLE\n\n- *#language*\n"
  },
  {
    "path": "man/waybar-sway-mode.5.scd",
    "content": "waybar-sway-mode(5)\n\n# NAME\n\nwaybar - sway mode module\n\n# DESCRIPTION\n\nThe *mode* module displays the current binding mode of Sway\n\n# CONFIGURATION\n\nAddressed by *sway/mode*\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {} ++\n\tThe format, how information should be displayed. On {} data gets inserted.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"sway/mode\": {\n\t\"format\": \" {}\",\n\t\"max-length\": 50\n}\n```\n\n# STYLE\n\n- *#mode*\n"
  },
  {
    "path": "man/waybar-sway-scratchpad.5.scd",
    "content": "waybar-sway-scratchpad(5)\n\n# NAME\n\nwaybar - sway scratchpad module\n\n# DESCRIPTION\n\nThe *scratchpad* module displays the scratchpad status in Sway\n\n# CONFIGURATION\n\nAddressed by *sway/scratchpad*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {icon} {count} ++\n\tThe format, how information should be displayed.\n\n*show-empty*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to show module when scratchpad is empty.\n\n*format-icons*: ++\n\ttypeof: array/object ++\n\tBased on the current scratchpad window counts, the corresponding icon gets selected.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: {app}: {title} ++\n\tThe format, how information in the tooltip should be displayed.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{count}*: Number of windows in the scratchpad.\n\n*{app}*: Name of the application in the scratchpad.\n\n*{title}*: Title of the application in the scratchpad.\n\n# EXAMPLES\n\n```\n\"sway/scratchpad\": {\n\t\"format\": \"{icon} {count}\",\n\t\"show-empty\": false,\n\t\"format-icons\": [\"\", \"\"],\n\t\"tooltip\": true,\n\t\"tooltip-format\": \"{app}: {title}\"\n}\n```\n\n# STYLE\n\n- *#scratchpad*\n- *#scratchpad.empty*\n"
  },
  {
    "path": "man/waybar-sway-window.5.scd",
    "content": "waybar-sway-window(5)\n\n# NAME\n\nwaybar - sway window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in Sway\n\n# CONFIGURATION\n\nAddressed by *sway/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information should be displayed.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to show the focused window along with its workspace styles on all outputs.\n\n*offscreen-css*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOnly effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style.\n\n*offscreen-css-text*: ++\n\ttypeof: string ++\n\tOnly effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspace styles.\n\n*show-focused-workspace-name*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.\n\n*show-hidden-marks*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tFor the *{marks}* format replacement, include hidden marks that start with an underscore.\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite the module format output. See *rewrite rules*.\n\n*icon*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to hide the application icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tOption to change the size of the application icon.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{title}*: The title of the focused window.\n\n*{app_id}*: The app_id of the focused window.\n\n*{shell}*: The shell of the focused window. It's 'xwayland' when the window is\nrunning through xwayland, otherwise, it's 'xdg-shell'.\n\n*{marks}*: Marks of the window.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the format output is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"sway/window\": {\n\t\"format\": \"{}\",\n\t\"max-length\": 50,\n\t\"rewrite\": {\n\t\t\"(.*) - Mozilla Firefox\": \"🌎 $1\",\n\t\t\"(.*) - zsh\": \"> [$1]\"\n\t}\n}\n```\n\n# STYLE\n\n- *#window*\n\nThe following classes are applied to the entire Waybar rather than just the\nwindow widget:\n\n- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-css option is not set\n- *window#waybar.solo* When one tiled window is in the workspace\n- *window#waybar.floating* When there are only floating windows in the workspace\n- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked\n- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed\n- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv\n- *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace\n"
  },
  {
    "path": "man/waybar-sway-workspaces.5.scd",
    "content": "waybar-sway-workspaces(5)\n\n# NAME\n\nwaybar - sway workspaces module\n\n# DESCRIPTION\n\nThe *workspaces* module displays the currently used workspaces in Sway.\n\n# CONFIGURATION\n\nAddressed by *sway/workspaces*\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {value} ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the workspace name and state, the corresponding icon gets selected. See *icons*.\n\n*disable-scroll*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.\n\n*reverse-scroll*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, scrolling up will switch to the previous workspace and scrolling down will switch to the next workspace. If set to true, the behavior will be reversed.\n\n*disable-click*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can click to change workspace. If set to true this behaviour is disabled.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*disable-scroll-wraparound*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, scrolling on the workspace indicator will wrap around to the first workspace when reading the end, and vice versa. If set to true this behavior is disabled.\n\n*enable-bar-scroll*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled.\n\n*disable-markup*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, button label will escape pango markup.\n\n*current-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true. Only focused workspaces will be shown.\n\n*persistent-workspaces*: ++\n\ttypeof: json (see below) ++\n\tdefault: empty ++\n\tLists workspaces that should always be shown, even when non-existent\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*disable-auto-back-and-forth*: ++\n\ttypeof: bool ++\n\tWhether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.\n\n*alphabetical_sort*: ++\n\ttypeof: bool ++\n\tWhether to sort workspaces alphabetically. Please note this can make \"swaymsg workspace prev/next\" move to workspaces inconsistent with the ordering shown in Waybar.\n\nwarp-on-scroll: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIf set to false, you can scroll to cycle through workspaces without mouse warping being enabled. If set to true this behaviour is disabled.\n\n*window-rewrite*: ++\n\ttypeof: object ++\n\tRegex rules to map window class to an icon or preferred method of representation for a workspace's window.\n\tKeys are the rules, while the values are the methods of representation.\n\tRules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching.\n\tYou may assign an empty value to a rule to have it ignored from generating any representation in workspaces.\n\tFor Wayland windows `class` is matched against the `app_id`, and for X11 windows against the `class` property.\n\n*window-rewrite-default*:\n\ttypeof: string ++\n\tdefault: \"?\" ++\n\tThe default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*.\n\n*format-window-separator*: ++\n\ttypeof: string ++\n\tdefault: \" \" ++\n\tThe separator to be used between windows in a workspace.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n\n# FORMAT REPLACEMENTS\n\n*{value}*: Name of the workspace, as defined by sway.\n\n*{name}*: Number stripped from workspace value.\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{index}*: Index of the workspace.\n\n*{output}*: Output where the workspace is located.\n\n*{windows}*: Result from window-rewrite\n\n# ICONS\n\nAdditional to workspace name matching, the following *format-icons* can be set.\n\n- *default*: Will be shown, when no string matches are found.\n- *urgent*: Will be shown, when workspace is flagged as urgent\n- *focused*: Will be shown, when workspace is focused\n- *persistent*: Will be shown, when workspace is persistent.\n- *high-priority-named*: Icons by names will be shown always for those workspaces, independent by state.\n\n# PERSISTENT WORKSPACES\n\nEach entry of *persistent_workspace* names a workspace that should always be shown.\nAssociated with that value is a list of outputs indicating *where* the workspace should be shown,\nan empty list denoting all outputs.\n\n```\n\"sway/workspaces\": {\n\t\"persistent-workspaces\": {\n\t\t\"3\": [], // Always show a workspace with name '3', on all outputs if it does not exist\n\t\t\"4\": [\"eDP-1\"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exist\n\t\t\"5\": [\"eDP-1\", \"DP-2\"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exist\n\t}\n}\n```\n\nn.b.: the list of outputs can be obtained from command line using *swaymsg -t get_outputs*\n\n# EXAMPLES\n\n```\n\"sway/workspaces\": {\n\t\"disable-scroll\": true,\n\t\"all-outputs\": true,\n\t\"format\": \"{name}: {icon}\",\n\t\"format-icons\": {\n\t\t\"1\": \"\",\n\t\t\"2\": \"\",\n\t\t\"3\": \"\",\n\t\t\"4\": \"\",\n\t\t\"5\": \"\",\n\t\t\"high-priority-named\": [ \"1\", \"2\" ],\n\t\t\"urgent\": \"\",\n\t\t\"focused\": \"\",\n\t\t\"default\": \"\"\n\t}\n}\n```\n\n```\n\"sway/workspaces\": {\n  \"format\": \"<span size='larger'>{name}</span> {windows}\",\n  \"format-window-separator\": \" | \",\n  \"window-rewrite-default\": \"{name}\",\n  \"window-rewrite\": {\n    \"class<firefox>\": \"\",\n    \"class<kitty>\": \"k\",\n\t\"title<.* - (.*) - VSCodium>\": \"codium $1\"  // captures part of the window title and formats it into output\n  }\n}\n```\n\n# Style\n\n- *#workspaces button*\n- *#workspaces button.visible*\n- *#workspaces button.focused*\n- *#workspaces button.urgent*\n- *#workspaces button.persistent*\n- *#workspaces button.empty*\n- *#workspaces button.current_output*\n- *#workspaces button#sway-workspace-${name}*\n"
  },
  {
    "path": "man/waybar-systemd-failed-units.5.scd",
    "content": "waybar-systemd-failed-units(5)\n\n# NAME\n\nwaybar - systemd failed units monitor module\n\n# DESCRIPTION\n\nThe *systemd-failed-units* module displays the number of failed systemd units.\n\n# CONFIGURATION\n\nAddressed by *systemd-failed-units*\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: *{nr_failed} failed* ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-ok*: ++\n\ttypeof: string ++\n\tThis format is used when there are no failing units.\n\n*user*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to count user systemd units.\n\n*system*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to count systemwide (PID=1) systemd units.\n\n*hide-on-ok*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to hide this module when there are no failed units.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: *System: {system_state}\\nUser: {user_state}\\nFailed units ({nr_failed}):\\n{failed_units_list}* ++\n\tTooltip format shown when there are failed units.\n\n*tooltip-format-ok*: ++\n\ttypeof: string ++\n\tdefault: *System: {system_state}\\nUser: {user_state}* ++\n\tTooltip format used when there are no failed units.\n\n*tooltip-unit-format*: ++\n\ttypeof: string ++\n\tdefault: *{name}: {description}* ++\n\tFormat used to render each failed unit inside the tooltip. Each item is prefixed with a bullet.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that pops up the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There needs to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all leftover space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{nr_failed_system}*: Number of failed units from systemwide (PID=1) systemd.\n\n*{nr_failed_user}*: Number of failed units from user systemd.\n\n*{nr_failed}*: Number of total failed units.\n\n*{system_state}:* State of the systemd system session.\n\n*{user_state}:* State of the systemd user session.\n\n*{overall_state}:* Overall state of the systemd and user session. (\"ok\" or \"degraded\")\n\n*{failed_units_list}:* Bulleted list of failed units using *tooltip-unit-format*. Empty when\nthere are no failed units.\n\nThe *tooltip-unit-format* string supports the following replacements:\n\n*{name}*: Unit name ++\n*{description}*: Unit description ++\n*{load_state}*: Unit load state ++\n*{active_state}*: Unit active state ++\n*{sub_state}*: Unit sub state ++\n*{scope}*: Either *system* or *user* depending on where the unit originated\n\n# EXAMPLES\n\n```\n\"systemd-failed-units\": {\n\t\"hide-on-ok\": false,\n\t\"format\": \"✗ {nr_failed}\",\n\t\"format-ok\": \"✓\",\n\t\"system\": true,\n\t\"user\": false,\n\t\"tooltip-format\": \"{nr_failed} failed units:\\n{failed_units_list}\",\n\t\"tooltip-unit-format\": \"{scope}: {name} ({active_state})\",\n}\n```\n\n# STYLE\n\n- *#systemd-failed-units*\n- *#systemd-failed-units.ok*\n- *#systemd-failed-units.degraded*\n"
  },
  {
    "path": "man/waybar-temperature.5.scd",
    "content": "waybar-temperature(5)\n\n# NAME\n\nwaybar - temperature module\n\n# DESCRIPTION\n\nThe *temperature* module displays the current temperature from a thermal zone.\n\n# CONFIGURATION\n\nAddressed by *temperature*\n\n*thermal-zone*: ++\n\ttypeof: integer ++\n\tThe thermal zone, as in */sys/class/thermal/*.\n\n*hwmon-path*: ++\n\ttypeof: string ++\n\tThe temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*.\n\tThis can also be an array of strings. In this case, waybar will check each item in the array and use the first valid one.\n\tThis is suitable if you want to share the same configuration file among different machines with different hardware configurations.\n\n*hwmon-path-abs*: ++\n\ttypeof: string ++\n\tThe path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*.\n\tThis can also be an array of strings, for which, it just works like *hwmon-path*.\n\n*input-filename*: ++\n\ttypeof: string ++\n\tThe temperature filename of your *hwmon-path-abs*, e.g. *temp1_input*\n\n*warning-threshold*: ++\n\ttypeof: integer ++\n\tThe threshold before it is considered warning (Celsius).\n\n*critical-threshold*: ++\n\ttypeof: integer ++\n\tThe threshold before it is considered critical (Celsius).\n\n*interval*: ++\n\ttypeof: integer or float ++\n\tdefault: 10 ++\n\tThe interval in which the information gets polled.\n\n*format-warning*: ++\n\ttypeof: string ++\n\tThe format to use when temperature is considered warning\n\n*format-critical*: ++\n\ttypeof: string ++\n\tThe format to use when temperature is considered critical\n\n*format*: ++\n\ttypeof: string  ++\n\tdefault: {temperatureC}°C ++\n\tThe format (Celsius/Fahrenheit/Kelvin) in which the temperature should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*.\n\n*tooltip-format*: ++\n\ttypeof: string  ++\n\tdefault: {temperatureC}°C ++\n\tThe format for the tooltip\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in characters the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when you click on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{temperatureC}*: Temperature in Celsius.\n\n*{temperatureF}*: Temperature in Fahrenheit.\n\n*{temperatureK}*: Temperature in Kelvin.\n\n# EXAMPLES\n\n```\n \"temperature\": {\n\t// \"thermal-zone\": 2,\n\t// \"hwmon-path\": [\"/sys/class/hwmon/hwmon2/temp1_input\", \"/sys/class/thermal/thermal_zone0/temp\"],\n\t// \"critical-threshold\": 80,\n\t// \"format-critical\": \"{temperatureC}°C \",\n\t\"format\": \"{temperatureC}°C \"\n}\n```\n\n# STYLE\n\n- *#temperature*\n- *#temperature.warning*\n- *#temperature.critical*\n"
  },
  {
    "path": "man/waybar-tray.5.scd",
    "content": "waybar-tray(5)\n\n# NAME\n\nwaybar - tray module\n\n# DESCRIPTION\n\n_WARNING_ *tray* is still in beta. There may be bugs. Breaking changes may occur.\n\n# CONFIGURATION\n\nAddressed by *tray*\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tDefines the size of the tray icons.\n\n*show-passive-items*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDefines visibility of the tray icons with *Passive* status.\n\n*smooth-scrolling-threshold*: ++\n\ttypeof: double ++\n\tThreshold to be used when scrolling.\n\n*spacing*: ++\n\ttypeof: integer ++\n\tDefines the spacing between the tray icons.\n\n*reverse-direction*: ++\n\ttypeof: bool ++\n\tDefines if new app icons should be added in a reverse order\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# EXAMPLES\n\n```\n\"tray\": {\n\t\"icon-size\": 21,\n\t\"spacing\": 10,\n  \"icons\": {\n    \"blueman\": \"bluetooth\",\n    \"TelegramDesktop\": \"$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png\"\n  }\n}\n\n```\n\n# STYLE\n\n- *#tray*\n- *#tray > .passive*\n- *#tray > .active*\n- *#tray > .needs-attention*\n"
  },
  {
    "path": "man/waybar-upower.5.scd",
    "content": "waybar-upower(5)\n\n# NAME\n\nwaybar - upower module\n\n# DESCRIPTION\n\nThe *upower* module displays the main battery capacity with all other upower\ncompatible devices in the tooltip.\n\n# CONFIGURATION\n\n*native-path*: ++\n\ttypeof: string ++\n\tdefault: ++\n\tThe battery to monitor. Refer to the https://upower.freedesktop.org/docs/UpDevice.html#UpDevice--native-path ++\n\tCan be obtained using `upower --dump`\n\n*model*: ++\n\ttypeof: string ++\n\tdefault: ++\n\tThe battery to monitor, based on the model. (this option is ignored if *native-path* is given). ++\n\tCan be obtained using `upower --dump`\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 20 ++\n\tDefines the size of the icons.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {percentage} ++\n\tThe text format.\n\n*format-alt*: ++\n\ttypeof: string ++\n\tdefault: {percentage} {time} ++\n\tThe text format when toggled.\n\n*hide-if-empty*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tDefines visibility of the module if no devices can be found.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable tooltip on hover.\n\n*tooltip-spacing*: ++\n\ttypeof: integer ++\n\tdefault: 4 ++\n\tDefines the spacing between the tooltip device name and device battery ++\n\tstatus.\n\n*tooltip-padding*: ++\n\ttypeof: integer ++\n\tdefault: 4 ++\n\tDefines the spacing between the tooltip window edge and the tooltip content.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*show-icon*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tOption to disable battery icon.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n# FORMAT REPLACEMENTS\n\n*{percentage}*: The battery capacity in percentage\n\n*{time}*: An estimated time either until empty or until fully charged ++\ndepending on the charging state.\n\n# EXAMPLES\n\n```\n\"upower\": {\n\t\"icon-size\": 20,\n\t\"hide-if-empty\": true,\n\t\"tooltip\": true,\n\t\"tooltip-spacing\": 20\n}\n\n```\n```\n\"upower\": {\n\t\"native-path\": \"/org/bluez/hci0/dev_D4_AE_41_38_D0_EF\",\n\t\"icon-size\": 20,\n\t\"hide-if-empty\": true,\n\t\"tooltip\": true,\n\t\"tooltip-spacing\": 20\n}\n\n```\n```\n\"upower\": {\n\t\"native-path\": \"battery_sony_controller_battery_d0o27o88o32ofcoee\",\n\t\"icon-size\": 20,\n\t\"hide-if-empty\": true,\n\t\"tooltip\": true,\n\t\"tooltip-spacing\": 20\n}\n```\n```\n\"upower\": {\n\t\"show-icon\": false,\n\t\"hide-if-empty\": true,\n\t\"tooltip\": true,\n\t\"tooltip-spacing\": 20\n}\n\n```\n\n# STYLE\n\n- *#upower*\n- *#upower.charging*\n- *#upower.discharging*\n- *#upower.full*\n- *#upower.empty*\n- *#upower.unknown-status*\n"
  },
  {
    "path": "man/waybar-wayfire-window.5.scd",
    "content": "waybar-wayfire-window(5)\n\n# NAME\n\nwaybar - wayfire window module\n\n# DESCRIPTION\n\nThe *window* module displays the title of the currently focused window in wayfire.\n\n# CONFIGURATION\n\nAddressed by *wayfire/window*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information should be displayed. On {} the current window title is displayed.\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite window title. See *rewrite rules*.\n\n*icon*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to hide the application icon.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 24 ++\n\tOption to change the size of the application icon.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\nSee the output of \"wayfire msg windows\" for examples\n\n*{title}*: The current title of the focused window.\n\n*{app_id}*: The current app ID of the focused window.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the title is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"wayfire/window\": {\n\t\"format\": \"{}\",\n\t\"rewrite\": {\n\t\t\"(.*) - Mozilla Firefox\": \"🌎 $1\",\n\t\t\"(.*) - zsh\": \"> [$1]\"\n\t}\n}\n```\n\n# STYLE\n\n- *#window*\n- *window#waybar.empty #window* When no windows are on the workspace\n\nThe following classes are applied to the entire Waybar rather than just the\nwindow widget:\n\n- *window#waybar.empty* When no windows are in the workspace\n- *window#waybar.solo* When only one window is on the workspace\n- *window#waybar.<app-id>* Where *app-id* is the app ID of the only window on\n  the workspace\n"
  },
  {
    "path": "man/waybar-wayfire-workspaces.5.scd",
    "content": "waybar-wayfire-workspaces(5)\n\n# NAME\n\nwaybar - wayfire workspaces module\n\n# DESCRIPTION\n\nThe *workspaces* module displays the currently used workspaces in wayfire.\n\n# CONFIGURATION\n\nAddressed by *wayfire/workspaces*\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {value} ++\n\tThe format, how information should be displayed.\n\n*format-icons*: ++\n\ttypeof: array ++\n\tBased on the workspace name, index and state, the corresponding icon gets selected. See *icons*.\n\n*disable-click*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false, you can click to change workspace. If set to true this behaviour is disabled.\n\n*disable-markup*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, button label will escape pango markup.\n\n*current-only*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, only the active or focused workspace will be shown.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*expand*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables this module to consume all left over space dynamically.\n\n# FORMAT REPLACEMENTS\n\n*{icon}*: Icon, as defined in *format-icons*.\n\n*{index}*: Index of the workspace on its output.\n\n*{output}*: Output where the workspace is located.\n\n# ICONS\n\nAdditional to workspace name matching, the following *format-icons* can be set.\n\n- *default*: Will be shown, when no string matches are found.\n- *focused*: Will be shown, when workspace is focused.\n\n# EXAMPLES\n\n```\n\"wayfire/workspaces\": {\n\t\"format\": \"{icon}\",\n\t\"format-icons\": {\n\t\t\"1\": \"\",\n\t\t\"2\": \"\",\n\t\t\"3\": \"\",\n\t\t\"4\": \"\",\n\t\t\"5\": \"\",\n\t\t\"focused\": \"\",\n\t\t\"default\": \"\"\n\t}\n}\n```\n\n# Style\n\n- *#workspaces button*\n- *#workspaces button.focused*: The single focused workspace.\n- *#workspaces button.empty*: The workspace is empty.\n- *#workspaces button.current_output*: The workspace is from the same output as\n  the bar that it is displayed on.\n"
  },
  {
    "path": "man/waybar-wireplumber.5.scd",
    "content": "waybar-wireplumber(5)\n\n# NAME\n\nwaybar - WirePlumber module\n\n# DESCRIPTION\n\nThe *wireplumber* module displays the current volume reported by WirePlumber.\n\n# CONFIGURATION\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: *{volume}%* ++\n\tThe format, how information should be displayed. This format is used when other formats aren't specified.\n\n*format-muted*: ++\n\ttypeof: string ++\n\tThis format is used when the sound is muted.\n\n*node-type*: ++\n\ttypeof: string ++\n\tdefault: *Audio/Sink* ++\n\tThe WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to disable tooltip on hover.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: *{node_name}* ++\n\tThe format of information displayed in the tooltip.\n\n*rotate*: ++\n\ttypeof: integer ++\n\tPositive value to rotate the text label (in 90 degree increments).\n\n*states*: ++\n\ttypeof: object ++\n\tA number of volume states which get activated on certain volume levels. See *waybar-states(5)*.\n\n*max-length*: ++\n\ttypeof: integer ++\n\tThe maximum length in character the module should display.\n\n*min-length*: ++\n\ttypeof: integer ++\n\tThe minimum length in characters the module should accept.\n\n*align*: ++\n\ttypeof: float ++\n\tThe alignment of the label within the module, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.\n\n*justify*: ++\n\ttypeof: string ++\n\tThe alignment of the text within the module's label, allowing options 'left', 'right', or 'center' to define the positioning.\n\n*scroll-step*: ++\n\ttypeof: float ++\n\tdefault: 1.0 ++\n\tThe speed at which to change the volume when scrolling.\n\n*on-click*: ++\n\ttypeof: string ++\n\tCommand to execute when clicked on the module.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tCommand to execute when middle-clicked on the module using mousewheel.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tCommand to execute when you right-click on the module.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*on-scroll-up*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling up on the module. This replaces the default behaviour of volume control.\n\n*on-scroll-down*: ++\n\ttypeof: string ++\n\tCommand to execute when scrolling down on the module. This replaces the default behaviour of volume control.\n\n*max-volume*: ++\n\ttypeof: float ++\n\tdefault: 100 ++\n\tThe maximum volume that can be set, in percentage.\n\n*menu*: ++\n\ttypeof: string ++\n\tAction that popups the menu.\n\n*menu-file*: ++\n\ttypeof: string ++\n\tLocation of the menu descriptor file. There need to be an element of type\n\tGtkMenu with id *menu*\n\n*menu-actions*: ++\n\ttypeof: array ++\n\tThe actions corresponding to the buttons of the menu.\n\n# FORMAT REPLACEMENTS\n\n*{volume}*: Volume in percentage.\n\n*{node_name}*: The node's nickname as reported by WirePlumber (*node.nick* property)\n\n# EXAMPLES\n\n## Basic:\n\n```\n\"wireplumber\": {\n\t\"format\": \"{volume}%\",\n\t\"format-muted\": \"\",\n\t\"on-click\": \"helvum\"\n}\n```\n\n## Separate Sink and Source Widgets \n\n```\n\"wireplumber#sink\": {\n    \"format\": \"{volume}% {icon}\",\n    \"format-muted\": \"󰅶\",\n    \"format-icons\": [\"\", \"\", \"\"],\n    \"on-click\": \"helvum\",\n    \"on-click-right\": \"wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle\",\n    \"scroll-step\": 5\n},\n\"wireplumber#source\": {\n    \"node-type\": \"Audio/Source\",\n    \"format\": \"{volume}% \",\n    \"format-muted\": \"\",\n    \"on-click-right\": \"wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle\",\n    \"scroll-step\": 5\n}\n```\n\n# STYLE\n\n- *#wireplumber*\n- *#wireplumber.muted*\n"
  },
  {
    "path": "man/waybar-wlr-taskbar.5.scd",
    "content": "waybar-wlr-taskbar(5)\n\n# NAME\n\nwaybar - wlr taskbar module\n\n# DESCRIPTION\n\nThe *taskbar* module displays the currently open applications. This module requires\na compositor that implements the foreign-toplevel-manager interface.\n\n# CONFIGURATION\n\nAddressed by *wlr/taskbar*\n\n*all-outputs*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown.\n\n*format*: ++\n\ttypeof: string ++\n\tdefault: {icon} ++\n\tThe format, how information should be displayed.\n\n*icon-theme*: ++\n\ttypeof: array|string ++\n\tThe names of the icon-themes that should be used to find an icon. The list will be traversed from left to right. If omitted, the system default will be used.\n\n*icon-size*: ++\n\ttypeof: integer ++\n\tdefault: 16 ++\n\tThe size of the icon.\n\n*markup*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, pango markup will be accepted in format and tooltip-format.\n\n*tooltip*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tIf set to false no tooltip will be shown.\n\n*tooltip-format*: ++\n\ttypeof: string ++\n\tdefault: {title} ++\n\tThe format, how information in the tooltip should be displayed.\n\n*active-first*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.\n\n*sort-by-app-id*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tIf set to true, group tasks by their app_id. Cannot be used with 'active-first'.\n\n*on-click*: ++\n\ttypeof: string ++\n\tThe action which should be triggered when clicking on the application button with the left mouse button.\n\n*on-click-middle*: ++\n\ttypeof: string ++\n\tThe action which should be triggered when clicking on the application button with the middle mouse button.\n\n*on-click-right*: ++\n\ttypeof: string ++\n\tThe action which should be triggered when clicking on the application button with the right mouse button.\n\n*on-update*: ++\n\ttypeof: string ++\n\tCommand to execute when the module is updated.\n\n*ignore-list*: ++\n\ttypeof: array ++\n\tList of app_id/titles to be invisible.\n\n*app_ids-mapping*: ++\n\ttypeof: object ++\n\tDictionary of app_id to be replaced with\n\n*rewrite*: ++\n\ttypeof: object ++\n\tRules to rewrite the module format output. See *rewrite rules*.\n\n# FORMAT REPLACEMENTS\n\n*{icon}*: The icon of the application.\n\n*{name}*: The application name as in desktop file if appropriate desktop files are found, otherwise same as {app_id}\n\n*{title}*: The title of the application.\n\n*{app_id}*: The app_id (== application name) of the application.\n\n*{state}*: The state (minimized, maximized, active, fullscreen) of the application.\n\n*{short_state}*: The state (minimize == m, maximized == M, active == A, fullscreen == F) represented as one character of the application.\n\n# CLICK ACTIONS\n\n*activate*: Bring the application into foreground.\n\n*minimize*: Toggle application's minimized state.\n\n*minimize-raise*: Bring the application into foreground or toggle its minimized state.\n\n*maximize*: Toggle application's maximized state.\n\n*fullscreen*: Toggle application's fullscreen state.\n\n*close*: Close the application.\n\n# REWRITE RULES\n\n*rewrite* is an object where keys are regular expressions and values are\nrewrite rules if the expression matches. Rules may contain references to\ncaptures of the expression.\n\nRegular expression and replacement follow ECMA-script rules.\n\nIf no expression matches, the format output is left unchanged.\n\nInvalid expressions (e.g., mismatched parentheses) are skipped.\n\n# EXAMPLES\n\n```\n\"wlr/taskbar\": {\n\t\"format\": \"{icon}\",\n\t\"icon-size\": 14,\n\t\"icon-theme\": \"Numix-Circle\",\n\t\"tooltip-format\": \"{title}\",\n\t\"on-click\": \"activate\",\n\t\"on-click-middle\": \"close\",\n\t\"ignore-list\": [\n\t    \"Alacritty\"\n\t],\n\t\"app_ids-mapping\": {\n\t\t\"firefoxdeveloperedition\": \"firefox-developer-edition\"\n\t},\n\t\"rewrite\": {\n\t\t\"Firefox Web Browser\": \"Firefox\",\n\t\t\"Foot Server\": \"Terminal\"\n\t}\n}\n```\n\n# Style\n\n- *#taskbar*\n- *#taskbar button*\n- *#taskbar button.maximized*\n- *#taskbar button.minimized*\n- *#taskbar button.active*\n- *#taskbar button.fullscreen*\n"
  },
  {
    "path": "man/waybar.5.scd.in",
    "content": "waybar(5)\n\n# NAME\n\nwaybar - configuration file\n\n# DESCRIPTION\n\nThe configuration uses the JSONC file format and is named *config* or *config.jsonc*.\n\nValid locations for this file are:\n\n- *$XDG_CONFIG_HOME/waybar/*\n- *~/.config/waybar/*\n- *~/waybar/*\n- */etc/xdg/waybar/*\n- *@sysconfdir@/xdg/waybar/*\n\nA good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.jsonc\nAlso, a minimal example configuration can be found at the bottom of this man page.\n\nThe visual display elements for waybar use a CSS stylesheet, see *waybar-styles(5)* for details.\n\n# BAR CONFIGURATION\n\n*expand-center* ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables the modules-center to consume all left over space dynamically.\n\n*expand-left* ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables the modules-left to consume all left over space dynamically.\n\n*expand-right* ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tEnables the modules-right to consume all left over space dynamically.\n\n*layer* ++\n\ttypeof: string ++\n\tdefault: bottom ++\n\tDecide if the bar is displayed in front (*top*) of the windows or behind (*bottom*)\n\tthem.\n\n*output* ++\n\ttypeof: string|array ++\n\tSpecifies on which screen this bar will be displayed. Exclamation mark(*!*) can be used to exclude specific output.\n\tOutput specification follows sway's and can either be the output port such as \"HDMI-A-1\" or a string consisting of the make, model, and serial such as \"Some Company ABC123 0x00000000\". See *sway-output(5)* for details.\n\tIn an array, star '*\\**' can be used at the end to accept all outputs, in case all previous entries are exclusions.\n\n*position* ++\n\ttypeof: string ++\n\tdefault: top ++\n\tBar position, can be *top*, *bottom*, *left*, *right*.\n\n*height* ++\n\ttypeof: integer ++\n\tHeight to be used by the bar if possible. Leave blank for a dynamic value.\n\n*width* ++\n\ttypeof: integer ++\n\tWidth to be used by the bar if possible. Leave blank for a dynamic value.\n\n*modules-left* ++\n\ttypeof: array ++\n\tModules that will be displayed on the left.\n\n*modules-center* ++\n\ttypeof: array ++\n\tModules that will be displayed in the center.\n\n*modules-right* ++\n\ttypeof: array\n\tModules that will be displayed on the right.\n\n*margin* ++\n\ttypeof: string ++\n\tMargins value using the CSS format without units.\n\n*margin-<top\\|left\\|bottom\\|right>* ++\n\ttypeof: integer ++\n\tMargins value without units.\n\n*no-center* ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to disable the center modules fully useful together with expand-\\*.\n\n*spacing* ++\n\ttypeof: integer ++\n\tSize of gaps in between the different modules.\n\n*name* ++\n\ttypeof: string ++\n\tOptional name added as a CSS class, for styling multiple waybars.\n\n*mode* ++\n\ttypeof: string ++\n\tSelects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++\n\tNote: *hide* and *invisible* modes may be not as useful without Sway IPC.\n\n*start_hidden* ++\n\ttypeof: bool ++\n\tdefault: *false* ++\n\tOption to start the bar hidden.\n\n*modifier-reset* ++\n\ttypeof: string ++\n\tdefault: *press*\n\tDefines the timing of modifier key to reset the bar visibility.\n\tTo reset the visibility of the bar with the press of the modifier key use *press*.\n\tUse *release* to reset the visibility upon the release of the modifier key and only if no other action happened while the key was pressed. This prevents hiding the bar when the modifier is used to switch a workspace, change binding mode, or start a keybinding.\n\n*exclusive* ++\n\ttypeof: bool ++\n\tdefault: *true* ++\n\tOption to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.\n\n*fixed-center* ++\n\ttypeof: bool ++\n\tdefault: *true*\n\tPrefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space.\n\tWhen false, the center block is centered in the space between the left and right block.\n\n*passthrough* ++\n\ttypeof: bool ++\n\tdefault: *false* ++\n\tOption to pass any pointer events to the window under the bar.\n\tIntended to be used with either *top* or *overlay* layers and without exclusive zone.\n\n*ipc* ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tOption to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++\n\tRequires *bar_id* value from sway configuration to be either passed with the *-b* command line argument or specified with the *id* option.\n\n*id* ++\n\ttypeof: string ++\n\t*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* command line argument for the specific bar instance.\n\n*include* ++\n\ttypeof: string|array ++\n\tPaths to additional configuration files.\n\tEach file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports.\n\tFor a multi-bar config, the include directive affects only current bar configuration object.\n\n*reload_style_on_change* ++\n\ttypeof: bool ++\n\tdefault: *false* ++\n\tOption to enable reloading the css style if a modification is detected on the style sheet file or any imported css files.\n\n*on-sigusr1* ++\n\ttypeof: string ++\n\tdefault: *toggle* ++\n\tAction that is performed when receiving SIGUSR1 kill signal. ++\n\tPossible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++\n\tDefault value: *toggle*.\n\n*on-sigusr2* ++\n\ttypeof: string ++\n\tdefault: *reload* ++\n\tAction that is performed when receiving SIGUSR2 kill signal. ++\n\tPossible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++\n\tDefault value: *reload*.\n\n# MODULE FORMAT\n\nYou can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).\n\ne.g.\n\n```\n\"format\": \"<span style=\\\"italic\\\">{}</span>\"\n```\n# MULTIPLE INSTANCES OF A MODULE\n\nIf you want to have a second instance of a module, you can suffix it by a '#' and a custom name.\nFor example, if you want a second battery module, you can add *\"battery#bat2\"* to your modules.\nTo configure the newly added module, you then also add a module configuration with the same name.\n\nThis could then look something like this *(this is an incomplete example)*:\n\n```\n\"modules-right\": [\"battery\", \"battery#bat2\"],\n\"battery\": {\n\t\"bat\": \"BAT1\"\n},\n\"battery#bat2\": {\n\t\"bat\": \"BAT2\"\n}\n```\n\n# MINIMAL CONFIGURATION\n\nA minimal *config* file could look like this:\n\n```\n{\n\t\"layer\": \"top\",\n\t\"modules-left\": [\"sway/workspaces\", \"sway/mode\"],\n\t\"modules-center\": [\"sway/window\"],\n\t\"modules-right\": [\"battery\", \"clock\"],\n\t\"sway/window\": {\n\t\t\"max-length\": 50\n\t},\n\t\"battery\": {\n\t\t\"format\": \"{capacity}% {icon}\",\n\t\t\"format-icons\": [\"\", \"\", \"\", \"\", \"\"]\n\t},\n\t\"clock\": {\n\t\t\"format-alt\": \"{:%a, %d. %b  %H:%M}\"\n\t}\n}\n```\n\n# SIGNALS\n\nWaybar accepts the following signals:\n\n*SIGUSR1*\n\tBy default toggles the bar visibility (hides if shown, shows if hidden)\n*SIGUSR2*\n\tBy default reloads (resets) the bar\n*SIGINT*\n\tQuits the bar\n\nFor example, to toggle the bar programmatically, you can invoke `killall -SIGUSR1 waybar`.\n\n## User signal configuration\n\nConfig parameters *on-sigusr1* and *on-sigusr2* change what happens when bars receive\n*SIGUSR1* and *SIGUSR2* signals.\n\nThis means that commands `killall -SIGUSR1 waybar` and `killall -SIGUSR2 waybar`\ncan perform user-configured action.\n\nIt also means that if an external script has the PID of the bar then it can\nperform more complex `show`/`hide`/`reload` logic for each instance of Waybar.\nOne can find the PID e.g. by doing `pgrep -a waybar` which could then match\nby config name or other parameters.\n\n## Kill parameter meanings\n\n*show*    Switches state to visible (per bar).\n*hide*    Switches state to hidden (per bar).\n*toggle*  Switches state between visible and hidden (per bar).\n*reload*  Reloads all waybars of current waybar process (basically equivalent to\nrestarting with updated config which sets initial visibility values).\n*noop*    Does nothing when the kill signal is received.\n\n# MULTI OUTPUT CONFIGURATION\n\n## Limit a configuration to some outputs\n\n```\n{\n\t\"layer\": \"top\",\n\t\"output\": \"eDP-1\",\n\t\"modules-left\": [\"sway/workspaces\", \"sway/mode\"],\n\t...\n\n}\n\n```\n\n```\n{\n\t\"layer\": \"top\",\n\t\"output\": [\"eDP-1\", \"VGA\"],\n\t\"modules-left\": [\"sway/workspaces\", \"sway/mode\"],\n\t...\n}\n\n```\n\n## Configuration of multiple outputs\n\nDon't specify an output to create multiple bars on the same screen.\n\n```\n[{\n\t\"layer\": \"top\",\n\t\"output\": \"eDP-1\",\n\t\"modules-left\": [\"sway/workspaces\", \"sway/mode\"],\n\t...\n}, {\n\t\"layer\": \"top\",\n\t\"output\": \"VGA\",\n\t\"modules-right\": [\"clock\"],\n\t...\n}]\n\n```\n\n## Rotating modules\n\nWhen positioning Waybar on the left or right side of the screen, sometimes it's useful to be able to rotate the contents of a module so the text runs vertically. This can be done using the \"rotate\" property of the module. Example:\n\n```\n{\n\t\"clock\": {\n\t\t\"rotate\": 90\n\t}\n}\n```\n\nValid options for the \"rotate\" property are: 0, 90, 180, and 270.\n\n## Swapping icon and label\n\nIf a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the \"swap-icon-label\" property, taking a boolean. Example:\n```\n{\n\t\"sway/window\": {\n\t\t\"swap-icon-label\": true\n\t}\n}\n```\n\n## Grouping modules\n\nModule groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the \"orientation\" property.\n\nA module group is defined by specifying a module named \"group/some-group-name\". The group must also be configured with a list of contained modules. Example:\n\n```\n{\n\t\"modules-right\": [\"group/hardware\", \"clock\"],\n\n\t\"group/hardware\": {\n\t\t\"orientation\": \"vertical\",\n\t\t\"modules\": [\n\t\t\t\"cpu\",\n\t\t\t\"memory\",\n\t\t\t\"battery\"\n\t\t]\n\t},\n\n\t...\n}\n```\n\nValid options for the (optional) \"orientation\" property are: \"horizontal\", \"vertical\", \"inherit\", and \"orthogonal\" (default).\n\n## Group Drawers\n\nA group may hide all but one element, showing them only on mouse hover. In order to configure this, you can use the `drawer` property, whose value is an object with the following properties:\n\n*transition-duration*: ++\n\ttypeof: integer ++\n\tdefault: 500 ++\n\tDefines the duration of the transition animation in milliseconds.\n\n*children-class*: ++\n\ttypeof: string ++\n\tdefault: \"hidden\" ++\n\tDefines the CSS class to be applied to the hidden elements.\n\n*click-to-reveal*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tWhether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events.\n\n*start-expanded*: ++\n\ttypeof: bool ++\n\tdefault: false ++\n\tDefines whether the drawer should initialize in an expanded state.\n\n*transition-left-to-right*: ++\n\ttypeof: bool ++\n\tdefault: true ++\n\tDefines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left.\n\tWhen the bar is vertical, it reads as top-to-bottom.\n\n```\n\"group/power\": {\n    \"orientation\": \"inherit\",\n    \"drawer\": {\n        \"transition-duration\": 500,\n        \"children-class\": \"not-power\",\n        \"transition-left-to-right\": false,\n    },\n    \"modules\": [\n        \"custom/power\", // First element is the \"group leader\" and won't ever be hidden\n        \"custom/quit\",\n        \"custom/lock\",\n        \"custom/reboot\",\n    ]\n},\n```\n\n# SUPPORTED MODULES\n\n- *waybar-backlight(5)*\n- *waybar-battery(5)*\n- *waybar-bluetooth(5)*\n- *waybar-cava(5)*\n- *waybar-clock(5)*\n- *waybar-cpu(5)*\n- *waybar-custom(5)*\n- *waybar-disk(5)*\n- *waybar-dwl-tags(5)*\n- *waybar-dwl-window(5)*\n- *waybar-gamemode(5)*\n- *waybar-hyprland-language(5)*\n- *waybar-hyprland-submap(5)*\n- *waybar-hyprland-window(5)*\n- *waybar-hyprland-workspaces(5)*\n- *waybar-niri-language(5)*\n- *waybar-niri-window(5)*\n- *waybar-niri-workspaces(5)*\n- *waybar-idle-inhibitor(5)*\n- *waybar-image(5)*\n- *waybar-inhibitor(5)*\n- *waybar-jack(5)*\n- *waybar-keyboard-state(5)*\n- *waybar-memory(5)*\n- *waybar-mpd(5)*\n- *waybar-mpris(5)*\n- *waybar-network(5)*\n- *waybar-pulseaudio(5)*\n- *waybar-river-layout(5)*\n- *waybar-river-mode(5)*\n- *waybar-river-tags(5)*\n- *waybar-river-window(5)*\n- *waybar-sndio(5)*\n- *waybar-states(5)*\n- *waybar-sway-language(5)*\n- *waybar-sway-mode(5)*\n- *waybar-sway-scratchpad(5)*\n- *waybar-sway-window(5)*\n- *waybar-sway-workspaces(5)*\n- *waybar-temperature(5)*\n- *waybar-tray(5)*\n- *waybar-upower(5)*\n- *waybar-wireplumber(5)*\n- *waybar-wlr-taskbar(5)*\n- *waybar-wlr-workspaces(5)*\n\n# SEE ALSO\n\n*sway-output(5)*\n*waybar-styles(5)*\n"
  },
  {
    "path": "meson.build",
    "content": "project(\n    'waybar', 'cpp', 'c',\n    version: '0.15.0',\n    license: 'MIT',\n    meson_version: '>= 0.59.0',\n    default_options : [\n        'cpp_std=c++20',\n        'buildtype=release',\n        'default_library=static'\n    ],\n)\n\ncompiler = meson.get_compiler('cpp')\n\ncpp_args = []\ncpp_link_args = []\n\nif get_option('libcxx')\n    cpp_args += ['-stdlib=libc++']\n    cpp_link_args += ['-stdlib=libc++', '-lc++abi']\nendif\n\nif compiler.has_link_argument('-lc++fs')\n    cpp_link_args += ['-lc++fs']\nelif compiler.has_link_argument('-lstdc++fs')\n    cpp_link_args += ['-lstdc++fs']\nendif\n\ngit = find_program('git', native: true, required: false)\n\nif not git.found()\n    add_project_arguments('-DVERSION=\"@0@\"'.format(meson.project_version()), language: 'cpp')\nelse\n    git_path = run_command(git, 'rev-parse', '--show-toplevel', check: false).stdout().strip()\n    if meson.project_source_root() == git_path\n        git_commit_hash = run_command(git, 'describe', '--always', '--tags', check: false).stdout().strip()\n        git_branch = run_command(git, 'rev-parse', '--abbrev-ref', 'HEAD', check: false).stdout().strip()\n        version = '\"@0@ (branch \\'@1@\\')\"'.format(git_commit_hash, git_branch)\n        add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp')\n    else\n        add_project_arguments('-DVERSION=\"@0@\"'.format(meson.project_version()), language: 'cpp')\n    endif\nendif\n\ncode = '''\n#include <langinfo.h>\n#include <locale.h>\nint main(int argc, char** argv) {\n    locale_t locale = newlocale(LC_ALL, \"en_US.UTF-8\", nullptr);\n    char* str;\n    str = nl_langinfo_l(_NL_TIME_WEEK_1STDAY, locale);\n    str = nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, locale);\n    freelocale(locale);\n    return 0;\n}\n'''\nif compiler.links(code, name : 'nl_langinfo with _NL_TIME_WEEK_1STDAY, _NL_TIME_FIRST_WEEKDAY')\n    add_project_arguments('-DHAVE_LANGINFO_1STDAY', language: 'cpp')\nendif\n\nadd_global_arguments(cpp_args, language : 'cpp')\nadd_global_link_arguments(cpp_link_args, language : 'cpp')\n\nis_linux = host_machine.system() == 'linux'\nis_dragonfly = host_machine.system() == 'dragonfly'\nis_freebsd = host_machine.system() == 'freebsd'\nis_netbsd = host_machine.system() == 'netbsd'\nis_openbsd = host_machine.system() == 'openbsd'\n\nthread_dep = dependency('threads')\nfmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep'])\nspdlog = dependency('spdlog', version : ['>=1.15.2'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['std_format=disabled', 'tests=disabled'])\nwayland_client = dependency('wayland-client')\nwayland_cursor = dependency('wayland-cursor')\nwayland_protos = dependency('wayland-protocols')\ngtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])\ndbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))\ngiounix = dependency('gio-unix-2.0')\njsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])\nsigcpp = dependency('sigc++-2.0')\nlibinotify = dependency('libinotify', required: false)\nlibepoll = dependency('epoll-shim', required: false)\nlibinput = dependency('libinput', required: get_option('libinput'))\nlibnl = dependency('libnl-3.0', required: get_option('libnl'))\nlibnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))\nupower_glib = dependency('upower-glib', required: get_option('upower_glib'))\npipewire = dependency('libpipewire-0.3', required: get_option('pipewire'))\nplayerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))\nlibpulse = dependency('libpulse', required: get_option('pulseaudio'))\nlibudev = dependency('libudev', required: get_option('libudev'))\nlibevdev = dependency('libevdev', required: get_option('libevdev'))\nlibmpdclient = dependency('libmpdclient', required: get_option('mpd'))\nxkbregistry = dependency('xkbregistry')\nlibjack = dependency('jack', required: get_option('jack'))\nlibwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber'))\nlibgps = dependency('libgps', required: get_option('gps'))\n\nlibsndio = compiler.find_library('sndio', required: get_option('sndio'))\nif libsndio.found()\n    if not compiler.has_function('sioctl_open', prefix: '#include <sndio.h>', dependencies: libsndio)\n        if get_option('sndio').enabled()\n            error('libsndio is too old, required >=1.7.0')\n        else\n            warning('libsndio is too old, required >=1.7.0')\n            libsndio = dependency('', required: false)\n        endif\n    endif\nendif\n\ngtk_layer_shell = dependency('gtk-layer-shell-0', version: ['>=0.9.0'],\n        default_options: ['introspection=false', 'vapi=false'],\n        fallback: ['gtk-layer-shell', 'gtk_layer_shell'])\nsystemd = dependency('systemd', required: get_option('systemd'))\n\ncpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include <chrono>')\nhave_chrono_timezones = cpp_lib_chrono >= 201611\n\nif have_chrono_timezones\n   code = '''\n#include <chrono>\nusing namespace std::chrono;\nint main(int argc, char** argv) {\n   const time_zone* tz;\n   return 0;\n}\n'''\n   if not compiler.links(code)\n      have_chrono_timezones = false\n   endif\nendif\n\nif have_chrono_timezones\n  tz_dep = declare_dependency()\nelse\n  tz_dep = dependency('date',\n      required: false,\n      default_options : [ 'use_system_tzdb=true' ],\n      modules : [ 'date::date', 'date::date-tz' ],\n      fallback: [ 'date', 'tz_dep' ])\nendif\n\nprefix = get_option('prefix')\nsysconfdir = get_option('sysconfdir')\nconf_data = configuration_data()\nconf_data.set('prefix', prefix)\n\nadd_project_arguments('-DSYSCONFDIR=\"@0@\"'.format(prefix / sysconfdir), language : 'cpp')\n\nif systemd.found()\n  user_units_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir')\n\n  configure_file(\n    configuration: conf_data,\n    input: './resources/waybar.service.in',\n    output: '@BASENAME@',\n    install_dir: user_units_dir\n  )\nendif\n\nsrc_files = files(\n    'src/factory.cpp',\n    'src/AModule.cpp',\n    'src/ALabel.cpp',\n    'src/AIconLabel.cpp',\n    'src/AAppIconLabel.cpp',\n    'src/modules/custom.cpp',\n    'src/modules/disk.cpp',\n    'src/modules/idle_inhibitor.cpp',\n    'src/modules/image.cpp',\n    'src/modules/load.cpp',\n    'src/modules/temperature.cpp',\n    'src/modules/user.cpp',\n    'src/ASlider.cpp',\n    'src/main.cpp',\n    'src/bar.cpp',\n    'src/client.cpp',\n    'src/config.cpp',\n    'src/group.cpp',\n    'src/util/portal.cpp',\n    'src/util/enum.cpp',\n    'src/util/prepare_for_sleep.cpp',\n    'src/util/ustring_clen.cpp',\n    'src/util/sanitize_str.cpp',\n    'src/util/rewrite_string.cpp',\n    'src/util/gtk_icon.cpp',\n    'src/util/icon_loader.cpp',\n    'src/util/regex_collection.cpp',\n    'src/util/css_reload_helper.cpp'\n)\n\nman_files = files(\n    'man/waybar-custom.5.scd',\n    'man/waybar-disk.5.scd',\n    'man/waybar-idle-inhibitor.5.scd',\n    'man/waybar-image.5.scd',\n    'man/waybar-states.5.scd',\n    'man/waybar-menu.5.scd',\n    'man/waybar-temperature.5.scd',\n)\n\ninc_dirs = ['include']\n\nif is_linux\n    add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp')\n    add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')\n    add_project_arguments('-DHAVE_SYSTEMD_MONITOR', language: 'cpp')\n    src_files += files(\n        'src/modules/battery.cpp',\n        'src/modules/bluetooth.cpp',\n        'src/modules/cffi.cpp',\n        'src/modules/cpu.cpp',\n        'src/modules/cpu_frequency/common.cpp',\n        'src/modules/cpu_frequency/linux.cpp',\n        'src/modules/cpu_usage/common.cpp',\n        'src/modules/cpu_usage/linux.cpp',\n        'src/modules/memory/common.cpp',\n        'src/modules/memory/linux.cpp',\n        'src/modules/power_profiles_daemon.cpp',\n        'src/modules/systemd_failed_units.cpp',\n    )\n    man_files += files(\n        'man/waybar-battery.5.scd',\n        'man/waybar-bluetooth.5.scd',\n        'man/waybar-cffi.5.scd',\n        'man/waybar-cpu.5.scd',\n        'man/waybar-memory.5.scd',\n        'man/waybar-systemd-failed-units.5.scd',\n        'man/waybar-power-profiles-daemon.5.scd',\n    )\nelif is_dragonfly or is_freebsd or is_netbsd or is_openbsd\n    add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp')\n    add_project_arguments('-DHAVE_MEMORY_BSD', language: 'cpp')\n    src_files += files(\n        'src/modules/cffi.cpp',\n        'src/modules/cpu.cpp',\n        'src/modules/cpu_frequency/bsd.cpp',\n        'src/modules/cpu_frequency/common.cpp',\n        'src/modules/cpu_usage/bsd.cpp',\n        'src/modules/cpu_usage/common.cpp',\n        'src/modules/memory/bsd.cpp',\n        'src/modules/memory/common.cpp',\n    )\n    man_files += files(\n        'man/waybar-cffi.5.scd',\n        'man/waybar-cpu.5.scd',\n        'man/waybar-memory.5.scd',\n    )\n    if is_freebsd\n        src_files += files('src/modules/battery.cpp')\n        man_files += files('man/waybar-battery.5.scd')\n    endif\nendif\n\nif true\n    add_project_arguments('-DHAVE_SWAY', language: 'cpp')\n    src_files += files(\n        'src/modules/sway/ipc/client.cpp',\n        'src/modules/sway/bar.cpp',\n        'src/modules/sway/mode.cpp',\n        'src/modules/sway/language.cpp',\n        'src/modules/sway/window.cpp',\n        'src/modules/sway/workspaces.cpp',\n        'src/modules/sway/scratchpad.cpp'\n    )\n    man_files += files(\n        'man/waybar-sway-language.5.scd',\n        'man/waybar-sway-mode.5.scd',\n        'man/waybar-sway-scratchpad.5.scd',\n        'man/waybar-sway-window.5.scd',\n        'man/waybar-sway-workspaces.5.scd',\n    )\nendif\n\nif true\n    add_project_arguments('-DHAVE_WLR_TASKBAR', language: 'cpp')\n    src_files += files('src/modules/wlr/taskbar.cpp')\n    man_files += files('man/waybar-wlr-taskbar.5.scd')\nendif\n\nif wayland_protos.version().version_compare('>=1.39')\n    add_project_arguments('-DHAVE_EXT_WORKSPACES', language: 'cpp')\n    src_files += files(\n        'src/modules/ext/workspace_manager.cpp',\n        'src/modules/ext/workspace_manager_binding.cpp',\n    )\n    man_files += files(\n        'man/waybar-ext-workspaces.5.scd',\n    )\nendif\n\nif true\n    add_project_arguments('-DHAVE_RIVER', language: 'cpp')\n    src_files += files(\n        'src/modules/river/layout.cpp',\n        'src/modules/river/mode.cpp',\n        'src/modules/river/tags.cpp',\n        'src/modules/river/window.cpp',\n    )\n    man_files += files(\n        'man/waybar-river-layout.5.scd',\n        'man/waybar-river-mode.5.scd',\n        'man/waybar-river-tags.5.scd',\n        'man/waybar-river-window.5.scd',\n    )\nendif\n\nif true\n    add_project_arguments('-DHAVE_DWL', language: 'cpp')\n    src_files += files('src/modules/dwl/tags.cpp')\n    src_files += files('src/modules/dwl/window.cpp')\n    man_files += files('man/waybar-dwl-tags.5.scd')\n    man_files += files('man/waybar-dwl-window.5.scd')\nendif\n\nif true\n    add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp')\n    src_files += files(\n        'src/modules/hyprland/backend.cpp',\n        'src/modules/hyprland/language.cpp',\n        'src/modules/hyprland/submap.cpp',\n        'src/modules/hyprland/window.cpp',\n        'src/modules/hyprland/windowcount.cpp',\n        'src/modules/hyprland/workspace.cpp',\n        'src/modules/hyprland/workspaces.cpp',\n        'src/modules/hyprland/windowcreationpayload.cpp',\n    )\n    man_files += files(\n        'man/waybar-hyprland-language.5.scd',\n        'man/waybar-hyprland-submap.5.scd',\n        'man/waybar-hyprland-window.5.scd',\n        'man/waybar-hyprland-workspaces.5.scd',\n    )\nendif\n\nif get_option('niri')\n    add_project_arguments('-DHAVE_NIRI', language: 'cpp')\n    src_files += files(\n        'src/modules/niri/backend.cpp',\n        'src/modules/niri/language.cpp',\n        'src/modules/niri/window.cpp',\n        'src/modules/niri/workspaces.cpp',\n    )\n    man_files += files(\n        'man/waybar-niri-language.5.scd',\n        'man/waybar-niri-window.5.scd',\n        'man/waybar-niri-workspaces.5.scd',\n    )\nendif\n\nif true\n    add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')\n    src_files += files(\n        'src/modules/wayfire/backend.cpp',\n        'src/modules/wayfire/window.cpp',\n        'src/modules/wayfire/workspaces.cpp',\n    )\nendif\n\nif get_option('login-proxy')\n    add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp')\nendif\n\nif libnl.found() and libnlgen.found()\n    add_project_arguments('-DHAVE_LIBNL', language: 'cpp')\n    src_files += files('src/modules/network.cpp')\n    man_files += files('man/waybar-network.5.scd')\nendif\n\nif not get_option('logind').disabled()\n    add_project_arguments('-DHAVE_GAMEMODE', '-DHAVE_LOGIND_INHIBITOR', language: 'cpp')\n    src_files += files(\n        'src/modules/gamemode.cpp',\n        'src/modules/inhibitor.cpp',\n    )\n    man_files += files(\n        'man/waybar-gamemode.5.scd',\n        'man/waybar-inhibitor.5.scd',\n    )\nendif\n\nif (upower_glib.found() and not get_option('logind').disabled())\n    add_project_arguments('-DHAVE_UPOWER', language: 'cpp')\n    src_files += files('src/modules/upower.cpp')\n    man_files += files('man/waybar-upower.5.scd')\nendif\n\n\nif pipewire.found()\n    add_project_arguments('-DHAVE_PIPEWIRE', language: 'cpp')\n    src_files += files(\n        'src/modules/privacy/privacy.cpp',\n        'src/modules/privacy/privacy_item.cpp',\n        'src/util/pipewire/pipewire_backend.cpp',\n        'src/util/pipewire/privacy_node_info.cpp',\n    )\n    man_files += files('man/waybar-privacy.5.scd')\nendif\n\nif playerctl.found()\n    add_project_arguments('-DHAVE_MPRIS', language: 'cpp')\n    src_files += files('src/modules/mpris/mpris.cpp')\n    man_files += files('man/waybar-mpris.5.scd')\nendif\n\nif libpulse.found()\n    add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')\n    src_files += files(\n        'src/modules/pulseaudio.cpp',\n        'src/modules/pulseaudio_slider.cpp',\n        'src/util/audio_backend.cpp',\n    )\n    man_files += files(\n        'man/waybar-pulseaudio.5.scd',\n        'man/waybar-pulseaudio-slider.5.scd',\n    )\nendif\n\nif libjack.found()\n    add_project_arguments('-DHAVE_LIBJACK', language: 'cpp')\n    src_files += files('src/modules/jack.cpp')\n    man_files += files('man/waybar-jack.5.scd')\nendif\n\nif libwireplumber.found()\n    add_project_arguments('-DHAVE_LIBWIREPLUMBER', language: 'cpp')\n    src_files += files('src/modules/wireplumber.cpp')\n    man_files += files('man/waybar-wireplumber.5.scd')\nendif\n\nif dbusmenu_gtk.found()\n    add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')\n    src_files += files(\n        'src/modules/sni/tray.cpp',\n        'src/modules/sni/watcher.cpp',\n        'src/modules/sni/host.cpp',\n        'src/modules/sni/item.cpp'\n    )\n    man_files += files(\n        'man/waybar-tray.5.scd',\n    )\nendif\n\nif libudev.found() and (is_linux or libepoll.found())\n    add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')\n    src_files += files(\n        'src/modules/backlight.cpp',\n        'src/modules/backlight_slider.cpp',\n        'src/util/backlight_backend.cpp',\n    )\n    man_files += files(\n        'man/waybar-backlight.5.scd',\n        'man/waybar-backlight-slider.5.scd',\n    )\nendif\n\nif libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())\n    add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')\n    add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp')\n    src_files += files('src/modules/keyboard_state.cpp')\n    man_files += files('man/waybar-keyboard-state.5.scd')\nendif\n\nif libmpdclient.found()\n    add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')\n    src_files += files(\n        'src/modules/mpd/mpd.cpp',\n        'src/modules/mpd/state.cpp',\n    )\n    man_files += files(\n        'man/waybar-mpd.5.scd',\n    )\nendif\n\nif libsndio.found()\n    add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp')\n    src_files += files('src/modules/sndio.cpp')\n    man_files += files('man/waybar-sndio.5.scd')\nendif\n\nif get_option('rfkill').enabled() and is_linux\n    add_project_arguments('-DWANT_RFKILL', language: 'cpp')\n    src_files += files(\n        'src/util/rfkill.cpp'\n    )\nendif\n\nif have_chrono_timezones\n    add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp')\n    src_files += files('src/modules/clock.cpp')\n    man_files += files('man/waybar-clock.5.scd')\nelif tz_dep.found()\n    add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')\n    src_files += files('src/modules/clock.cpp')\n    man_files += files('man/waybar-clock.5.scd')\nelse\n    src_files += files('src/modules/simpleclock.cpp')\n    man_files += files('man/waybar-clock.5.scd')\nendif\n\ncava = dependency('libcava',\n                  version : '>=0.10.7',\n                  required: get_option('cava'),\n                  fallback : ['libcava', 'cava_dep'],\n                  not_found_message: 'cava is not found. Building waybar without cava')\n\neproxy = dependency('epoxy', required: false)\n\nif cava.found()\n   add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')\n   src_files += files('src/modules/cava/cavaRaw.cpp',\n                      'src/modules/cava/cava_backend.cpp')\n   man_files += files('man/waybar-cava.5.scd')\n\n   if eproxy.found()\n      add_project_arguments('-DHAVE_LIBCAVAGLSL', language: 'cpp')\n      src_files += files('src/modules/cava/cavaGLSL.cpp')\n   endif\nendif\n\nif libgps.found()\n   add_project_arguments('-DHAVE_LIBGPS', language: 'cpp')\n   src_files += files('src/modules/gps.cpp')\n   man_files += files('man/waybar-gps.5.scd')\nendif\n\nsubdir('protocol')\n\napp_resources = []\nsubdir('resources/icons')\n\nexecutable(\n    'waybar',\n    [src_files, app_resources],\n    dependencies: [\n        thread_dep,\n        client_protos,\n        wayland_client,\n        fmt,\n        spdlog,\n        sigcpp,\n        jsoncpp,\n        wayland_cursor,\n        gtkmm,\n        dbusmenu_gtk,\n        giounix,\n        libinput,\n        libnl,\n        libnlgen,\n        upower_glib,\n        pipewire,\n        playerctl,\n        libpulse,\n        libjack,\n        libwireplumber,\n        libudev,\n        libinotify,\n        libepoll,\n        libmpdclient,\n        libevdev,\n        gtk_layer_shell,\n        libsndio,\n        tz_dep,\n\t\txkbregistry,\n        cava,\n        eproxy,\n        libgps\n    ],\n    include_directories: inc_dirs,\n    install: true,\n)\n\ninstall_data(\n    'resources/config.jsonc',\n    'resources/style.css',\n    install_dir: sysconfdir / 'xdg/waybar'\n)\n\nscdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))\n\nif scdoc.found()\n    man_files += configure_file(\n        input: 'man/waybar.5.scd.in',\n        output: 'waybar.5.scd',\n        configuration: {\n            'sysconfdir': prefix / sysconfdir\n        }\n    )\n\n    man_files += configure_file(\n        input: 'man/waybar-styles.5.scd.in',\n        output: 'waybar-styles.5.scd',\n        configuration: {\n            'sysconfdir': prefix / sysconfdir\n        }\n    )\n\n    fs = import('fs')\n    mandir = get_option('mandir')\n    foreach file : man_files\n        basename = fs.name(file)\n\n        topic = basename.split('.')[-3]\n        section = basename.split('.')[-2]\n        output = '@0@.@1@'.format(topic, section)\n\n        custom_target(\n            output,\n            input: file,\n            output: output,\n            command: scdoc.get_variable('scdoc'),\n            feed: true,\n            capture: true,\n            install: true,\n            install_dir: '@0@/man@1@'.format(mandir, section)\n        )\n    endforeach\nendif\n\ncatch2 = dependency(\n    'catch2',\n    default_options: [ 'tests=false' ],\n    fallback: ['catch2', 'catch2_dep'],\n    required: get_option('tests'),\n)\nif catch2.found()\n    subdir('test')\nendif\n\nclangtidy = find_program('clang-tidy', required: false)\n\nif clangtidy.found()\n    run_target(\n        'tidy',\n        command: [\n            clangtidy,\n            '-checks=*,-fuchsia-default-arguments',\n            '-p', meson.project_build_root()\n        ] + src_files)\nendif\n"
  },
  {
    "path": "meson_options.txt",
    "content": "option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\\'s libc++ instead of libstdc++ on Linux.')\noption('libinput', type: 'feature', value: 'auto', description: 'Enable libinput support for libinput related features')\noption('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')\noption('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')\noption('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')\noption('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')\noption('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')\noption('pipewire', type: 'feature', value: 'auto', description: 'Enable support for pipewire')\noption('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')\noption('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')\noption('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')\noption('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')\noption('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')\noption('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')\noption('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')\noption('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')\noption('tests', type: 'feature', value: 'auto', description: 'Enable tests')\noption('experimental', type : 'boolean', value : false, description: 'Enable experimental features')\noption('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')\noption('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')\noption('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')\noption('niri', type: 'boolean', description: 'Enable support for niri')\noption('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface')\noption('gps', type: 'feature', value: 'auto', description: 'Enable support for gps')\n"
  },
  {
    "path": "nix/default.nix",
    "content": "{\n  lib,\n  pkgs,\n  waybar,\n  version,\n}:\nlet\n  libcava = rec {\n    version = \"0.10.7\";\n    src = pkgs.fetchFromGitHub {\n      owner = \"LukashonakV\";\n      repo = \"cava\";\n      # NOTE: Needs to match the cava.wrap\n      tag = \"${version}\";\n      hash = \"sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ=\";\n    };\n  };\nin\nwaybar.overrideAttrs (oldAttrs: {\n  inherit version;\n\n  src = lib.cleanSourceWith {\n    filter = name: type: type != \"regular\" || !lib.hasSuffix \".nix\" name;\n    src = lib.cleanSource ../.;\n  };\n\n  mesonFlags = lib.remove \"-Dgtk-layer-shell=enabled\" oldAttrs.mesonFlags;\n\n  # downstream patch should not affect upstream\n  patches = [ ];\n  # nixpkgs checks version, no need when building locally\n  nativeInstallCheckInputs = [ ];\n\n  buildInputs =\n    (builtins.filter (p: p.pname != \"wireplumber\" && p.pname != \"gps\") oldAttrs.buildInputs)\n    ++ [\n      pkgs.wireplumber\n      pkgs.gpsd\n    ];\n\n  postUnpack = ''\n    pushd \"$sourceRoot\"\n    cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version}\n    patchShebangs .\n    popd\n  '';\n})\n"
  },
  {
    "path": "protocol/dbus-menu.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n<interface name=\"com.canonical.dbusmenu\">\n    <!-- Properties -->\n    <property name=\"Version\" type=\"u\" access=\"read\" />\n    <property name=\"TextDirection\" type=\"s\" access=\"read\" />\n    <property name=\"Status\" type=\"s\" access=\"read\" />\n    <property name=\"IconThemePath\" type=\"as\" access=\"read\" />\n\n    <!-- Functions -->\n    <method name=\"GetLayout\">\n        <arg type=\"i\" name=\"parentId\" direction=\"in\" />\n        <arg type=\"i\" name=\"recursionDepth\" direction=\"in\" />\n        <arg type=\"as\" name=\"propertyNames\" direction=\"in\" />\n        <arg type=\"u\" name=\"revision\" direction=\"out\" />\n        <arg type=\"(ia{sv}av)\" name=\"layout\" direction=\"out\" />\n    </method>\n\n    <method name=\"GetGroupProperties\">\n        <arg type=\"ai\" name=\"ids\" direction=\"in\" />\n        <arg type=\"as\" name=\"propertyNames\" direction=\"in\" />\n        <arg type=\"a(ia{sv})\" name=\"properties\" direction=\"out\" />\n    </method>\n\n    <method name=\"GetProperty\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"s\" name=\"name\" direction=\"in\" />\n        <arg type=\"v\" name=\"value\" direction=\"out\" />\n    </method>\n\n    <method name=\"Event\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"s\" name=\"eventId\" direction=\"in\" />\n        <arg type=\"v\" name=\"data\" direction=\"in\" />\n        <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n    </method>\n\n    <method name=\"EventGroup\">\n        <arg type=\"a(isvu)\" name=\"events\" direction=\"in\" />\n        <arg type=\"ai\" name=\"idErrors\" direction=\"out\" />\n    </method>\n\n    <method name=\"AboutToShow\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"b\" name=\"needUpdate\" direction=\"out\" />\n    </method>\n\n    <method name=\"AboutToShowGroup\">\n        <arg type=\"ai\" name=\"ids\" direction=\"in\" />\n        <arg type=\"ai\" name=\"updatesNeeded\" direction=\"out\" />\n        <arg type=\"ai\" name=\"idErrors\" direction=\"out\" />\n    </method>\n\n    <!-- Signals -->\n    <signal name=\"ItemsPropertiesUpdated\">\n        <arg type=\"a(ia{sv})\" name=\"updatedProps\" direction=\"out\" />\n        <arg type=\"a(ias)\" name=\"removedProps\" direction=\"out\" />\n    </signal>\n    <signal name=\"LayoutUpdated\">\n        <arg type=\"u\" name=\"revision\" direction=\"out\" />\n        <arg type=\"i\" name=\"parent\" direction=\"out\" />\n    </signal>\n    <signal name=\"ItemActivationRequested\">\n        <arg type=\"i\" name=\"id\" direction=\"out\" />\n        <arg type=\"u\" name=\"timestamp\" direction=\"out\" />\n    </signal>\n</interface>\n</node>"
  },
  {
    "path": "protocol/dbus-status-notifier-item.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n  <interface name='org.kde.StatusNotifierItem'>\n    <annotation name=\"org.gtk.GDBus.C.Name\" value=\"Item\" />\n    <method name='ContextMenu'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='Activate'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='SecondaryActivate'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='Scroll'>\n      <arg type='i' direction='in' name='delta'/>\n      <arg type='s' direction='in' name='orientation'/>\n    </method>\n    <signal name='NewTitle'/>\n    <signal name='NewIcon'/>\n    <signal name='NewAttentionIcon'/>\n    <signal name='NewOverlayIcon'/>\n    <signal name='NewToolTip'/>\n    <signal name='NewStatus'>\n      <arg type='s' name='status'/>\n    </signal>\n    <property name='Category' type='s' access='read'/>\n    <property name='Id' type='s' access='read'/>\n    <property name='Title' type='s' access='read'/>\n    <property name='Status' type='s' access='read'/>\n    <!-- See discussion on pull #536\n    <property name='WindowId' type='u' access='read'/>\n    -->\n    <property name='IconThemePath' type='s' access='read'/>\n    <property name='IconName' type='s' access='read'/>\n    <property name='IconPixmap' type='a(iiay)' access='read'/>\n    <property name='OverlayIconName' type='s' access='read'/>\n    <property name='OverlayIconPixmap' type='a(iiay)' access='read'/>\n    <property name='AttentionIconName' type='s' access='read'/>\n    <property name='AttentionIconPixmap' type='a(iiay)' access='read'/>\n    <property name='AttentionMovieName' type='s' access='read'/>\n    <property name='ToolTip' type='(sa(iiay)ss)' access='read'/>\n    <property name='Menu' type='o' access='read'/>\n    <property name='ItemIsMenu' type='b' access='read'/>\n  </interface>\n</node>\n"
  },
  {
    "path": "protocol/dbus-status-notifier-watcher.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n  <interface name=\"org.kde.StatusNotifierWatcher\">\n    <annotation name=\"org.gtk.GDBus.C.Name\" value=\"Watcher\" />\n\n    <!-- methods -->\n    <method name=\"RegisterStatusNotifierItem\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisterItem\" />\n      <arg name=\"service\" type=\"s\" direction=\"in\"/>\n    </method>\n\n    <method name=\"RegisterStatusNotifierHost\">\n        <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisterHost\" />\n        <arg name=\"service\" type=\"s\" direction=\"in\"/>\n    </method>\n\n\n    <!-- properties -->\n\n    <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisteredItems\" />\n      <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QStringList\"/>\n    </property>\n\n    <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"IsHostRegistered\" />\n    </property>\n\n    <property name=\"ProtocolVersion\" type=\"i\" access=\"read\"/>\n\n\n    <!-- signals -->\n\n    <signal name=\"StatusNotifierItemRegistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"ItemRegistered\" />\n      <arg type=\"s\" direction=\"out\" name=\"service\" />\n    </signal>\n\n    <signal name=\"StatusNotifierItemUnregistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"ItemUnregistered\" />\n      <arg type=\"s\" direction=\"out\" name=\"service\" />\n    </signal>\n\n    <signal name=\"StatusNotifierHostRegistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"HostRegistered\" />\n    </signal>\n\n    <signal name=\"StatusNotifierHostUnregistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"HostUnregistered\" />\n    </signal>\n  </interface>\n</node>"
  },
  {
    "path": "protocol/dwl-ipc-unstable-v2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nThis is largely ripped from somebar's ipc patchset; just with some personal modifications.\nI would probably just submit raphi's patchset but I don't think that would be polite.\n-->\n<protocol name=\"dwl_ipc_unstable_v2\">\n  <description summary=\"inter-proccess-communication about dwl's state\">\n      This protocol allows clients to update and get updates from dwl.\n\n      Warning! The protocol described in this file is experimental and\n      backward incompatible changes may be made. Backward compatible\n      changes may be added together with the corresponding interface\n      version bump.\n      Backward incompatible changes are done by bumping the version\n      number in the protocol and interface names and resetting the\n      interface version. Once the protocol is to be declared stable,\n      the 'z' prefix and the version number in the protocol and\n      interface names are removed and the interface version number is\n      reset.\n  </description>\n\n  <interface name=\"zdwl_ipc_manager_v2\" version=\"1\">\n    <description summary=\"manage dwl state\">\n      This interface is exposed as a global in wl_registry.\n\n      Clients can use this interface to get a dwl_ipc_output.\n      After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.\n      The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.\n    </description>\n\n    <request name=\"release\" type=\"destructor\">\n      <description summary=\"release dwl_ipc_manager\">\n        Indicates that the client will not the dwl_ipc_manager object anymore.\n        Objects created through this instance are not affected.\n      </description>\n    </request>\n\n    <request name=\"get_output\">\n      <description summary=\"get a dwl_ipc_outout for a wl_output\">\n        Get a dwl_ipc_outout for the specified wl_output.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zdwl_ipc_output_v2\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </request>\n\n    <event name=\"tags\">\n      <description summary=\"Announces tag amount\">\n        This event is sent after binding.\n        A roundtrip after binding guarantees the client recieved all tags.\n      </description>\n      <arg name=\"amount\" type=\"uint\"/>\n    </event>\n\n    <event name=\"layout\">\n      <description summary=\"Announces a layout\">\n        This event is sent after binding.\n        A roundtrip after binding guarantees the client recieved all layouts.\n      </description>\n      <arg name=\"name\" type=\"string\"/>\n    </event>\n  </interface>\n\n  <interface name=\"zdwl_ipc_output_v2\" version=\"1\">\n    <description summary=\"control dwl output\">\n      Observe and control a dwl output.\n\n      Events are double-buffered:\n      Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.\n\n      Request are not double-buffered:\n      The compositor will update immediately upon request.\n    </description>\n\n    <enum name=\"tag_state\">\n      <entry name=\"none\" value=\"0\" summary=\"no state\"/>\n      <entry name=\"active\" value=\"1\" summary=\"tag is active\"/>\n      <entry name=\"urgent\" value=\"2\" summary=\"tag has at least one urgent client\"/>\n    </enum>\n\n    <request name=\"release\" type=\"destructor\">\n      <description summary=\"release dwl_ipc_outout\">\n        Indicates to that the client no longer needs this dwl_ipc_output.\n      </description>\n    </request>\n\n    <event name=\"toggle_visibility\">\n      <description summary=\"Toggle client visibilty\">\n        Indicates the client should hide or show themselves.\n        If the client is visible then hide, if hidden then show.\n      </description>\n    </event>\n\n    <event name=\"active\">\n      <description summary=\"Update the selected output.\">\n        Indicates if the output is active. Zero is invalid, nonzero is valid.\n      </description>\n      <arg name=\"active\" type=\"uint\"/>\n    </event>\n\n    <event name=\"tag\">\n      <description summary=\"Update the state of a tag.\">\n        Indicates that a tag has been updated.\n      </description>\n      <arg name=\"tag\" type=\"uint\" summary=\"Index of the tag\"/>\n      <arg name=\"state\" type=\"uint\" enum=\"tag_state\" summary=\"The state of the tag.\"/>\n      <arg name=\"clients\" type=\"uint\" summary=\"The number of clients in the tag.\"/>\n      <arg name=\"focused\" type=\"uint\" summary=\"If there is a focused client. Nonzero being valid, zero being invalid.\"/>\n    </event>\n\n    <event name=\"layout\">\n      <description summary=\"Update the layout.\">\n        Indicates a new layout is selected.\n      </description>\n      <arg name=\"layout\" type=\"uint\" summary=\"Index of the layout.\"/>\n    </event>\n\n    <event name=\"title\">\n      <description summary=\"Update the title.\">\n        Indicates the title has changed.\n      </description>\n      <arg name=\"title\" type=\"string\" summary=\"The new title name.\"/>\n    </event>\n\n    <event name=\"appid\" since=\"1\">\n      <description summary=\"Update the appid.\">\n        Indicates the appid has changed.\n      </description>\n      <arg name=\"appid\" type=\"string\" summary=\"The new appid.\"/>\n    </event>\n\n    <event name=\"layout_symbol\" since=\"1\">\n      <description summary=\"Update the current layout symbol\">\n          Indicates the layout has changed. Since layout symbols are dynamic.\n          As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying.\n          You can ignore the zdwl_ipc_output.layout event.\n      </description>\n      <arg name=\"layout\" type=\"string\" summary=\"The new layout\"/>\n    </event>\n\n    <event name=\"frame\">\n      <description summary=\"The update sequence is done.\">\n        Indicates that a sequence of status updates have finished and the client should redraw.\n      </description>\n    </event>\n\n    <request name=\"set_tags\">\n      <description summary=\"Set the active tags of this output\"/>\n      <arg name=\"tagmask\" type=\"uint\" summary=\"bitmask of the tags that should be set.\"/>\n      <arg name=\"toggle_tagset\" type=\"uint\" summary=\"toggle the selected tagset, zero for invalid, nonzero for valid.\"/>\n    </request>\n\n    <request name=\"set_client_tags\">\n      <description summary=\"Set the tags of the focused client.\">\n        The tags are updated as follows:\n        new_tags = (current_tags AND and_tags) XOR xor_tags\n      </description>\n      <arg name=\"and_tags\" type=\"uint\"/>\n      <arg name=\"xor_tags\" type=\"uint\"/>\n    </request>\n\n    <request name=\"set_layout\">\n      <description summary=\"Set the layout of this output\"/>\n      <arg name=\"index\" type=\"uint\" summary=\"index of a layout recieved by dwl_ipc_manager.layout\"/>\n    </request>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocol/meson.build",
    "content": "wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir')\n\nwayland_scanner = find_program('wayland-scanner')\n\n# should check wayland_scanner's version, but it is hard to get\nif wayland_client.version().version_compare('>=1.14.91')\n\tcode_type = 'private-code'\nelse\n\tcode_type = 'code'\nendif\n\nwayland_scanner_code = generator(\n\twayland_scanner,\n\toutput: '@BASENAME@-protocol.c',\n\targuments: [code_type, '@INPUT@', '@OUTPUT@'],\n)\n\nwayland_scanner_client = generator(\n\twayland_scanner,\n\toutput: '@BASENAME@-client-protocol.h',\n\targuments: ['client-header', '@INPUT@', '@OUTPUT@'],\n)\n\nclient_protocols = [\n\t[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],\n\t[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],\n\t[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],\n\t['wlr-foreign-toplevel-management-unstable-v1.xml'],\n\t['river-status-unstable-v1.xml'],\n\t['river-control-unstable-v1.xml'],\n\t['dwl-ipc-unstable-v2.xml'],\n]\n\nif wayland_protos.version().version_compare('>=1.39')\n    client_protocols += [\n        [wl_protocol_dir, 'staging/ext-workspace/ext-workspace-v1.xml']\n    ]\nendif\n\nclient_protos_src = []\nclient_protos_headers = []\n\nforeach p : client_protocols\n\txml = join_paths(p)\n\tclient_protos_src += wayland_scanner_code.process(xml)\n\tclient_protos_headers += wayland_scanner_client.process(xml)\nendforeach\n\ngdbus_codegen = find_program('gdbus-codegen')\n\nr = run_command(gdbus_codegen, '--body', '--output', '/dev/null', check: false)\nif r.returncode() != 0\n  gdbus_code_dsnw = custom_target(\n      'dbus-status-notifier-watcher.[ch]',\n      output: ['@BASENAME@.c','@BASENAME@.h'],\n      input: './dbus-status-notifier-watcher.xml',\n      command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],\n  )\n\n  gdbus_code_dsni = custom_target(\n      'dbus-status-notifier-item.[ch]',\n      output: ['@BASENAME@.c','@BASENAME@.h'],\n      input: './dbus-status-notifier-item.xml',\n      command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],\n  )\n\n  gdbus_code_dm = custom_target(\n      'dbus-menu.[ch]',\n      output: ['@BASENAME@.c','@BASENAME@.h'],\n      input: './dbus-menu.xml',\n      command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'],\n  )\n\n  client_protos_src += gdbus_code_dsnw[0]\n  client_protos_headers += gdbus_code_dsnw[1]\n  client_protos_src += gdbus_code_dsni[0]\n  client_protos_headers += gdbus_code_dsni[1]\n  client_protos_src += gdbus_code_dm[0]\n  client_protos_headers += gdbus_code_dm[1]\nelse\n  gdbus_code = generator(\n      gdbus_codegen,\n      output: '@BASENAME@.c',\n      arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@']\n  )\n\n  gdbus_header = generator(\n      gdbus_codegen,\n      output: '@BASENAME@.h',\n      arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@']\n  )\n\n  client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml')\n  client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml')\n\n  client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml')\n  client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml')\n\n  client_protos_src += gdbus_code.process('./dbus-menu.xml')\n  client_protos_headers += gdbus_header.process('./dbus-menu.xml')\nendif\n\n\nlib_client_protos = static_library(\n\t'client_protos',\n\tclient_protos_src + client_protos_headers,\n\tdependencies: [wayland_client, gtkmm, giounix],\n\tinclude_directories: include_directories('..'),\n) # for the include directory\n\nclient_protos = declare_dependency(\n\tlink_with: lib_client_protos,\n\tsources: client_protos_headers,\n)\n"
  },
  {
    "path": "protocol/river-control-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"river_control_unstable_v1\">\n  <copyright>\n    Copyright 2020 The River Developers\n\n    Permission to use, copy, modify, and/or distribute this software for any\n    purpose with or without fee is hereby granted, provided that the above\n    copyright notice and this permission notice appear in all copies.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n  </copyright>\n\n  <interface name=\"zriver_control_v1\" version=\"1\">\n    <description summary=\"run compositor commands\">\n      This interface allows clients to run compositor commands and receive a\n      success/failure response with output or a failure message respectively.\n\n      Each command is built up in a series of add_argument requests and\n      executed with a run_command request. The first argument is the command\n      to be run.\n\n      A complete list of commands should be made available in the man page of\n      the compositor.\n    </description>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the river_control object\">\n        This request indicates that the client will not use the\n        river_control object any more. Objects that have been created\n        through this instance are not affected.\n      </description>\n    </request>\n\n    <request name=\"add_argument\">\n      <description summary=\"add an argument to the current command\">\n        Arguments are stored by the server in the order they were sent until\n        the run_command request is made.\n      </description>\n      <arg name=\"argument\" type=\"string\" summary=\"the argument to add\"/>\n    </request>\n\n    <request name=\"run_command\">\n      <description summary=\"run the current command\">\n        Execute the command built up using the add_argument request for the\n        given seat.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n      <arg name=\"callback\" type=\"new_id\" interface=\"zriver_command_callback_v1\"\n        summary=\"callback object\"/>\n    </request>\n  </interface>\n\n  <interface name=\"zriver_command_callback_v1\" version=\"1\">\n    <description summary=\"callback object\">\n      This object is created by the run_command request. Exactly one of the\n      success or failure events will be sent. This object will be destroyed\n      by the compositor after one of the events is sent.\n    </description>\n\n    <event name=\"success\">\n      <description summary=\"command successful\">\n        Sent when the command has been successfully received and executed by\n        the compositor. Some commands may produce output, in which case the\n        output argument will be a non-empty string.\n      </description>\n      <arg name=\"output\" type=\"string\" summary=\"the output of the command\"/>\n    </event>\n\n    <event name=\"failure\">\n      <description summary=\"command failed\">\n        Sent when the command could not be carried out. This could be due to\n        sending a non-existent command, no command, not enough arguments, too\n        many arguments, invalid arguments, etc.\n      </description>\n      <arg name=\"failure_message\" type=\"string\"\n        summary=\"a message explaining why failure occurred\"/>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocol/river-status-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"river_status_unstable_v1\">\n  <copyright>\n    Copyright 2020 The River Developers\n\n    Permission to use, copy, modify, and/or distribute this software for any\n    purpose with or without fee is hereby granted, provided that the above\n    copyright notice and this permission notice appear in all copies.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n  </copyright>\n\n  <interface name=\"zriver_status_manager_v1\" version=\"4\">\n    <description summary=\"manage river status objects\">\n      A global factory for objects that receive status information specific\n      to river. It could be used to implement, for example, a status bar.\n    </description>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the river_status_manager object\">\n        This request indicates that the client will not use the\n        river_status_manager object any more. Objects that have been created\n        through this instance are not affected.\n      </description>\n    </request>\n\n    <request name=\"get_river_output_status\">\n      <description summary=\"create an output status object\">\n        This creates a new river_output_status object for the given wl_output.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zriver_output_status_v1\"/>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </request>\n\n    <request name=\"get_river_seat_status\">\n      <description summary=\"create a seat status object\">\n        This creates a new river_seat_status object for the given wl_seat.\n      </description>\n      <arg name=\"id\" type=\"new_id\" interface=\"zriver_seat_status_v1\"/>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n    </request>\n  </interface>\n\n  <interface name=\"zriver_output_status_v1\" version=\"4\">\n    <description summary=\"track output tags and focus\">\n      This interface allows clients to receive information about the current\n      windowing state of an output.\n    </description>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the river_output_status object\">\n        This request indicates that the client will not use the\n        river_output_status object any more.\n      </description>\n    </request>\n\n    <event name=\"focused_tags\">\n      <description summary=\"focused tags of the output\">\n        Sent once binding the interface and again whenever the tag focus of\n        the output changes.\n      </description>\n      <arg name=\"tags\" type=\"uint\" summary=\"32-bit bitfield\"/>\n    </event>\n\n    <event name=\"view_tags\">\n      <description summary=\"tag state of an output's views\">\n        Sent once on binding the interface and again whenever the tag state\n        of the output changes.\n      </description>\n      <arg name=\"tags\" type=\"array\" summary=\"array of 32-bit bitfields\"/>\n    </event>\n\n    <event name=\"urgent_tags\" since=\"2\">\n      <description summary=\"tags of the output with an urgent view\">\n        Sent once on binding the interface and again whenever the set of\n        tags with at least one urgent view changes.\n      </description>\n      <arg name=\"tags\" type=\"uint\" summary=\"32-bit bitfield\"/>\n    </event>\n\n    <event name=\"layout_name\" since=\"4\">\n      <description summary=\"name of the layout\">\n        Sent once on binding the interface should a layout name exist and again\n        whenever the name changes.\n      </description>\n      <arg name=\"name\" type=\"string\" summary=\"layout name\"/>\n    </event>\n\n    <event name=\"layout_name_clear\" since=\"4\">\n      <description summary=\"name of the layout\">\n        Sent when the current layout name has been removed without a new one\n        being set, for example when the active layout generator disconnects.\n      </description>\n    </event>\n  </interface>\n\n  <interface name=\"zriver_seat_status_v1\" version=\"3\">\n    <description summary=\"track seat focus\">\n      This interface allows clients to receive information about the current\n      focus of a seat. Note that (un)focused_output events will only be sent\n      if the client has bound the relevant wl_output globals.\n    </description>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the river_seat_status object\">\n        This request indicates that the client will not use the\n        river_seat_status object any more.\n      </description>\n    </request>\n\n    <event name=\"focused_output\">\n      <description summary=\"the seat focused an output\">\n        Sent on binding the interface and again whenever an output gains focus.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <event name=\"unfocused_output\">\n      <description summary=\"the seat unfocused an output\">\n        Sent whenever an output loses focus.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <event name=\"focused_view\">\n      <description summary=\"information on the focused view\">\n        Sent once on binding the interface and again whenever the focused\n        view or a property thereof changes. The title may be an empty string\n        if no view is focused or the focused view did not set a title.\n      </description>\n      <arg name=\"title\" type=\"string\" summary=\"title of the focused view\"/>\n    </event>\n\n    <event name=\"mode\" since=\"3\">\n      <description summary=\"the active mode changed\">\n        Sent once on binding the interface and again whenever a new mode\n        is entered (e.g. with riverctl enter-mode foobar).\n      </description>\n      <arg name=\"name\" type=\"string\" summary=\"name of the mode\"/>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "protocol/wlr-foreign-toplevel-management-unstable-v1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<protocol name=\"wlr_foreign_toplevel_management_unstable_v1\">\n  <copyright>\n    Copyright © 2018 Ilia Bozhinov\n\n    Permission to use, copy, modify, distribute, and sell this\n    software and its documentation for any purpose is hereby granted\n    without fee, provided that the above copyright notice appear in\n    all copies and that both that copyright notice and this permission\n    notice appear in supporting documentation, and that the name of\n    the copyright holders not be used in advertising or publicity\n    pertaining to distribution of the software without specific,\n    written prior permission.  The copyright holders make no\n    representations about the suitability of this software for any\n    purpose.  It is provided \"as is\" without express or implied\n    warranty.\n\n    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS\n    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\n    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\n    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\n    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\n    THIS SOFTWARE.\n  </copyright>\n\n  <interface name=\"zwlr_foreign_toplevel_manager_v1\" version=\"3\">\n    <description summary=\"list and control opened apps\">\n      The purpose of this protocol is to enable the creation of taskbars\n      and docks by providing them with a list of opened applications and\n      letting them request certain actions on them, like maximizing, etc.\n\n      After a client binds the zwlr_foreign_toplevel_manager_v1, each opened\n      toplevel window will be sent via the toplevel event\n    </description>\n\n    <event name=\"toplevel\">\n      <description summary=\"a toplevel has been created\">\n        This event is emitted whenever a new toplevel window is created. It\n        is emitted for all toplevels, regardless of the app that has created\n        them.\n\n        All initial details of the toplevel(title, app_id, states, etc.) will\n        be sent immediately after this event via the corresponding events in\n        zwlr_foreign_toplevel_handle_v1.\n      </description>\n      <arg name=\"toplevel\" type=\"new_id\" interface=\"zwlr_foreign_toplevel_handle_v1\"/>\n    </event>\n\n    <request name=\"stop\">\n      <description summary=\"stop sending events\">\n        Indicates the client no longer wishes to receive events for new toplevels.\n        However the compositor may emit further toplevel_created events, until\n        the finished event is emitted.\n\n        The client must not send any more requests after this one.\n      </description>\n    </request>\n\n    <event name=\"finished\">\n      <description summary=\"the compositor has finished with the toplevel manager\">\n        This event indicates that the compositor is done sending events to the\n        zwlr_foreign_toplevel_manager_v1. The server will destroy the object\n        immediately after sending this request, so it will become invalid and\n        the client should free any resources associated with it.\n      </description>\n    </event>\n  </interface>\n\n  <interface name=\"zwlr_foreign_toplevel_handle_v1\" version=\"3\">\n    <description summary=\"an opened toplevel\">\n      A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel\n      window. Each app may have multiple opened toplevels.\n\n      Each toplevel has a list of outputs it is visible on, conveyed to the\n      client with the output_enter and output_leave events.\n    </description>\n\n    <event name=\"title\">\n      <description summary=\"title change\">\n        This event is emitted whenever the title of the toplevel changes.\n      </description>\n      <arg name=\"title\" type=\"string\"/>\n    </event>\n\n    <event name=\"app_id\">\n      <description summary=\"app-id change\">\n        This event is emitted whenever the app-id of the toplevel changes.\n      </description>\n      <arg name=\"app_id\" type=\"string\"/>\n    </event>\n\n    <event name=\"output_enter\">\n      <description summary=\"toplevel entered an output\">\n        This event is emitted whenever the toplevel becomes visible on\n        the given output. A toplevel may be visible on multiple outputs.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <event name=\"output_leave\">\n      <description summary=\"toplevel left an output\">\n        This event is emitted whenever the toplevel stops being visible on\n        the given output. It is guaranteed that an entered-output event\n        with the same output has been emitted before this event.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\"/>\n    </event>\n\n    <request name=\"set_maximized\">\n      <description summary=\"requests that the toplevel be maximized\">\n        Requests that the toplevel be maximized. If the maximized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"unset_maximized\">\n      <description summary=\"requests that the toplevel be unmaximized\">\n        Requests that the toplevel be unmaximized. If the maximized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"set_minimized\">\n      <description summary=\"requests that the toplevel be minimized\">\n        Requests that the toplevel be minimized. If the minimized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"unset_minimized\">\n      <description summary=\"requests that the toplevel be unminimized\">\n        Requests that the toplevel be unminimized. If the minimized state actually\n        changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <request name=\"activate\">\n      <description summary=\"activate the toplevel\">\n        Request that this toplevel be activated on the given seat.\n        There is no guarantee the toplevel will be actually activated.\n      </description>\n      <arg name=\"seat\" type=\"object\" interface=\"wl_seat\"/>\n    </request>\n\n    <enum name=\"state\">\n      <description summary=\"types of states on the toplevel\">\n        The different states that a toplevel can have. These have the same meaning\n        as the states with the same names defined in xdg-toplevel\n      </description>\n\n      <entry name=\"maximized\"  value=\"0\" summary=\"the toplevel is maximized\"/>\n      <entry name=\"minimized\"  value=\"1\" summary=\"the toplevel is minimized\"/>\n      <entry name=\"activated\"  value=\"2\" summary=\"the toplevel is active\"/>\n      <entry name=\"fullscreen\" value=\"3\" summary=\"the toplevel is fullscreen\" since=\"2\"/>\n    </enum>\n\n    <event name=\"state\">\n      <description summary=\"the toplevel state changed\">\n        This event is emitted immediately after the zlw_foreign_toplevel_handle_v1\n        is created and each time the toplevel state changes, either because of a\n        compositor action or because of a request in this protocol.\n      </description>\n\n      <arg name=\"state\" type=\"array\"/>\n    </event>\n\n    <event name=\"done\">\n      <description summary=\"all information about the toplevel has been sent\">\n        This event is sent after all changes in the toplevel state have been\n        sent.\n\n        This allows changes to the zwlr_foreign_toplevel_handle_v1 properties\n        to be seen as atomic, even if they happen via multiple events.\n      </description>\n    </event>\n\n    <request name=\"close\">\n      <description summary=\"request that the toplevel be closed\">\n        Send a request to the toplevel to close itself. The compositor would\n        typically use a shell-specific method to carry out this request, for\n        example by sending the xdg_toplevel.close event. However, this gives\n        no guarantees the toplevel will actually be destroyed. If and when\n        this happens, the zwlr_foreign_toplevel_handle_v1.closed event will\n        be emitted.\n      </description>\n    </request>\n\n    <request name=\"set_rectangle\">\n      <description summary=\"the rectangle which represents the toplevel\">\n        The rectangle of the surface specified in this request corresponds to\n        the place where the app using this protocol represents the given toplevel.\n        It can be used by the compositor as a hint for some operations, e.g\n        minimizing. The client is however not required to set this, in which\n        case the compositor is free to decide some default value.\n\n        If the client specifies more than one rectangle, only the last one is\n        considered.\n\n        The dimensions are given in surface-local coordinates.\n        Setting width=height=0 removes the already-set rectangle.\n      </description>\n\n      <arg name=\"surface\" type=\"object\" interface=\"wl_surface\"/>\n      <arg name=\"x\" type=\"int\"/>\n      <arg name=\"y\" type=\"int\"/>\n      <arg name=\"width\" type=\"int\"/>\n      <arg name=\"height\" type=\"int\"/>\n    </request>\n\n    <enum name=\"error\">\n      <entry name=\"invalid_rectangle\" value=\"0\"\n        summary=\"the provided rectangle is invalid\"/>\n    </enum>\n\n    <event name=\"closed\">\n      <description summary=\"this toplevel has been destroyed\">\n        This event means the toplevel has been destroyed. It is guaranteed there\n        won't be any more events for this zwlr_foreign_toplevel_handle_v1. The\n        toplevel itself becomes inert so any requests will be ignored except the\n        destroy request.\n      </description>\n    </event>\n\n    <request name=\"destroy\" type=\"destructor\">\n      <description summary=\"destroy the zwlr_foreign_toplevel_handle_v1 object\">\n        Destroys the zwlr_foreign_toplevel_handle_v1 object.\n\n        This request should be called either when the client does not want to\n        use the toplevel anymore or after the closed event to finalize the\n        destruction of the object.\n      </description>\n    </request>\n\n    <!-- Version 2 additions -->\n\n    <request name=\"set_fullscreen\" since=\"2\">\n      <description summary=\"request that the toplevel be fullscreened\">\n        Requests that the toplevel be fullscreened on the given output. If the\n        fullscreen state and/or the outputs the toplevel is visible on actually\n        change, this will be indicated by the state and output_enter/leave\n        events.\n\n        The output parameter is only a hint to the compositor. Also, if output\n        is NULL, the compositor should decide which output the toplevel will be\n        fullscreened on, if at all.\n      </description>\n      <arg name=\"output\" type=\"object\" interface=\"wl_output\" allow-null=\"true\"/>\n    </request>\n\n    <request name=\"unset_fullscreen\" since=\"2\">\n      <description summary=\"request that the toplevel be unfullscreened\">\n        Requests that the toplevel be unfullscreened. If the fullscreen state\n        actually changes, this will be indicated by the state event.\n      </description>\n    </request>\n\n    <!-- Version 3 additions -->\n\n    <event name=\"parent\" since=\"3\">\n      <description summary=\"parent change\">\n        This event is emitted whenever the parent of the toplevel changes.\n\n        No event is emitted when the parent handle is destroyed by the client.\n      </description>\n      <arg name=\"parent\" type=\"object\" interface=\"zwlr_foreign_toplevel_handle_v1\" allow-null=\"true\"/>\n    </event>\n  </interface>\n</protocol>\n"
  },
  {
    "path": "resources/config.jsonc",
    "content": "// -*- mode: jsonc -*-\n{\n    // \"layer\": \"top\", // Waybar at top layer\n    // \"position\": \"bottom\", // Waybar position (top|bottom|left|right)\n    \"height\": 30, // Waybar height (to be removed for auto height)\n    // \"width\": 1280, // Waybar width\n    \"spacing\": 4, // Gaps between modules (4px)\n    // Choose the order of the modules\n    \"modules-left\": [\n        \"sway/workspaces\",\n        \"sway/mode\",\n        \"sway/scratchpad\",\n        \"custom/media\"\n    ],\n    \"modules-center\": [\n        \"sway/window\"\n    ],\n    \"modules-right\": [\n        \"mpd\",\n        \"idle_inhibitor\",\n        \"pulseaudio\",\n        \"network\",\n        \"power-profiles-daemon\",\n        \"cpu\",\n        \"memory\",\n        \"temperature\",\n        \"backlight\",\n        \"keyboard-state\",\n        \"sway/language\",\n        \"battery\",\n        \"battery#bat2\",\n        \"clock\",\n        \"tray\",\n        \"custom/power\"\n    ],\n    // Modules configuration\n    // \"sway/workspaces\": {\n    //     \"disable-scroll\": true,\n    //     \"all-outputs\": true,\n    //     \"warp-on-scroll\": false,\n    //     \"format\": \"{name}: {icon}\",\n    //     \"format-icons\": {\n    //         \"1\": \"\",\n    //         \"2\": \"\",\n    //         \"3\": \"\",\n    //         \"4\": \"\",\n    //         \"5\": \"\",\n    //         \"urgent\": \"\",\n    //         \"focused\": \"\",\n    //         \"default\": \"\"\n    //     }\n    // },\n    \"keyboard-state\": {\n        \"numlock\": true,\n        \"capslock\": true,\n        \"format\": \"{name} {icon}\",\n        \"format-icons\": {\n            \"locked\": \"\",\n            \"unlocked\": \"\"\n        }\n    },\n    \"sway/mode\": {\n        \"format\": \"<span style=\\\"italic\\\">{}</span>\"\n    },\n    \"sway/scratchpad\": {\n        \"format\": \"{icon} {count}\",\n        \"show-empty\": false,\n        \"format-icons\": [\"\", \"\"],\n        \"tooltip\": true,\n        \"tooltip-format\": \"{app}: {title}\"\n    },\n    \"mpd\": {\n        \"format\": \"{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% \",\n        \"format-disconnected\": \"Disconnected \",\n        \"format-stopped\": \"{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped \",\n        \"unknown-tag\": \"N/A\",\n        \"interval\": 5,\n        \"consume-icons\": {\n            \"on\": \" \"\n        },\n        \"random-icons\": {\n            \"off\": \"<span color=\\\"#f53c3c\\\"></span> \",\n            \"on\": \" \"\n        },\n        \"repeat-icons\": {\n            \"on\": \" \"\n        },\n        \"single-icons\": {\n            \"on\": \"1 \"\n        },\n        \"state-icons\": {\n            \"paused\": \"\",\n            \"playing\": \"\"\n        },\n        \"tooltip-format\": \"MPD (connected)\",\n        \"tooltip-format-disconnected\": \"MPD (disconnected)\"\n    },\n    \"idle_inhibitor\": {\n        \"format\": \"{icon}\",\n        \"format-icons\": {\n            \"activated\": \"\",\n            \"deactivated\": \"\"\n        }\n    },\n    \"tray\": {\n        // \"icon-size\": 21,\n        \"spacing\": 10,\n        // \"icons\": {\n        //   \"blueman\": \"bluetooth\",\n        //   \"TelegramDesktop\": \"$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png\"\n        // }\n    },\n    \"clock\": {\n        // \"timezone\": \"America/New_York\",\n        \"tooltip-format\": \"<big>{:%Y %B}</big>\\n<tt><small>{calendar}</small></tt>\",\n        \"format-alt\": \"{:%Y-%m-%d}\"\n    },\n    \"cpu\": {\n        \"format\": \"{usage}% \",\n        \"tooltip\": false\n    },\n    \"memory\": {\n        \"format\": \"{}% \"\n    },\n    \"temperature\": {\n        // \"thermal-zone\": 2,\n        // \"hwmon-path\": \"/sys/class/hwmon/hwmon2/temp1_input\",\n        \"critical-threshold\": 80,\n        // \"format-critical\": \"{temperatureC}°C {icon}\",\n        \"format\": \"{temperatureC}°C {icon}\",\n        \"format-icons\": [\"󰉬\", \"\", \"󰉪\"]\n    },\n    \"backlight\": {\n        // \"device\": \"acpi_video1\",\n        \"format\": \"{percent}% {icon}\",\n        \"format-icons\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"]\n    },\n    \"battery\": {\n        \"states\": {\n            // \"good\": 95,\n            \"warning\": 30,\n            \"critical\": 15\n        },\n        \"format\": \"{capacity}% {icon}\",\n        \"format-full\": \"{capacity}% {icon}\",\n        \"format-charging\": \"{capacity}% 󰃨\",\n        \"format-plugged\": \"{capacity}% \",\n        \"format-alt\": \"{time} {icon}\",\n        // \"format-good\": \"\", // An empty format will hide the module\n        // \"format-full\": \"\",\n        \"format-icons\": [\"\", \"\", \"\", \"\", \"\"]\n    },\n    \"battery#bat2\": {\n        \"bat\": \"BAT2\"\n    },\n    \"power-profiles-daemon\": {\n      \"format\": \"{icon}\",\n      \"tooltip-format\": \"Power profile: {profile}\\nDriver: {driver}\",\n      \"tooltip\": true,\n      \"format-icons\": {\n        \"default\": \"\",\n        \"performance\": \"\",\n        \"balanced\": \"\",\n        \"power-saver\": \"\"\n      }\n    },\n    \"network\": {\n        // \"interface\": \"wlp2*\", // (Optional) To force the use of this interface\n        \"format-wifi\": \"{essid} ({signalStrength}%) \",\n        \"format-ethernet\": \"{ipaddr}/{cidr} 󰊗\",\n        \"tooltip-format\": \"{ifname} via {gwaddr} 󰊗\",\n        \"format-linked\": \"{ifname} (No IP) 󰊗\",\n        \"format-disconnected\": \"Disconnected ⚠\",\n        \"format-alt\": \"{ifname}: {ipaddr}/{cidr}\"\n    },\n    \"pulseaudio\": {\n        // \"scroll-step\": 1, // %, can be a float\n        \"format\": \"{volume}% {icon} {format_source}\",\n        \"format-bluetooth\": \"{volume}% {icon} {format_source}\",\n        \"format-bluetooth-muted\": \"󰅶 {icon} {format_source}\",\n        \"format-muted\": \"󰅶 {format_source}\",\n        \"format-source\": \"{volume}% \",\n        \"format-source-muted\": \"\",\n        \"format-icons\": {\n            \"headphone\": \"\",\n            \"hands-free\": \"󰂑\",\n            \"headset\": \"󰂑\",\n            \"phone\": \"\",\n            \"portable\": \"\",\n            \"car\": \"\",\n            \"default\": [\"\", \"\", \"\"]\n        },\n        \"on-click\": \"pavucontrol\"\n    },\n    \"custom/media\": {\n        \"format\": \"{icon} {text}\",\n        \"return-type\": \"json\",\n        \"max-length\": 40,\n        \"format-icons\": {\n            \"spotify\": \"\",\n            \"default\": \"🎜\"\n        },\n        \"escape\": true,\n        \"exec\": \"$HOME/.config/waybar/mediaplayer.py 2> /dev/null\" // Script in resources folder\n        // \"exec\": \"$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null\" // Filter player based on name\n    },\n    \"custom/power\": {\n        \"format\" : \"⏻ \",\n\t\t\"tooltip\": false,\n\t\t\"menu\": \"on-click\",\n\t\t\"menu-file\": \"$HOME/.config/waybar/power_menu.xml\", // Menu file in resources folder\n\t\t\"menu-actions\": {\n\t\t\t\"shutdown\": \"shutdown\",\n\t\t\t\"reboot\": \"reboot\",\n\t\t\t\"suspend\": \"systemctl suspend\",\n\t\t\t\"hibernate\": \"systemctl hibernate\"\n\t\t}\n    }\n}\n"
  },
  {
    "path": "resources/custom_modules/cffi_example/.gitignore",
    "content": ".cache/"
  },
  {
    "path": "resources/custom_modules/cffi_example/README.md",
    "content": "# C FFI module\n\nA C FFI module is a dynamic library that exposes standard C functions and\nconstants, that Waybar can load and execute to create custom advanced widgets.\n\nMost language can implement the required functions and constants (C, C++, Rust,\nGo, Python, ...), meaning you can develop custom modules using your language of\nchoice, as long as there's GTK bindings.\n\nSymbols to implement are documented in the\n[waybar_cffi_module.h](waybar_cffi_module.h) file.\n\n# Usage\n\n## Building this module\n\n```bash\nmeson setup build\nmeson compile -C build\n```\n\n## Load the module\n\nEdit your waybar config:\n```json\n{\n\t// ...\n\t\"modules-center\": [\n\t\t// ...\n\t\t\"cffi/c_example\"\n\t],\n\t// ...\n\t\"cffi/c_example\": {\n\t\t// Path to the compiled dynamic library file\n\t\t\"module_path\": \"resources/custom_modules/cffi_example/build/wb_cffi_example.so\"\n\t}\n}\n```\n"
  },
  {
    "path": "resources/custom_modules/cffi_example/main.c",
    "content": "\n#include \"waybar_cffi_module.h\"\n\ntypedef struct {\n  wbcffi_module* waybar_module;\n  GtkBox* container;\n  GtkButton* button;\n} ExampleMod;\n\n// This static variable is shared between all instances of this module\nstatic int instance_count = 0;\n\nvoid onclicked(GtkButton* button) {\n  char text[256];\n  snprintf(text, 256, \"Dice throw result: %d\", rand() % 6 + 1);\n  gtk_button_set_label(button, text);\n}\n\n// You must\nconst size_t wbcffi_version = 2;\n\nvoid* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,\n                  size_t config_entries_len) {\n  printf(\"cffi_example: init config:\\n\");\n  for (size_t i = 0; i < config_entries_len; i++) {\n    printf(\"  %s = %s\\n\", config_entries[i].key, config_entries[i].value);\n  }\n\n  // Allocate the instance object\n  ExampleMod* inst = malloc(sizeof(ExampleMod));\n  inst->waybar_module = init_info->obj;\n\n  GtkContainer* root = init_info->get_root_widget(init_info->obj);\n\n  // Add a container for displaying the next widgets\n  inst->container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5));\n  gtk_container_add(GTK_CONTAINER(root), GTK_WIDGET(inst->container));\n\n  // Add a label\n  GtkLabel* label = GTK_LABEL(gtk_label_new(\"[Example C FFI Module:\"));\n  gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(label));\n\n  // Add a button\n  inst->button = GTK_BUTTON(gtk_button_new_with_label(\"click me !\"));\n  g_signal_connect(inst->button, \"clicked\", G_CALLBACK(onclicked), NULL);\n  gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(inst->button));\n\n  // Add a label\n  label = GTK_LABEL(gtk_label_new(\"]\"));\n  gtk_container_add(GTK_CONTAINER(inst->container), GTK_WIDGET(label));\n\n  // Return instance object\n  printf(\"cffi_example inst=%p: init success ! (%d total instances)\\n\", inst, ++instance_count);\n  return inst;\n}\n\nvoid wbcffi_deinit(void* instance) {\n  printf(\"cffi_example inst=%p: free memory\\n\", instance);\n  free(instance);\n}\n\nvoid wbcffi_update(void* instance) { printf(\"cffi_example inst=%p: Update request\\n\", instance); }\n\nvoid wbcffi_refresh(void* instance, int signal) {\n  printf(\"cffi_example inst=%p: Received refresh signal %d\\n\", instance, signal);\n}\n\nvoid wbcffi_doaction(void* instance, const char* name) {\n  printf(\"cffi_example inst=%p: doAction(%s)\\n\", instance, name);\n}\n"
  },
  {
    "path": "resources/custom_modules/cffi_example/meson.build",
    "content": "project(\n    'waybar_cffi_example', 'c',\n    version: '0.1.0',\n    license: 'MIT',\n)\n\nshared_library('wb_cffi_example',\n    ['main.c'],\n    dependencies: [\n        dependency('gtk+-3.0', version : ['>=3.22.0'])\n    ],\n    name_prefix: ''\n)\n"
  },
  {
    "path": "resources/custom_modules/cffi_example/waybar_cffi_module.h",
    "content": "#pragma once\n\n#include <gtk/gtk.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/// Waybar ABI version. 2 is the latest version\nextern const size_t wbcffi_version;\n\n/// Private Waybar CFFI module\ntypedef struct wbcffi_module wbcffi_module;\n\n/// Waybar module information\ntypedef struct {\n  /// Waybar CFFI object pointer\n  wbcffi_module* obj;\n\n  /// Waybar version string\n  const char* waybar_version;\n\n  /// Returns the waybar widget allocated for this module\n  /// @param obj Waybar CFFI object pointer\n  GtkContainer* (*get_root_widget)(wbcffi_module* obj);\n\n  /// Queues a request for calling wbcffi_update() on the next GTK main event\n  /// loop iteration\n  /// @param obj Waybar CFFI object pointer\n  void (*queue_update)(wbcffi_module*);\n} wbcffi_init_info;\n\n/// Config key-value pair\ntypedef struct {\n  /// Entry key\n  const char* key;\n  /// Entry value\n  ///\n  /// In ABI version 1, this may be either a bare string if the value is a\n  /// string, or the JSON representation of any other JSON object as a string.\n  ///\n  /// From ABI version 2 onwards, this is always the JSON representation of the\n  /// value as a string.\n  const char* value;\n} wbcffi_config_entry;\n\n/// Module init/new function, called on module instantiation\n///\n/// MANDATORY CFFI function\n///\n/// @param init_info          Waybar module information\n/// @param config_entries     Flat representation of the module JSON config. The data only available\n///                           during wbcffi_init call.\n/// @param config_entries_len Number of entries in `config_entries`\n///\n/// @return A untyped pointer to module data, NULL if the module failed to load.\nvoid* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries,\n                  size_t config_entries_len);\n\n/// Module deinit/delete function, called when Waybar is closed or when the module is removed\n///\n/// MANDATORY CFFI function\n///\n/// @param instance Module instance data (as returned by `wbcffi_init`)\nvoid wbcffi_deinit(void* instance);\n\n/// Called from the GTK main event loop, to update the UI\n///\n/// Optional CFFI function\n///\n/// @param instance Module instance data (as returned by `wbcffi_init`)\n/// @param action_name Action name\nvoid wbcffi_update(void* instance);\n\n/// Called when Waybar receives a POSIX signal and forwards it to each module\n///\n/// Optional CFFI function\n///\n/// @param instance Module instance data (as returned by `wbcffi_init`)\n/// @param signal Signal ID\nvoid wbcffi_refresh(void* instance, int signal);\n\n/// Called on module action (see\n/// https://github.com/Alexays/Waybar/wiki/Configuration#module-actions-config)\n///\n/// Optional CFFI function\n///\n/// @param instance Module instance data (as returned by `wbcffi_init`)\n/// @param action_name Action name\nvoid wbcffi_doaction(void* instance, const char* action_name);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "resources/custom_modules/mediaplayer.py",
    "content": "#!/usr/bin/env python3\nimport gi\ngi.require_version(\"Playerctl\", \"2.0\")\nfrom gi.repository import Playerctl, GLib\nfrom gi.repository.Playerctl import Player\nimport argparse\nimport logging\nimport sys\nimport signal\nimport gi\nimport json\nimport os\nfrom typing import List\n\nlogger = logging.getLogger(__name__)\n\ndef signal_handler(sig, frame):\n    logger.info(\"Received signal to stop, exiting\")\n    sys.stdout.write(\"\\n\")\n    sys.stdout.flush()\n    # loop.quit()\n    sys.exit(0)\n\n\nclass PlayerManager:\n    def __init__(self, selected_player=None, excluded_player=[]):\n        self.manager = Playerctl.PlayerManager()\n        self.loop = GLib.MainLoop()\n        self.manager.connect(\n            \"name-appeared\", lambda *args: self.on_player_appeared(*args))\n        self.manager.connect(\n            \"player-vanished\", lambda *args: self.on_player_vanished(*args))\n\n        signal.signal(signal.SIGINT, signal_handler)\n        signal.signal(signal.SIGTERM, signal_handler)\n        signal.signal(signal.SIGPIPE, signal.SIG_DFL)\n        self.selected_player = selected_player\n        self.excluded_player = excluded_player.split(',') if excluded_player else []\n\n        self.init_players()\n\n    def init_players(self):\n        for player in self.manager.props.player_names:\n            if player.name in self.excluded_player:\n                continue\n            if self.selected_player is not None and self.selected_player != player.name:\n                logger.debug(f\"{player.name} is not the filtered player, skipping it\")\n                continue\n            self.init_player(player)\n\n    def run(self):\n        logger.info(\"Starting main loop\")\n        self.loop.run()\n\n    def init_player(self, player):\n        logger.info(f\"Initialize new player: {player.name}\")\n        player = Playerctl.Player.new_from_name(player)\n        player.connect(\"playback-status\",\n                       self.on_playback_status_changed, None)\n        player.connect(\"metadata\", self.on_metadata_changed, None)\n        self.manager.manage_player(player)\n        self.on_metadata_changed(player, player.props.metadata)\n\n    def get_players(self) -> List[Player]:\n        return self.manager.props.players\n\n    def write_output(self, text, player):\n        logger.debug(f\"Writing output: {text}\")\n\n        output = {\"text\": text,\n                  \"class\": \"custom-\" + player.props.player_name,\n                  \"alt\": player.props.player_name}\n\n        sys.stdout.write(json.dumps(output) + \"\\n\")\n        sys.stdout.flush()\n\n    def clear_output(self):\n        sys.stdout.write(\"\\n\")\n        sys.stdout.flush()\n\n    def on_playback_status_changed(self, player, status, _=None):\n        logger.debug(f\"Playback status changed for player {player.props.player_name}: {status}\")\n        self.on_metadata_changed(player, player.props.metadata)\n\n    def get_first_playing_player(self):\n        players = self.get_players()\n        logger.debug(f\"Getting first playing player from {len(players)} players\")\n        if len(players) > 0:\n            # if any are playing, show the first one that is playing\n            # reverse order, so that the most recently added ones are preferred\n            for player in players[::-1]:\n                if player.props.status == \"Playing\":\n                    return player\n            # if none are playing, show the first one\n            return players[0]\n        else:\n            logger.debug(\"No players found\")\n            return None\n\n    def show_most_important_player(self):\n        logger.debug(\"Showing most important player\")\n        # show the currently playing player\n        # or else show the first paused player\n        # or else show nothing\n        current_player = self.get_first_playing_player()\n        if current_player is not None:\n            self.on_metadata_changed(current_player, current_player.props.metadata)\n        else:    \n            self.clear_output()\n\n    def on_metadata_changed(self, player, metadata, _=None):\n        logger.debug(f\"Metadata changed for player {player.props.player_name}\")\n        player_name = player.props.player_name\n        artist = player.get_artist()\n        artist = artist.replace(\"&\", \"&amp;\")\n        title = player.get_title()\n        title = title.replace(\"&\", \"&amp;\")\n\n        track_info = \"\"\n        if player_name == \"spotify\" and \"mpris:trackid\" in metadata.keys() and \":ad:\" in player.props.metadata[\"mpris:trackid\"]:\n            track_info = \"Advertisement\"\n        elif artist is not None and title is not None:\n            track_info = f\"{artist} - {title}\"\n        else:\n            track_info = title\n\n        if track_info:\n            if player.props.status == \"Playing\":\n                track_info = \" \" + track_info\n            else:\n                track_info = \" \" + track_info\n        # only print output if no other player is playing\n        current_playing = self.get_first_playing_player()\n        if current_playing is None or current_playing.props.player_name == player.props.player_name:\n            self.write_output(track_info, player)\n        else:\n            logger.debug(f\"Other player {current_playing.props.player_name} is playing, skipping\")\n\n    def on_player_appeared(self, _, player):\n        logger.info(f\"Player has appeared: {player.name}\")\n        if player.name in self.excluded_player:\n            logger.debug(\n                \"New player appeared, but it's in exclude player list, skipping\")\n            return\n        if player is not None and (self.selected_player is None or player.name == self.selected_player):\n            self.init_player(player)\n        else:\n            logger.debug(\n                \"New player appeared, but it's not the selected player, skipping\")\n\n    def on_player_vanished(self, _, player):\n        logger.info(f\"Player {player.props.player_name} has vanished\")\n        self.show_most_important_player()\n\ndef parse_arguments():\n    parser = argparse.ArgumentParser()\n\n    # Increase verbosity with every occurrence of -v\n    parser.add_argument(\"-v\", \"--verbose\", action=\"count\", default=0)\n\n    parser.add_argument(\"-x\", \"--exclude\", \"- Comma-separated list of excluded player\")\n\n    # Define for which player we\"re listening\n    parser.add_argument(\"--player\")\n\n    parser.add_argument(\"--enable-logging\", action=\"store_true\")\n\n    return parser.parse_args()\n\n\ndef main():\n    arguments = parse_arguments()\n\n    # Initialize logging\n    if arguments.enable_logging:\n        logfile = os.path.join(os.path.dirname(\n            os.path.realpath(__file__)), \"media-player.log\")\n        logging.basicConfig(filename=logfile, level=logging.DEBUG,\n                            format=\"%(asctime)s %(name)s %(levelname)s:%(lineno)d %(message)s\")\n\n    # Logging is set by default to WARN and higher.\n    # With every occurrence of -v it's lowered by one\n    logger.setLevel(max((3 - arguments.verbose) * 10, 0))\n\n    logger.info(\"Creating player manager\")\n    if arguments.player:\n        logger.info(f\"Filtering for player: {arguments.player}\")\n    if arguments.exclude:\n        logger.info(f\"Exclude player {arguments.exclude}\")\n\n    player = PlayerManager(arguments.player, arguments.exclude)\n    player.run()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "resources/custom_modules/power_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<interface>\n  <object class=\"GtkMenu\" id=\"menu\">\n    <child>\n\t\t<object class=\"GtkMenuItem\" id=\"suspend\">\n\t\t\t<property name=\"label\">Suspend</property>\n        </object>\n\t</child>\n\t<child>\n        <object class=\"GtkMenuItem\" id=\"hibernate\">\n\t\t\t<property name=\"label\">Hibernate</property>\n        </object>\n\t</child>\n    <child>\n        <object class=\"GtkMenuItem\" id=\"shutdown\">\n\t\t\t<property name=\"label\">Shutdown</property>\n        </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"delimiter1\"/>\n    </child>\n    <child>\n\t\t<object class=\"GtkMenuItem\" id=\"reboot\">\n\t\t\t<property name=\"label\">Reboot</property>\n\t\t</object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "resources/icons/meson.build",
    "content": "gnome = import('gnome')\n\napp_resources += gnome.compile_resources('icon-resources',\n  'waybar_icons.gresource.xml',\n  c_name: 'waybar_icons'\n)\n"
  },
  {
    "path": "resources/icons/waybar_icons.gresource.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gresources>\n  <gresource prefix=\"/fr/arouillard/waybar/icons/scalable/actions\">\n    <file preprocess=\"xml-stripblanks\">waybar-privacy-audio-input-symbolic.svg</file>\n    <file preprocess=\"xml-stripblanks\">waybar-privacy-audio-output-symbolic.svg</file>\n    <file preprocess=\"xml-stripblanks\">waybar-privacy-screen-share-symbolic.svg</file>\n  </gresource>\n</gresources>\n"
  },
  {
    "path": "resources/style.css",
    "content": "* {\n    /* `otf-font-awesome` is required to be installed for icons */\n    font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;\n    font-size: 13px;\n}\n\nwindow#waybar {\n    background-color: rgba(43, 48, 59, 0.5);\n    border-bottom: 3px solid rgba(100, 114, 125, 0.5);\n    color: #ffffff;\n    transition-property: background-color;\n    transition-duration: .5s;\n}\n\nwindow#waybar.hidden {\n    opacity: 0.2;\n}\n\n/*\nwindow#waybar.empty {\n    background-color: transparent;\n}\nwindow#waybar.solo {\n    background-color: #FFFFFF;\n}\n*/\n\nwindow#waybar.termite {\n    background-color: #3F3F3F;\n}\n\nwindow#waybar.chromium {\n    background-color: #000000;\n    border: none;\n}\n\nbutton {\n    /* Use box-shadow instead of border so the text isn't offset */\n    box-shadow: inset 0 -3px transparent;\n    /* Avoid rounded borders under each button name */\n    border: none;\n    border-radius: 0;\n}\n\n/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */\nbutton:hover {\n    background: inherit;\n    box-shadow: inset 0 -3px #ffffff;\n}\n\n/* you can set a style on hover for any module like this */\n#pulseaudio:hover {\n    background-color: #a37800;\n}\n\n#workspaces button {\n    padding: 0 5px;\n    background-color: transparent;\n    color: #ffffff;\n}\n\n#workspaces button:hover {\n    background: rgba(0, 0, 0, 0.2);\n}\n\n#workspaces button.focused, #workspaces button.active {\n    background-color: #64727D;\n    box-shadow: inset 0 -3px #ffffff;\n}\n\n#workspaces button.urgent {\n    background-color: #eb4d4b;\n}\n\n#mode {\n    background-color: #64727D;\n    box-shadow: inset 0 -3px #ffffff;\n}\n\n#clock,\n#battery,\n#cpu,\n#memory,\n#disk,\n#temperature,\n#backlight,\n#network,\n#pulseaudio,\n#wireplumber,\n#custom-media,\n#tray,\n#mode,\n#idle_inhibitor,\n#scratchpad,\n#power-profiles-daemon,\n#mpd {\n    padding: 0 10px;\n    color: #ffffff;\n}\n\n#window,\n#workspaces {\n    margin: 0 4px;\n}\n\n/* If workspaces is the leftmost module, omit left margin */\n.modules-left > widget:first-child > #workspaces {\n    margin-left: 0;\n}\n\n/* If workspaces is the rightmost module, omit right margin */\n.modules-right > widget:last-child > #workspaces {\n    margin-right: 0;\n}\n\n#clock {\n    background-color: #64727D;\n}\n\n#battery {\n    background-color: #ffffff;\n    color: #000000;\n}\n\n#battery.charging, #battery.plugged {\n    color: #ffffff;\n    background-color: #26A65B;\n}\n\n@keyframes blink {\n    to {\n        background-color: #ffffff;\n        color: #000000;\n    }\n}\n\n/* Using steps() instead of linear as a timing function to limit cpu usage */\n#battery.critical:not(.charging) {\n    background-color: #f53c3c;\n    color: #ffffff;\n    animation-name: blink;\n    animation-duration: 0.5s;\n    animation-timing-function: steps(12);\n    animation-iteration-count: infinite;\n    animation-direction: alternate;\n}\n\n#power-profiles-daemon {\n    padding-right: 15px;\n}\n\n#power-profiles-daemon.performance {\n    background-color: #f53c3c;\n    color: #ffffff;\n}\n\n#power-profiles-daemon.balanced {\n    background-color: #2980b9;\n    color: #ffffff;\n}\n\n#power-profiles-daemon.power-saver {\n    background-color: #2ecc71;\n    color: #000000;\n}\n\nlabel:focus {\n    background-color: #000000;\n}\n\n#cpu {\n    background-color: #2ecc71;\n    color: #000000;\n}\n\n#memory {\n    background-color: #9b59b6;\n}\n\n#disk {\n    background-color: #964B00;\n}\n\n#backlight {\n    background-color: #90b1b1;\n}\n\n#network {\n    background-color: #2980b9;\n}\n\n#network.disconnected {\n    background-color: #f53c3c;\n}\n\n#pulseaudio {\n    background-color: #f1c40f;\n    color: #000000;\n}\n\n#pulseaudio.muted {\n    background-color: #90b1b1;\n    color: #2a5c45;\n}\n\n#wireplumber {\n    background-color: #fff0f5;\n    color: #000000;\n}\n\n#wireplumber.muted {\n    background-color: #f53c3c;\n}\n\n#custom-media {\n    background-color: #66cc99;\n    color: #2a5c45;\n    min-width: 100px;\n}\n\n#custom-media.custom-spotify {\n    background-color: #66cc99;\n}\n\n#custom-media.custom-vlc {\n    background-color: #ffa000;\n}\n\n#temperature {\n    background-color: #f0932b;\n}\n\n#temperature.critical {\n    background-color: #eb4d4b;\n}\n\n#tray {\n    background-color: #2980b9;\n}\n\n#tray > .passive {\n    -gtk-icon-effect: dim;\n}\n\n#tray > .needs-attention {\n    -gtk-icon-effect: highlight;\n    background-color: #eb4d4b;\n}\n\n#idle_inhibitor {\n    background-color: #2d3436;\n}\n\n#idle_inhibitor.activated {\n    background-color: #ecf0f1;\n    color: #2d3436;\n}\n\n#mpd {\n    background-color: #66cc99;\n    color: #2a5c45;\n}\n\n#mpd.disconnected {\n    background-color: #f53c3c;\n}\n\n#mpd.stopped {\n    background-color: #90b1b1;\n}\n\n#mpd.paused {\n    background-color: #51a37a;\n}\n\n#language {\n    background: #00b093;\n    color: #740864;\n    padding: 0 5px;\n    margin: 0 5px;\n    min-width: 16px;\n}\n\n#keyboard-state {\n    background: #97e1ad;\n    color: #000000;\n    padding: 0 0px;\n    margin: 0 5px;\n    min-width: 16px;\n}\n\n#keyboard-state > label {\n    padding: 0 5px;\n}\n\n#keyboard-state > label.locked {\n    background: rgba(0, 0, 0, 0.2);\n}\n\n#scratchpad {\n    background: rgba(0, 0, 0, 0.2);\n}\n\n#scratchpad.empty {\n\tbackground-color: transparent;\n}\n\n#privacy {\n    padding: 0;\n}\n\n#privacy-item {\n    padding: 0 5px;\n    color: white;\n}\n\n#privacy-item.screenshare {\n    background-color: #cf5700;\n}\n\n#privacy-item.audio-in {\n    background-color: #1ca000;\n}\n\n#privacy-item.audio-out {\n    background-color: #0069d4;\n}\n"
  },
  {
    "path": "resources/waybar.service.in",
    "content": "[Unit]\nDescription=Highly customizable Wayland bar for Sway and Wlroots based compositors\nDocumentation=https://github.com/Alexays/Waybar/wiki/\nPartOf=graphical-session.target\nAfter=graphical-session.target\nRequisite=graphical-session.target\n\n[Service]\nExecStart=@prefix@/bin/waybar\nExecReload=kill -SIGUSR2 $MAINPID\nRestart=on-failure\n\n[Install]\nWantedBy=graphical-session.target\n"
  },
  {
    "path": "src/AAppIconLabel.cpp",
    "content": "#include \"AAppIconLabel.hpp\"\n\n#include <gdkmm/pixbuf.h>\n#include <glibmm/fileutils.h>\n#include <glibmm/keyfile.h>\n#include <glibmm/miscutils.h>\n#include <spdlog/spdlog.h>\n\n#include <filesystem>\n#include <optional>\n\n#include \"util/gtk_icon.hpp\"\n\nnamespace waybar {\n\nAAppIconLabel::AAppIconLabel(const Json::Value& config, const std::string& name,\n                             const std::string& id, const std::string& format, uint16_t interval,\n                             bool ellipsize, bool enable_click, bool enable_scroll)\n    : AIconLabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {\n  // Icon size\n  if (config[\"icon-size\"].isUInt()) {\n    app_icon_size_ = config[\"icon-size\"].asUInt();\n  }\n  image_.set_pixel_size(app_icon_size_);\n}\n\nstd::string toLowerCase(const std::string& input) {\n  std::string result = input;\n  std::transform(result.begin(), result.end(), result.begin(),\n                 [](unsigned char c) { return std::tolower(c); });\n  return result;\n}\n\nstd::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix,\n                                           bool check_lower_case) {\n  if (!std::filesystem::exists(dir)) {\n    return {};\n  }\n  for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {\n    if (entry.is_regular_file()) {\n      std::string filename = entry.path().filename().string();\n      if (filename.size() < suffix.size()) {\n        continue;\n      }\n      if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) ||\n          (check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(),\n                                                toLowerCase(suffix)) == 0)) {\n        return entry.path().string();\n      }\n    }\n  }\n\n  return {};\n}\n\nstd::optional<std::string> getFileBySuffix(const std::string& dir, const std::string& suffix) {\n  return getFileBySuffix(dir, suffix, false);\n}\n\nstd::optional<std::string> getDesktopFilePath(const std::string& app_identifier,\n                                              const std::string& alternative_app_identifier) {\n  if (app_identifier.empty()) {\n    return {};\n  }\n\n  auto data_dirs = Glib::get_system_data_dirs();\n  data_dirs.insert(data_dirs.begin(), Glib::get_user_data_dir());\n  for (const auto& data_dir : data_dirs) {\n    const auto data_app_dir = data_dir + \"/applications/\";\n    auto desktop_file_suffix = app_identifier + \".desktop\";\n    // searching for file by suffix catches cases like terminal emulator \"foot\" where class is\n    // \"footclient\" and desktop file is named \"org.codeberg.dnkl.footclient.desktop\"\n    auto desktop_file_path = getFileBySuffix(data_app_dir, desktop_file_suffix, true);\n    // \"true\" argument allows checking for lowercase - this catches cases where class name is\n    // \"LibreWolf\" and desktop file is named \"librewolf.desktop\"\n    if (desktop_file_path.has_value()) {\n      return desktop_file_path;\n    }\n    if (!alternative_app_identifier.empty()) {\n      desktop_file_suffix = alternative_app_identifier + \".desktop\";\n      desktop_file_path = getFileBySuffix(data_app_dir, desktop_file_suffix, true);\n      if (desktop_file_path.has_value()) {\n        return desktop_file_path;\n      }\n    }\n  }\n  return {};\n}\n\nstd::optional<Glib::ustring> getIconName(const std::string& app_identifier,\n                                         const std::string& alternative_app_identifier) {\n  const auto desktop_file_path = getDesktopFilePath(app_identifier, alternative_app_identifier);\n  if (!desktop_file_path.has_value()) {\n    // Try some heuristics to find a matching icon\n\n    if (DefaultGtkIconThemeWrapper::has_icon(app_identifier)) {\n      return app_identifier;\n    }\n\n    auto app_identifier_desktop = app_identifier + \"-desktop\";\n    if (DefaultGtkIconThemeWrapper::has_icon(app_identifier_desktop)) {\n      return app_identifier_desktop;\n    }\n\n    auto first_space = app_identifier.find_first_of(' ');\n    if (first_space != std::string::npos) {\n      auto first_word = toLowerCase(app_identifier.substr(0, first_space));\n      if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {\n        return first_word;\n      }\n    }\n\n    const auto first_dash = app_identifier.find_first_of('-');\n    if (first_dash != std::string::npos) {\n      auto first_word = toLowerCase(app_identifier.substr(0, first_dash));\n      if (DefaultGtkIconThemeWrapper::has_icon(first_word)) {\n        return first_word;\n      }\n    }\n\n    return {};\n  }\n\n  try {\n    Glib::KeyFile desktop_file;\n    desktop_file.load_from_file(desktop_file_path.value());\n    return desktop_file.get_string(\"Desktop Entry\", \"Icon\");\n  } catch (Glib::FileError& error) {\n    spdlog::warn(\"Error while loading desktop file {}: {}\", desktop_file_path.value(),\n                 error.what().c_str());\n  } catch (Glib::KeyFileError& error) {\n    spdlog::warn(\"Error while loading desktop file {}: {}\", desktop_file_path.value(),\n                 error.what().c_str());\n  }\n  return {};\n}\n\nvoid AAppIconLabel::updateAppIconName(const std::string& app_identifier,\n                                      const std::string& alternative_app_identifier) {\n  if (!iconEnabled()) {\n    return;\n  }\n\n  const auto icon_name = getIconName(app_identifier, alternative_app_identifier);\n  if (icon_name.has_value()) {\n    app_icon_name_ = icon_name.value();\n  } else {\n    app_icon_name_ = \"\";\n  }\n  update_app_icon_ = true;\n}\n\nvoid AAppIconLabel::updateAppIcon() {\n  if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) {\n    update_app_icon_ = false;\n    if (app_icon_name_.empty()) {\n      image_.set_visible(false);\n    } else if (app_icon_name_.front() == '/') {\n      try {\n        int scaled_icon_size = app_icon_size_ * image_.get_scale_factor();\n        auto pixbuf =\n            Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size);\n\n        auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),\n                                                              image_.get_window());\n        image_.set(surface);\n        image_.set_visible(true);\n      } catch (const Glib::Exception& e) {\n        spdlog::warn(\"Failed to load app icon {}: {}\", app_icon_name_, std::string(e.what()));\n        image_.set_visible(false);\n      }\n    } else {\n      image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);\n      image_.set_visible(true);\n    }\n  }\n}\n\nauto AAppIconLabel::update() -> void {\n  updateAppIcon();\n  AIconLabel::update();\n}\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/AIconLabel.cpp",
    "content": "#include \"AIconLabel.hpp\"\n\n#include <gdkmm/pixbuf.h>\n#include <spdlog/spdlog.h>\n\nnamespace waybar {\n\nAIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const std::string& id,\n                       const std::string& format, uint16_t interval, bool ellipsize,\n                       bool enable_click, bool enable_scroll)\n    : ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {\n  event_box_.remove();\n  label_.unset_name();\n  label_.get_style_context()->remove_class(MODULE_CLASS);\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  if (!id.empty()) {\n    label_.get_style_context()->remove_class(id);\n    box_.get_style_context()->add_class(id);\n  }\n\n  int rot = 0;\n\n  if (config_[\"rotate\"].isUInt()) {\n    rot = config[\"rotate\"].asUInt() % 360;\n    if ((rot % 90) != 00) rot = 0;\n    rot /= 90;\n  }\n\n  if ((rot % 2) == 0)\n    box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);\n  else\n    box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);\n  box_.set_name(name);\n\n  int spacing = config_[\"icon-spacing\"].isInt() ? config_[\"icon-spacing\"].asInt() : 8;\n  box_.set_spacing(spacing);\n\n  bool swap_icon_label = false;\n  if (config_[\"swap-icon-label\"].isNull()) {\n  } else if (config_[\"swap-icon-label\"].isBool()) {\n    swap_icon_label = config_[\"swap-icon-label\"].asBool();\n  } else {\n    spdlog::warn(\"'swap-icon-label' must be a bool, found '{}'. Using default value (false).\",\n                 config_[\"swap-icon-label\"].asString());\n  }\n\n  if ((rot == 0 || rot == 3) ^ swap_icon_label) {\n    box_.add(image_);\n    box_.add(label_);\n  } else {\n    box_.add(label_);\n    box_.add(image_);\n  }\n\n  event_box_.add(box_);\n}\n\nauto AIconLabel::update() -> void {\n  image_.set_visible(image_.get_visible() && iconEnabled());\n  ALabel::update();\n}\n\nbool AIconLabel::iconEnabled() const {\n  return config_[\"icon\"].isBool() ? config_[\"icon\"].asBool() : false;\n}\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/ALabel.cpp",
    "content": "#include \"ALabel.hpp\"\n\n#include <fmt/format.h>\n\n#include <fstream>\n#include <iostream>\n#include <util/command.hpp>\n\n#include \"config.hpp\"\n\nnamespace waybar {\n\nALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,\n               const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,\n               bool enable_scroll)\n    : AModule(config, name, id,\n              config[\"format-alt\"].isString() || config[\"menu\"].isString() || enable_click,\n              enable_scroll),\n      format_(config_[\"format\"].isString() ? config_[\"format\"].asString() : format),\n\n      // Leave the default option outside of the std::max(1L, ...), because the zero value\n      // (default) is used in modules/custom.cpp to make the difference between\n      // two types of custom scripts. Fixes #4521.\n      interval_(config_[\"interval\"] == \"once\"\n                    ? std::chrono::milliseconds::max()\n                    : std::chrono::milliseconds(\n                          (config_[\"interval\"].isNumeric()\n                               ? std::max(1L,  // Minimum 1ms due to millisecond precision\n                                          static_cast<long>(config_[\"interval\"].asDouble() * 1000))\n                               : 1000 * (long)interval))),\n      default_format_(format_) {\n  label_.set_name(name);\n  if (!id.empty()) {\n    label_.get_style_context()->add_class(id);\n  }\n  label_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(label_);\n  if (config_[\"max-length\"].isUInt()) {\n    label_.set_max_width_chars(config_[\"max-length\"].asInt());\n    label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);\n    label_.set_single_line_mode(true);\n  } else if (ellipsize && label_.get_max_width_chars() == -1) {\n    label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);\n    label_.set_single_line_mode(true);\n  }\n\n  if (config_[\"min-length\"].isUInt()) {\n    label_.set_width_chars(config_[\"min-length\"].asUInt());\n  }\n\n  uint rotate = 0;\n\n  if (config_[\"rotate\"].isUInt()) {\n    rotate = config[\"rotate\"].asUInt();\n    if (not(rotate == 0 || rotate == 90 || rotate == 180 || rotate == 270))\n      spdlog::warn(\"'rotate' is only supported in 90 degree increments {} is not valid.\", rotate);\n    label_.set_angle(rotate);\n  }\n\n  if (config_[\"align\"].isDouble()) {\n    auto align = config_[\"align\"].asFloat();\n    if (rotate == 90 || rotate == 270) {\n      label_.set_yalign(align);\n    } else {\n      label_.set_xalign(align);\n    }\n  }\n\n  // If a GTKMenu is requested in the config\n  if (config_[\"menu\"].isString()) {\n    // Create the GTKMenu widget\n    try {\n      // Check that the file exists\n      std::string menuFile = config_[\"menu-file\"].asString();\n\n      // there might be \"~\" or \"$HOME\" in original path, try to expand it.\n      auto result = Config::tryExpandPath(menuFile, \"\");\n      if (result.empty()) {\n        throw std::runtime_error(\"Failed to expand file: \" + menuFile);\n      }\n\n      menuFile = result.front();\n      // Read the menu descriptor file\n      std::ifstream file(menuFile);\n      if (!file.is_open()) {\n        throw std::runtime_error(\"Failed to open file: \" + menuFile);\n      }\n      std::stringstream fileContent;\n      fileContent << file.rdbuf();\n      GtkBuilder* builder = gtk_builder_new();\n\n      // Make the GtkBuilder and check for errors in his parsing\n      if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) {\n        g_object_unref(builder);\n        throw std::runtime_error(\"Error found in the file \" + menuFile);\n      }\n\n      menu_ = gtk_builder_get_object(builder, \"menu\");\n      if (menu_ == nullptr) {\n        g_object_unref(builder);\n        throw std::runtime_error(\"Failed to get 'menu' object from GtkBuilder\");\n      }\n      // Keep the menu alive after dropping the transient GtkBuilder.\n      g_object_ref(menu_);\n      submenus_ = std::map<std::string, GtkMenuItem*>();\n      menuActionsMap_ = std::map<std::string, std::string>();\n\n      // Linking actions to the GTKMenu based on\n      for (Json::Value::const_iterator it = config_[\"menu-actions\"].begin();\n           it != config_[\"menu-actions\"].end(); ++it) {\n        std::string key = it.key().asString();\n        auto* item = gtk_builder_get_object(builder, key.c_str());\n        if (item == nullptr) {\n          spdlog::warn(\"Menu item '{}' not found in builder file\", key);\n          continue;\n        }\n        submenus_[key] = GTK_MENU_ITEM(item);\n        menuActionsMap_[key] = it->asString();\n        g_signal_connect(submenus_[key], \"activate\",\n                 G_CALLBACK(handleGtkMenuEvent),\n                 (gpointer)g_strdup(menuActionsMap_[key].c_str()));          \n      }\n      g_object_unref(builder);\n    } catch (std::runtime_error& e) {\n      spdlog::warn(\"Error while creating the menu : {}. Menu popup not activated.\", e.what());\n    }\n  }\n\n  if (config_[\"justify\"].isString()) {\n    auto justify_str = config_[\"justify\"].asString();\n    if (justify_str == \"left\") {\n      label_.set_justify(Gtk::Justification::JUSTIFY_LEFT);\n    } else if (justify_str == \"right\") {\n      label_.set_justify(Gtk::Justification::JUSTIFY_RIGHT);\n    } else if (justify_str == \"center\") {\n      label_.set_justify(Gtk::Justification::JUSTIFY_CENTER);\n    }\n  }\n}\n\nauto ALabel::update() -> void { AModule::update(); }\n\nstd::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {\n  auto format_icons = config_[\"format-icons\"];\n  if (format_icons.isObject()) {\n    if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {\n      format_icons = format_icons[alt];\n    } else {\n      format_icons = format_icons[\"default\"];\n    }\n  }\n  if (format_icons.isArray()) {\n    auto size = format_icons.size();\n    if (size != 0U) {\n      auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);\n      auto idx = std::clamp(percentage / divisor, 0U, size - 1);\n      format_icons = format_icons[idx];\n    }\n  }\n  if (format_icons.isString()) {\n    return format_icons.asString();\n  }\n  return \"\";\n}\n\nstd::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts,\n                            uint16_t max) {\n  auto format_icons = config_[\"format-icons\"];\n  if (format_icons.isObject()) {\n    std::string _alt = \"default\";\n    for (const auto& alt : alts) {\n      if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {\n        _alt = alt;\n        break;\n      }\n    }\n    format_icons = format_icons[_alt];\n  }\n  if (format_icons.isArray()) {\n    auto size = format_icons.size();\n    if (size != 0U) {\n      auto divisor = std::max(1U, (max == 0 ? 100U : static_cast<unsigned>(max)) / size);\n      auto idx = std::clamp(percentage / divisor, 0U, size - 1);\n      format_icons = format_icons[idx];\n    }\n  }\n  if (format_icons.isString()) {\n    return format_icons.asString();\n  }\n  return \"\";\n}\n\nbool waybar::ALabel::handleToggle(GdkEventButton* const& e) {\n  if (config_[\"format-alt-click\"].isUInt() && e->button == config_[\"format-alt-click\"].asUInt()) {\n    alt_ = !alt_;\n    if (alt_ && config_[\"format-alt\"].isString()) {\n      format_ = config_[\"format-alt\"].asString();\n    } else {\n      format_ = default_format_;\n    }\n  }\n  return AModule::handleToggle(e);\n}\n\nvoid ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) {\n  waybar::util::command::forkExec((char*)data, \"GtkMenu\");\n}\n\nstd::string ALabel::getState(uint8_t value, bool lesser) {\n  if (!config_[\"states\"].isObject()) {\n    return \"\";\n  }\n  // Get current state\n  std::vector<std::pair<std::string, uint8_t>> states;\n  if (config_[\"states\"].isObject()) {\n    for (auto it = config_[\"states\"].begin(); it != config_[\"states\"].end(); ++it) {\n      if (it->isUInt() && it.key().isString()) {\n        states.emplace_back(it.key().asString(), it->asUInt());\n      }\n    }\n  }\n  // Sort states\n  std::ranges::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {\n    return lesser ? a.second < b.second : a.second > b.second;\n  });\n  std::string valid_state;\n  for (auto const& state : states) {\n    if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) {\n      label_.get_style_context()->add_class(state.first);\n      valid_state = state.first;\n    } else {\n      label_.get_style_context()->remove_class(state.first);\n    }\n  }\n  return valid_state;\n}\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/AModule.cpp",
    "content": "#include \"AModule.hpp\"\n\n#include <fmt/format.h>\n#include <spdlog/spdlog.h>\n\n#include <util/command.hpp>\n\n#include \"gdk/gdk.h\"\n#include \"gdkmm/cursor.h\"\n\nnamespace waybar {\n\nAModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,\n                 bool enable_click, bool enable_scroll)\n    : name_(name),\n      config_(config),\n      isTooltip{config_[\"tooltip\"].isBool() ? config_[\"tooltip\"].asBool() : true},\n      isExpand{config_[\"expand\"].isBool() ? config_[\"expand\"].asBool() : false},\n      distance_scrolled_y_(0.0),\n      distance_scrolled_x_(0.0) {\n  // Configure module action Map\n  const Json::Value actions{config_[\"actions\"]};\n\n  for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) {\n    if (it.key().isString() && it->isString())\n      if (!eventActionMap_.contains(it.key().asString())) {\n        eventActionMap_.insert({it.key().asString(), it->asString()});\n        enable_click = true;\n        enable_scroll = true;\n      } else\n        spdlog::warn(\"Duplicate action is ignored: {0}\", it.key().asString());\n    else\n      spdlog::warn(\"Wrong actions section configuration. See config by index: {}\", it.index());\n  }\n\n  event_box_.signal_enter_notify_event().connect(sigc::mem_fun(*this, &AModule::handleMouseEnter));\n  event_box_.signal_leave_notify_event().connect(sigc::mem_fun(*this, &AModule::handleMouseLeave));\n\n  // configure events' user commands\n  // hasUserEvents is true if any element from eventMap_ is satisfying the condition in the lambda\n  bool hasUserEvents =\n      std::find_if(eventMap_.cbegin(), eventMap_.cend(), [&config](const auto& eventEntry) {\n        // True if there is any non-release type event\n        return eventEntry.first.second != GdkEventType::GDK_BUTTON_RELEASE &&\n               config[eventEntry.second].isString();\n      }) != eventMap_.cend();\n\n  if (enable_click || hasUserEvents) {\n    hasUserEvents_ = true;\n    event_box_.add_events(Gdk::BUTTON_PRESS_MASK);\n    event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &AModule::handleToggle));\n  } else {\n    hasUserEvents_ = false;\n  }\n\n  bool hasReleaseEvent =\n      std::find_if(eventMap_.cbegin(), eventMap_.cend(), [&config](const auto& eventEntry) {\n        // True if there is any non-release type event\n        return eventEntry.first.second == GdkEventType::GDK_BUTTON_RELEASE &&\n               config[eventEntry.second].isString();\n      }) != eventMap_.cend();\n  if (hasReleaseEvent) {\n    event_box_.add_events(Gdk::BUTTON_RELEASE_MASK);\n    event_box_.signal_button_release_event().connect(sigc::mem_fun(*this, &AModule::handleRelease));\n  }\n  if (config_[\"on-scroll-up\"].isString() || config_[\"on-scroll-down\"].isString() ||\n      config_[\"on-scroll-left\"].isString() || config_[\"on-scroll-right\"].isString() ||\n      enable_scroll) {\n    event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n    event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &AModule::handleScroll));\n  }\n\n  // Respect user configuration of cursor\n  if (config_.isMember(\"cursor\")) {\n    if (config_[\"cursor\"].isBool() && config_[\"cursor\"].asBool()) {\n      setCursor(Gdk::HAND2);\n    } else if (config_[\"cursor\"].isInt()) {\n      setCursor(Gdk::CursorType(config_[\"cursor\"].asInt()));\n    } else {\n      spdlog::warn(\"unknown cursor option configured on module {}\", name_);\n    }\n  }\n}\n\nAModule::~AModule() {\n  for (const auto& pid : pid_children_) {\n    if (pid != -1) {\n      killpg(pid, SIGTERM);\n    }\n  }\n  if (menu_ != nullptr) {\n    g_object_unref(menu_);\n    menu_ = nullptr;\n  }\n}\n\nauto AModule::update() -> void {\n  // Run user-provided update handler if configured\n  if (config_[\"on-update\"].isString()) {\n    pid_children_.push_back(util::command::forkExec(config_[\"on-update\"].asString()));\n  }\n}\n// Get mapping between event name and module action name\n// Then call overridden doAction in order to call appropriate module action\nauto AModule::doAction(const std::string& name) -> void {\n  if (!name.empty()) {\n    const std::map<std::string, std::string>::const_iterator& recA{eventActionMap_.find(name)};\n    // Call overridden action if derived class has implemented it\n    if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);\n  }\n}\n\nvoid AModule::setCursor(Gdk::CursorType const& c) {\n  auto gdk_window = event_box_.get_window();\n  if (gdk_window) {\n    auto cursor = Gdk::Cursor::create(c);\n    gdk_window->set_cursor(cursor);\n  } else {\n    // window may not be accessible yet, in this case,\n    // schedule another call for setting the cursor in 1 sec\n    Glib::signal_timeout().connect_seconds(\n        [this, c]() {\n          setCursor(c);\n          return false;\n        },\n        1);\n  }\n}\n\nbool AModule::handleMouseEnter(GdkEventCrossing* const& e) {\n  if (auto* module = event_box_.get_child(); module != nullptr) {\n    module->set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  }\n\n  // Default behavior indicating event availability\n  if (hasUserEvents_ && !config_.isMember(\"cursor\")) {\n    setCursor(Gdk::HAND2);\n  }\n\n  return false;\n}\n\nbool AModule::handleMouseLeave(GdkEventCrossing* const& e) {\n  if (auto* module = event_box_.get_child(); module != nullptr) {\n    module->unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  }\n\n  // Default behavior indicating event availability\n  if (hasUserEvents_ && !config_.isMember(\"cursor\")) {\n    setCursor(Gdk::ARROW);\n  }\n\n  return false;\n}\n\nbool AModule::handleToggle(GdkEventButton* const& e) { return handleUserEvent(e); }\n\nbool AModule::handleRelease(GdkEventButton* const& e) { return handleUserEvent(e); }\n\nbool AModule::handleUserEvent(GdkEventButton* const& e) {\n  std::string format{};\n  const std::map<std::pair<uint, GdkEventType>, std::string>::const_iterator& rec{\n      eventMap_.find(std::pair(e->button, e->type))};\n\n  if (rec != eventMap_.cend()) {\n    // First call module actions\n    this->AModule::doAction(rec->second);\n\n    format = rec->second;\n  }\n\n  // Check that a menu has been configured\n  if (rec != eventMap_.cend() && config_[\"menu\"].isString()) {\n    // Check if the event is the one specified for the \"menu\" option\n    if (rec->second == config_[\"menu\"].asString() && menu_ != nullptr) {\n      // Popup the menu\n      gtk_widget_show_all(GTK_WIDGET(menu_));\n      gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));\n      // Manually reset prelight to make sure the module doesn't stay in a hover state\n      if (auto* module = event_box_.get_child(); module != nullptr) {\n        module->unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n      }\n    }\n  }\n  // Second call user scripts\n  if (!format.empty()) {\n    if (config_[format].isString())\n      format = config_[format].asString();\n    else\n      format.clear();\n  }\n  if (!format.empty()) {\n    pid_children_.push_back(util::command::forkExec(format));\n  }\n  dp.emit();\n  return true;\n}\n\nAModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) {\n  // only affects up/down\n  bool reverse = config_[\"reverse-scrolling\"].asBool();\n  bool reverse_mouse = config_[\"reverse-mouse-scrolling\"].asBool();\n\n  // ignore reverse-scrolling if event comes from a mouse wheel\n  GdkDevice* device = gdk_event_get_source_device((GdkEvent*)e);\n  if (device != nullptr && gdk_device_get_source(device) == GDK_SOURCE_MOUSE) {\n    reverse = reverse_mouse;\n  }\n\n  switch (e->direction) {\n    case GDK_SCROLL_UP:\n      return reverse ? SCROLL_DIR::DOWN : SCROLL_DIR::UP;\n    case GDK_SCROLL_DOWN:\n      return reverse ? SCROLL_DIR::UP : SCROLL_DIR::DOWN;\n    case GDK_SCROLL_LEFT:\n      return SCROLL_DIR::LEFT;\n    case GDK_SCROLL_RIGHT:\n      return SCROLL_DIR::RIGHT;\n    case GDK_SCROLL_SMOOTH: {\n      SCROLL_DIR dir{SCROLL_DIR::NONE};\n\n      distance_scrolled_y_ += e->delta_y;\n      distance_scrolled_x_ += e->delta_x;\n\n      gdouble threshold = 0;\n      if (config_[\"smooth-scrolling-threshold\"].isNumeric()) {\n        threshold = config_[\"smooth-scrolling-threshold\"].asDouble();\n      }\n\n      if (distance_scrolled_y_ < -threshold) {\n        dir = reverse ? SCROLL_DIR::DOWN : SCROLL_DIR::UP;\n      } else if (distance_scrolled_y_ > threshold) {\n        dir = reverse ? SCROLL_DIR::UP : SCROLL_DIR::DOWN;\n      } else if (distance_scrolled_x_ > threshold) {\n        dir = SCROLL_DIR::RIGHT;\n      } else if (distance_scrolled_x_ < -threshold) {\n        dir = SCROLL_DIR::LEFT;\n      }\n\n      switch (dir) {\n        case SCROLL_DIR::UP:\n        case SCROLL_DIR::DOWN:\n          distance_scrolled_y_ = 0;\n          break;\n        case SCROLL_DIR::LEFT:\n        case SCROLL_DIR::RIGHT:\n          distance_scrolled_x_ = 0;\n          break;\n        case SCROLL_DIR::NONE:\n          break;\n      }\n\n      return dir;\n    }\n    // Silence -Wreturn-type:\n    default:\n      return SCROLL_DIR::NONE;\n  }\n}\n\nbool AModule::handleScroll(GdkEventScroll* e) {\n  auto dir = getScrollDir(e);\n  std::string eventName{};\n\n  if (dir == SCROLL_DIR::UP)\n    eventName = \"on-scroll-up\";\n  else if (dir == SCROLL_DIR::DOWN)\n    eventName = \"on-scroll-down\";\n  else if (dir == SCROLL_DIR::LEFT)\n    eventName = \"on-scroll-left\";\n  else if (dir == SCROLL_DIR::RIGHT)\n    eventName = \"on-scroll-right\";\n\n  // First call module actions\n  this->AModule::doAction(eventName);\n  // Second call user scripts\n  if (config_[eventName].isString())\n    pid_children_.push_back(util::command::forkExec(config_[eventName].asString()));\n\n  dp.emit();\n  return true;\n}\n\nbool AModule::tooltipEnabled() const { return isTooltip; }\nbool AModule::expandEnabled() const { return isExpand; }\n\nAModule::operator Gtk::Widget&() { return event_box_; }\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/ASlider.cpp",
    "content": "#include \"ASlider.hpp\"\n\n#include \"gtkmm/adjustment.h\"\n#include \"gtkmm/enums.h\"\n\nnamespace waybar {\n\nASlider::ASlider(const Json::Value& config, const std::string& name, const std::string& id)\n    : AModule(config, name, id, false, false),\n      vertical_(config_[\"orientation\"].asString() == \"vertical\"),\n      scale_(vertical_ ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL) {\n  scale_.set_name(name);\n  if (!id.empty()) {\n    scale_.get_style_context()->add_class(id);\n  }\n  scale_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(scale_);\n  scale_.signal_value_changed().connect(sigc::mem_fun(*this, &ASlider::onValueChanged));\n\n  if (config_[\"min\"].isUInt()) {\n    min_ = config_[\"min\"].asUInt();\n  }\n\n  if (config_[\"max\"].isUInt()) {\n    max_ = config_[\"max\"].asUInt();\n  }\n\n  scale_.set_inverted(vertical_);\n  scale_.set_draw_value(false);\n  scale_.set_adjustment(Gtk::Adjustment::create(curr_, min_, max_ + 1, 1, 1, 1));\n}\n\nvoid ASlider::onValueChanged() {}\n\n}  // namespace waybar"
  },
  {
    "path": "src/bar.cpp",
    "content": "#include \"bar.hpp\"\n\n#include <gtk-layer-shell.h>\n#include <spdlog/spdlog.h>\n\n#include <type_traits>\n\n#include \"client.hpp\"\n#include \"factory.hpp\"\n#include \"group.hpp\"\n#include \"util/enum.hpp\"\n#include \"util/kill_signal.hpp\"\n\n#ifdef HAVE_SWAY\n#include \"modules/sway/bar.hpp\"\n#endif\n\nnamespace waybar {\nstatic constexpr const char* MIN_HEIGHT_MSG =\n    \"Requested height: {} is less than the minimum height: {} required by the modules\";\n\nstatic constexpr const char* MIN_WIDTH_MSG =\n    \"Requested width: {} is less than the minimum width: {} required by the modules\";\n\nstatic constexpr const char* BAR_SIZE_MSG = \"Bar configured (width: {}, height: {}) for output: {}\";\n\nconst Bar::bar_mode_map Bar::PRESET_MODES = {  //\n    {\"default\",\n     {// Special mode to hold the global bar configuration\n      .layer = bar_layer::BOTTOM,\n      .exclusive = true,\n      .passthrough = false,\n      .visible = true}},\n    {\"dock\",\n     {// Modes supported by the sway config; see man sway-bar(5)\n      .layer = bar_layer::BOTTOM,\n      .exclusive = true,\n      .passthrough = false,\n      .visible = true}},\n    {\"hide\",\n     {//\n      .layer = bar_layer::OVERLAY,\n      .exclusive = false,\n      .passthrough = false,\n      .visible = true}},\n    {\"invisible\",\n     {//\n      .layer = bar_layer::BOTTOM,\n      .exclusive = false,\n      .passthrough = true,\n      .visible = false}},\n    {\"overlay\",\n     {//\n      .layer = bar_layer::OVERLAY,\n      .exclusive = false,\n      .passthrough = true,\n      .visible = true}}};\n\nconst std::string Bar::MODE_DEFAULT = \"default\";\nconst std::string Bar::MODE_INVISIBLE = \"invisible\";\nconst std::string_view DEFAULT_BAR_ID = \"bar-0\";\n\n/* Deserializer for enum bar_layer */\nvoid from_json(const Json::Value& j, bar_layer& l) {\n  if (j == \"bottom\") {\n    l = bar_layer::BOTTOM;\n  } else if (j == \"top\") {\n    l = bar_layer::TOP;\n  } else if (j == \"overlay\") {\n    l = bar_layer::OVERLAY;\n  }\n}\n\n/* Deserializer for struct bar_mode */\nvoid from_json(const Json::Value& j, bar_mode& m) {\n  if (j.isObject()) {\n    if (const auto& v = j[\"layer\"]; v.isString()) {\n      from_json(v, m.layer);\n    }\n    if (const auto& v = j[\"exclusive\"]; v.isBool()) {\n      m.exclusive = v.asBool();\n    }\n    if (const auto& v = j[\"passthrough\"]; v.isBool()) {\n      m.passthrough = v.asBool();\n    }\n    if (const auto& v = j[\"visible\"]; v.isBool()) {\n      m.visible = v.asBool();\n    }\n  }\n}\n\n/* Deserializer for enum Gtk::PositionType */\nvoid from_json(const Json::Value& j, Gtk::PositionType& pos) {\n  if (j == \"left\") {\n    pos = Gtk::POS_LEFT;\n  } else if (j == \"right\") {\n    pos = Gtk::POS_RIGHT;\n  } else if (j == \"top\") {\n    pos = Gtk::POS_TOP;\n  } else if (j == \"bottom\") {\n    pos = Gtk::POS_BOTTOM;\n  }\n}\n\nGlib::ustring to_string(Gtk::PositionType pos) {\n  switch (pos) {\n    case Gtk::POS_LEFT:\n      return \"left\";\n    case Gtk::POS_RIGHT:\n      return \"right\";\n    case Gtk::POS_TOP:\n      return \"top\";\n    case Gtk::POS_BOTTOM:\n      return \"bottom\";\n  }\n  throw std::runtime_error(\"Invalid Gtk::PositionType\");\n}\n\n/* Deserializer for JSON Object -> map<string compatible type, Value>\n * Assumes that all the values in the object are deserializable to the same type.\n */\ntemplate <typename Key, typename Value,\n          typename = std::enable_if_t<std::is_convertible_v<std::string, Key>>>\nvoid from_json(const Json::Value& j, std::map<Key, Value>& m) {\n  if (j.isObject()) {\n    for (auto it = j.begin(); it != j.end(); ++it) {\n      from_json(*it, m[it.key().asString()]);\n    }\n  }\n}\n\n};  // namespace waybar\n\nwaybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)\n    : output(w_output),\n      config(w_config),\n      surface(nullptr),\n      window{Gtk::WindowType::WINDOW_TOPLEVEL},\n      x_global(0),\n      y_global(0),\n      margins_{.top = 0, .right = 0, .bottom = 0, .left = 0},\n      left_(Gtk::ORIENTATION_HORIZONTAL, 0),\n      center_(Gtk::ORIENTATION_HORIZONTAL, 0),\n      right_(Gtk::ORIENTATION_HORIZONTAL, 0),\n      box_(Gtk::ORIENTATION_HORIZONTAL, 0) {\n  window.set_title(\"waybar\");\n  window.set_name(\"waybar\");\n  window.set_decorated(false);\n  window.get_style_context()->add_class(output->name);\n  window.get_style_context()->add_class(config[\"name\"].asString());\n\n  from_json(config[\"position\"], position);\n  orientation = (position == Gtk::POS_LEFT || position == Gtk::POS_RIGHT)\n                    ? Gtk::ORIENTATION_VERTICAL\n                    : Gtk::ORIENTATION_HORIZONTAL;\n\n  window.get_style_context()->add_class(to_string(position));\n\n  left_ = Gtk::Box(orientation, 0);\n  center_ = Gtk::Box(orientation, 0);\n  right_ = Gtk::Box(orientation, 0);\n  box_ = Gtk::Box(orientation, 0);\n\n  left_.get_style_context()->add_class(\"modules-left\");\n  center_.get_style_context()->add_class(\"modules-center\");\n  right_.get_style_context()->add_class(\"modules-right\");\n\n  if (config[\"spacing\"].isInt()) {\n    int spacing = config[\"spacing\"].asInt();\n    left_.set_spacing(spacing);\n    center_.set_spacing(spacing);\n    right_.set_spacing(spacing);\n  }\n\n  height_ = config[\"height\"].isUInt() ? config[\"height\"].asUInt() : 0;\n  width_ = config[\"width\"].isUInt() ? config[\"width\"].asUInt() : 0;\n\n  if (config[\"margin-top\"].isInt() || config[\"margin-right\"].isInt() ||\n      config[\"margin-bottom\"].isInt() || config[\"margin-left\"].isInt()) {\n    margins_ = {\n        config[\"margin-top\"].isInt() ? config[\"margin-top\"].asInt() : 0,\n        config[\"margin-right\"].isInt() ? config[\"margin-right\"].asInt() : 0,\n        config[\"margin-bottom\"].isInt() ? config[\"margin-bottom\"].asInt() : 0,\n        config[\"margin-left\"].isInt() ? config[\"margin-left\"].asInt() : 0,\n    };\n  } else if (config[\"margin\"].isString()) {\n    std::istringstream iss(config[\"margin\"].asString());\n    std::vector<std::string> margins{std::istream_iterator<std::string>(iss), {}};\n    try {\n      if (margins.size() == 1) {\n        auto gaps = std::stoi(margins[0], nullptr, 10);\n        margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};\n      }\n      if (margins.size() == 2) {\n        auto vertical_margins = std::stoi(margins[0], nullptr, 10);\n        auto horizontal_margins = std::stoi(margins[1], nullptr, 10);\n        margins_ = {.top = vertical_margins,\n                    .right = horizontal_margins,\n                    .bottom = vertical_margins,\n                    .left = horizontal_margins};\n      }\n      if (margins.size() == 3) {\n        auto horizontal_margins = std::stoi(margins[1], nullptr, 10);\n        margins_ = {.top = std::stoi(margins[0], nullptr, 10),\n                    .right = horizontal_margins,\n                    .bottom = std::stoi(margins[2], nullptr, 10),\n                    .left = horizontal_margins};\n      }\n      if (margins.size() == 4) {\n        margins_ = {.top = std::stoi(margins[0], nullptr, 10),\n                    .right = std::stoi(margins[1], nullptr, 10),\n                    .bottom = std::stoi(margins[2], nullptr, 10),\n                    .left = std::stoi(margins[3], nullptr, 10)};\n      }\n    } catch (...) {\n      spdlog::warn(\"Invalid margins: {}\", config[\"margin\"].asString());\n    }\n  } else if (config[\"margin\"].isInt()) {\n    auto gaps = config[\"margin\"].asInt();\n    margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};\n  }\n\n  window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));\n  output->monitor->property_geometry().signal_changed().connect(\n      sigc::mem_fun(*this, &Bar::onOutputGeometryChanged));\n\n  // this has to be executed before GtkWindow.realize\n  auto* gtk_window = window.gobj();\n  gtk_layer_init_for_window(gtk_window);\n  gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE);\n  gtk_layer_set_monitor(gtk_window, output->monitor->gobj());\n  gtk_layer_set_namespace(gtk_window,\n                          config[\"name\"].isString() ? config[\"name\"].asCString() : \"waybar\");\n\n  gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);\n  gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);\n  gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top);\n  gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom);\n\n  window.set_size_request(width_, height_);\n\n  // Position needs to be set after calculating the height due to the\n  // GTK layer shell anchors logic relying on the dimensions of the bar.\n  setPosition(position);\n\n  /* Read custom modes if available */\n  if (auto modes = config.get(\"modes\", {}); modes.isObject()) {\n    from_json(modes, configured_modes);\n  }\n\n  /* Update \"default\" mode with the global bar options */\n  from_json(config, configured_modes[MODE_DEFAULT]);\n\n  if (auto mode = config.get(\"mode\", {}); mode.isString()) {\n    setMode(config[\"mode\"].asString());\n  } else {\n    setMode(MODE_DEFAULT);\n  }\n\n  if (config[\"start_hidden\"].asBool()) {\n    setVisible(false);\n  }\n\n  window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));\n\n#if HAVE_SWAY\n  if (auto ipc = config[\"ipc\"]; ipc.isBool() && ipc.asBool()) {\n    bar_id = Client::inst()->bar_id;\n    if (auto id = config[\"id\"]; id.isString()) {\n      bar_id = id.asString();\n    }\n    if (bar_id.empty()) {\n      bar_id = DEFAULT_BAR_ID;\n    }\n    try {\n      _ipc_client = std::make_unique<BarIpcClient>(*this);\n    } catch (const std::exception& exc) {\n      spdlog::warn(\"Failed to open bar ipc connection: {}\", exc.what());\n    }\n  }\n#endif\n\n  waybar::util::EnumParser<util::KillSignalAction> m_signalActionEnumParser;\n  const auto& configSigusr1 = config[\"on-sigusr1\"];\n  if (configSigusr1.isString()) {\n    auto strSigusr1 = configSigusr1.asString();\n    try {\n      onSigusr1 =\n          m_signalActionEnumParser.parseStringToEnum(strSigusr1, util::userKillSignalActions);\n    } catch (const std::invalid_argument& e) {\n      onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1;\n      spdlog::warn(\n          \"Invalid string representation for on-sigusr1. Falling back to default mode (toggle).\");\n    }\n  }\n  const auto& configSigusr2 = config[\"on-sigusr2\"];\n  if (configSigusr2.isString()) {\n    auto strSigusr2 = configSigusr2.asString();\n    try {\n      onSigusr2 =\n          m_signalActionEnumParser.parseStringToEnum(strSigusr2, util::userKillSignalActions);\n    } catch (const std::invalid_argument& e) {\n      onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2;\n      spdlog::warn(\n          \"Invalid string representation for on-sigusr2. Falling back to default mode (reload).\");\n    }\n  }\n\n  setupWidgets();\n  window.show_all();\n\n  if (spdlog::should_log(spdlog::level::debug)) {\n    // Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.\n    char* gtk_tree = gtk_style_context_to_string(\n        window.get_style_context()->gobj(),\n        (GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |\n                                    GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));\n    spdlog::debug(\"GTK widget tree:\\n{}\", gtk_tree);\n    g_free(gtk_tree);\n  }\n}\n\n/* Need to define it here because of forward declared members */\nwaybar::Bar::~Bar() = default;\n\nvoid waybar::Bar::setMode(const std::string& mode) {\n  using namespace std::literals::string_literals;\n\n  auto style = window.get_style_context();\n  /* remove styles added by previous setMode calls */\n  style->remove_class(\"mode-\"s + last_mode_);\n\n  auto it = configured_modes.find(mode);\n  if (it != configured_modes.end()) {\n    last_mode_ = mode;\n    style->add_class(\"mode-\"s + last_mode_);\n    setMode(it->second);\n  } else {\n    spdlog::warn(\"Unknown mode \\\"{}\\\" requested\", mode);\n    last_mode_ = MODE_DEFAULT;\n    style->add_class(\"mode-\"s + last_mode_);\n    setMode(configured_modes.at(MODE_DEFAULT));\n  }\n}\n\nvoid waybar::Bar::setMode(const struct bar_mode& mode) {\n  auto* gtk_window = window.gobj();\n\n  auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;\n  if (mode.layer == bar_layer::TOP) {\n    layer = GTK_LAYER_SHELL_LAYER_TOP;\n  } else if (mode.layer == bar_layer::OVERLAY) {\n    layer = GTK_LAYER_SHELL_LAYER_OVERLAY;\n  }\n  gtk_layer_set_layer(gtk_window, layer);\n\n  if (mode.exclusive) {\n    gtk_layer_auto_exclusive_zone_enable(gtk_window);\n  } else {\n    gtk_layer_set_exclusive_zone(gtk_window, 0);\n  }\n\n  setPassThrough(passthrough_ = mode.passthrough);\n\n  if (mode.visible) {\n    window.get_style_context()->remove_class(\"hidden\");\n    window.set_opacity(1);\n  } else {\n    window.get_style_context()->add_class(\"hidden\");\n    window.set_opacity(0);\n  }\n  /*\n   * All the changes above require `wl_surface_commit`.\n   * gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in\n   * certain scenarios, such as fully occluded bar.\n   */\n  gtk_layer_try_force_commit(gtk_window);\n  wl_display_flush(Client::inst()->wl_display);\n}\n\nvoid waybar::Bar::setPassThrough(bool passthrough) {\n  auto gdk_window = window.get_window();\n  if (gdk_window) {\n    Cairo::RefPtr<Cairo::Region> region;\n    if (passthrough) {\n      region = Cairo::Region::create();\n    }\n    gdk_window->input_shape_combine_region(region, 0, 0);\n  }\n}\n\nvoid waybar::Bar::setPosition(Gtk::PositionType position) {\n  std::array<gboolean, GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER> anchors;\n  anchors.fill(TRUE);\n\n  auto orientation = (position == Gtk::POS_LEFT || position == Gtk::POS_RIGHT)\n                         ? Gtk::ORIENTATION_VERTICAL\n                         : Gtk::ORIENTATION_HORIZONTAL;\n\n  switch (position) {\n    case Gtk::POS_LEFT:\n      anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = FALSE;\n      break;\n    case Gtk::POS_RIGHT:\n      anchors[GTK_LAYER_SHELL_EDGE_LEFT] = FALSE;\n      break;\n    case Gtk::POS_BOTTOM:\n      anchors[GTK_LAYER_SHELL_EDGE_TOP] = FALSE;\n      break;\n    default: /* Gtk::POS_TOP */\n      anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = FALSE;\n      break;\n  };\n  // Disable anchoring for other edges too if the width\n  // or the height has been set to a value other than 'auto'\n  // otherwise the bar will use all space\n  uint32_t configured_width = config[\"width\"].isUInt() ? config[\"width\"].asUInt() : 0;\n  uint32_t configured_height = config[\"height\"].isUInt() ? config[\"height\"].asUInt() : 0;\n  if (orientation == Gtk::ORIENTATION_VERTICAL && configured_height > 1) {\n    anchors[GTK_LAYER_SHELL_EDGE_TOP] = FALSE;\n    anchors[GTK_LAYER_SHELL_EDGE_BOTTOM] = FALSE;\n  } else if (orientation == Gtk::ORIENTATION_HORIZONTAL && configured_width > 1) {\n    anchors[GTK_LAYER_SHELL_EDGE_LEFT] = FALSE;\n    anchors[GTK_LAYER_SHELL_EDGE_RIGHT] = FALSE;\n  }\n\n  for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT, GTK_LAYER_SHELL_EDGE_RIGHT, GTK_LAYER_SHELL_EDGE_TOP,\n                    GTK_LAYER_SHELL_EDGE_BOTTOM}) {\n    gtk_layer_set_anchor(window.gobj(), edge, anchors[edge]);\n  }\n}\n\nvoid waybar::Bar::onMap(GdkEventAny* /*unused*/) {\n  /*\n   * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).\n   */\n  auto gdk_window_ref = window.get_window();\n  if (!gdk_window_ref) {\n    spdlog::warn(\"Failed to get GDK window during onMap, deferring surface initialization\");\n    return;\n  }\n\n  auto* gdk_window = gdk_window_ref->gobj();\n  if (!gdk_window) {\n    spdlog::warn(\"GDK window object is null during onMap, deferring surface initialization\");\n    return;\n  }\n\n  surface = gdk_wayland_window_get_wl_surface(gdk_window);\n  configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window));\n\n  setPassThrough(passthrough_);\n}\n\nvoid waybar::Bar::setVisible(bool value) {\n  visible = value;\n  if (auto mode = config.get(\"mode\", {}); mode.isString()) {\n    setMode(visible ? config[\"mode\"].asString() : MODE_INVISIBLE);\n  } else {\n    setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);\n  }\n}\n\nvoid waybar::Bar::toggle() { setVisible(!visible); }\nvoid waybar::Bar::show() { setVisible(true); }\nvoid waybar::Bar::hide() { setVisible(false); }\n\n// Converting string to button code rn as to avoid doing it later\nvoid waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {\n  if (config.isMember(module_name)) {\n    Json::Value& module = config[module_name];\n    if (module.isMember(\"format-alt\")) {\n      if (module.isMember(\"format-alt-click\")) {\n        Json::Value& click = module[\"format-alt-click\"];\n        if (click.isString()) {\n          if (click == \"click-right\") {\n            module[\"format-alt-click\"] = 3U;\n          } else if (click == \"click-middle\") {\n            module[\"format-alt-click\"] = 2U;\n          } else if (click == \"click-backward\") {\n            module[\"format-alt-click\"] = 8U;\n          } else if (click == \"click-forward\") {\n            module[\"format-alt-click\"] = 9U;\n          } else {\n            module[\"format-alt-click\"] = 1U;  // default click-left\n          }\n        } else {\n          module[\"format-alt-click\"] = 1U;\n        }\n      } else {\n        module[\"format-alt-click\"] = 1U;\n      }\n    }\n  }\n}\n\nvoid waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {\n  if (config.isMember(module_list_name)) {\n    Json::Value& modules = config[module_list_name];\n    for (const Json::Value& module_name : modules) {\n      if (module_name.isString()) {\n        auto ref = module_name.asString();\n        if (ref.compare(0, 6, \"group/\") == 0 && ref.size() > 6) {\n          Json::Value& group_modules = config[ref][\"modules\"];\n          for (const Json::Value& module_name : group_modules) {\n            if (module_name.isString()) {\n              setupAltFormatKeyForModule(module_name.asString());\n            }\n          }\n        } else {\n          setupAltFormatKeyForModule(ref);\n        }\n      }\n    }\n  }\n}\n\nvoid waybar::Bar::handleSignal(int signal) {\n  for (auto& module : modules_all_) {\n    module->refresh(signal);\n  }\n}\n\nwaybar::util::KillSignalAction waybar::Bar::getOnSigusr1Action() { return this->onSigusr1; }\nwaybar::util::KillSignalAction waybar::Bar::getOnSigusr2Action() { return this->onSigusr2; }\n\nvoid waybar::Bar::getModules(const Factory& factory, const std::string& pos,\n                             waybar::Group* group = nullptr) {\n  auto module_list = group != nullptr ? config[pos][\"modules\"] : config[pos];\n  if (module_list.isArray()) {\n    for (const auto& name : module_list) {\n      try {\n        auto ref = name.asString();\n        AModule* module;\n\n        if (ref.compare(0, 6, \"group/\") == 0 && ref.size() > 6) {\n          auto hash_pos = ref.find('#');\n          auto id_name = ref.substr(6, hash_pos - 6);\n          auto class_name = hash_pos != std::string::npos ? ref.substr(hash_pos + 1) : \"\";\n\n          auto vertical = (group != nullptr ? group->getBox().get_orientation()\n                                            : box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL;\n\n          auto group_config = config[ref];\n          if (group_config[\"modules\"].isNull()) {\n            spdlog::warn(\"Group definition '{}' has not been found, group will be hidden\", ref);\n          }\n          auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical);\n          getModules(factory, ref, group_module);\n          module = group_module;\n        } else {\n          module = factory.makeModule(ref, pos);\n        }\n\n        std::shared_ptr<AModule> module_sp(module);\n        modules_all_.emplace_back(module_sp);\n        if (group != nullptr) {\n          group->addWidget(*module);\n        } else {\n          if (pos == \"modules-left\") {\n            modules_left_.emplace_back(module_sp);\n          }\n          if (pos == \"modules-center\") {\n            modules_center_.emplace_back(module_sp);\n          }\n          if (pos == \"modules-right\") {\n            modules_right_.emplace_back(module_sp);\n          }\n        }\n        module->dp.connect([module, ref] {\n          try {\n            module->update();\n          } catch (const std::exception& e) {\n            spdlog::error(\"{}: {}\", ref, e.what());\n          }\n        });\n      } catch (const std::exception& e) {\n        spdlog::warn(\"module {}: {}\", name.asString(), e.what());\n      }\n    }\n  }\n}\n\nauto waybar::Bar::setupWidgets() -> void {\n  window.add(box_);\n\n  bool expand_left = config[\"expand-left\"].isBool() ? config[\"expand-left\"].asBool() : false;\n  bool expand_center = config[\"expand-center\"].isBool() ? config[\"expand-center\"].asBool() : false;\n  bool expand_right = config[\"expand-right\"].isBool() ? config[\"expand-right\"].asBool() : false;\n  bool no_center = config[\"no-center\"].isBool() ? config[\"no-center\"].asBool() : false;\n\n  box_.pack_start(left_, expand_left, expand_left);\n  if (!no_center) {\n    if (config[\"fixed-center\"].isBool() ? config[\"fixed-center\"].asBool() : true) {\n      box_.set_center_widget(center_);\n    } else {\n      box_.pack_start(center_, true, expand_center);\n    }\n  }\n  box_.pack_end(right_, expand_right, expand_right);\n\n  // Convert to button code for every module that is used.\n  setupAltFormatKeyForModuleList(\"modules-left\");\n  setupAltFormatKeyForModuleList(\"modules-right\");\n  setupAltFormatKeyForModuleList(\"modules-center\");\n\n  Factory factory(*this, config);\n  getModules(factory, \"modules-left\");\n  if (!no_center) {\n    getModules(factory, \"modules-center\");\n  }\n  getModules(factory, \"modules-right\");\n\n  for (auto const& module : modules_left_) {\n    left_.pack_start(*module, module->expandEnabled(), module->expandEnabled());\n  }\n\n  if (!no_center) {\n    for (auto const& module : modules_center_) {\n      center_.pack_start(*module, module->expandEnabled(), module->expandEnabled());\n    }\n  }\n\n  std::reverse(modules_right_.begin(), modules_right_.end());\n  for (auto const& module : modules_right_) {\n    right_.pack_end(*module, module->expandEnabled(), module->expandEnabled());\n  }\n}\n\nvoid waybar::Bar::onConfigure(GdkEventConfigure* ev) {\n  /*\n   * GTK wants new size for the window.\n   * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell\n   * code. This event handler only updates stored size of the window and prints some warnings.\n   *\n   * Note: forced resizing to a window smaller than required by GTK would not work with\n   * gtk-layer-shell.\n   */\n  if (orientation == Gtk::ORIENTATION_VERTICAL) {\n    if (width_ > 1 && ev->width > static_cast<int>(width_)) {\n      spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);\n    }\n  } else {\n    if (height_ > 1 && ev->height > static_cast<int>(height_)) {\n      spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);\n    }\n  }\n  width_ = ev->width;\n  height_ = ev->height;\n\n  configureGlobalOffset(ev->width, ev->height);\n  spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name);\n}\n\nvoid waybar::Bar::configureGlobalOffset(int width, int height) {\n  auto monitor_geometry = *output->monitor->property_geometry().get_value().gobj();\n  int x;\n  int y;\n  switch (position) {\n    case Gtk::POS_BOTTOM:\n      if (width + margins_.left + margins_.right >= monitor_geometry.width)\n        x = margins_.left;\n      else\n        x = (monitor_geometry.width - width) / 2;\n      y = monitor_geometry.height - height - margins_.bottom;\n      break;\n    case Gtk::POS_LEFT:\n      x = margins_.left;\n      if (height + margins_.top + margins_.bottom >= monitor_geometry.height)\n        y = margins_.top;\n      else\n        y = (monitor_geometry.height - height) / 2;\n      break;\n    case Gtk::POS_RIGHT:\n      x = monitor_geometry.width - width - margins_.right;\n      if (height + margins_.top + margins_.bottom >= monitor_geometry.height)\n        y = margins_.top;\n      else\n        y = (monitor_geometry.height - height) / 2;\n      break;\n    default: /* Gtk::POS_TOP */\n      if (width + margins_.left + margins_.right >= monitor_geometry.width)\n        x = margins_.left;\n      else\n        x = (monitor_geometry.width - width) / 2;\n      y = margins_.top;\n      break;\n  }\n\n  x_global = x + monitor_geometry.x;\n  y_global = y + monitor_geometry.y;\n}\n\nvoid waybar::Bar::onOutputGeometryChanged() {\n  configureGlobalOffset(window.get_width(), window.get_height());\n}\n"
  },
  {
    "path": "src/client.cpp",
    "content": "#include \"client.hpp\"\n\n#include <gtk-layer-shell.h>\n#include <spdlog/spdlog.h>\n\n#include <iostream>\n#include <utility>\n\n#include \"gtkmm/icontheme.h\"\n#include \"idle-inhibit-unstable-v1-client-protocol.h\"\n#include \"util/clara.hpp\"\n#include \"util/format.hpp\"\n\nwaybar::Client* waybar::Client::inst() {\n  static auto* c = new Client();\n  return c;\n}\n\nvoid waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name,\n                                  const char* interface, uint32_t version) {\n  auto* client = static_cast<Client*>(data);\n  if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&\n      version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {\n    client->xdg_output_manager = static_cast<struct zxdg_output_manager_v1*>(wl_registry_bind(\n        registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION));\n  } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {\n    client->idle_inhibit_manager = static_cast<struct zwp_idle_inhibit_manager_v1*>(\n        wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1));\n  }\n}\n\nvoid waybar::Client::handleGlobalRemove(void* data, struct wl_registry* /*registry*/,\n                                        uint32_t name) {\n  // Nothing here\n}\n\nvoid waybar::Client::handleOutput(struct waybar_output& output) {\n  static const struct zxdg_output_v1_listener xdgOutputListener = {\n      .logical_position = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {},\n      .logical_size = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {},\n      .done = &handleOutputDone,\n      .name = &handleOutputName,\n      .description = &handleOutputDescription,\n  };\n  // owned by output->monitor; no need to destroy\n  auto* wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());\n  output.xdg_output.reset(zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, wl_output));\n  zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);\n}\n\nstruct waybar::waybar_output& waybar::Client::getOutput(void* addr) {\n  auto it = std::find_if(outputs_.begin(), outputs_.end(),\n                         [&addr](const auto& output) { return &output == addr; });\n  if (it == outputs_.end()) {\n    throw std::runtime_error(\"Unable to find valid output\");\n  }\n  return *it;\n}\n\nstd::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output& output) {\n  return config.getOutputConfigs(output.name, output.identifier);\n}\n\nvoid waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_output*/) {\n  auto* client = waybar::Client::inst();\n  try {\n    auto& output = client->getOutput(data);\n    /**\n     * Multiple .done events may arrive in batch. In this case libwayland would queue\n     * xdg_output.destroy and dispatch all pending events, triggering this callback several times\n     * for the same output. .done events can also arrive after that for a scale or position changes.\n     * We wouldn't want to draw a duplicate bar for each such event either.\n     *\n     * All the properties we care about are immutable so it's safe to delete the xdg_output object\n     * on the first event and use the ptr value to check that the callback was already invoked.\n     */\n    if (output.xdg_output) {\n      output.xdg_output.reset();\n      spdlog::debug(\"Output detection done: {} ({})\", output.name, output.identifier);\n\n      auto configs = client->getOutputConfigs(output);\n      if (!configs.empty()) {\n        for (const auto& config : configs) {\n          client->bars.emplace_back(std::make_unique<Bar>(&output, config));\n        }\n      }\n    }\n  } catch (const std::exception& e) {\n    spdlog::warn(\"caught exception in zxdg_output_v1_listener::done: {}\", e.what());\n  }\n}\n\nvoid waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/,\n                                      const char* name) {\n  auto* client = waybar::Client::inst();\n  try {\n    auto& output = client->getOutput(data);\n    output.name = name;\n  } catch (const std::exception& e) {\n    spdlog::warn(\"caught exception in zxdg_output_v1_listener::name: {}\", e.what());\n  }\n}\n\nvoid waybar::Client::handleOutputDescription(void* data, struct zxdg_output_v1* /*xdg_output*/,\n                                             const char* description) {\n  auto* client = waybar::Client::inst();\n  try {\n    auto& output = client->getOutput(data);\n\n    // Description format: \"identifier (name)\"\n    auto s = std::string(description);\n    auto pos = s.find(\" (\");\n    output.identifier = pos != std::string::npos ? s.substr(0, pos) : s;\n  } catch (const std::exception& e) {\n    spdlog::warn(\"caught exception in zxdg_output_v1_listener::description: {}\", e.what());\n  }\n}\n\nvoid waybar::Client::handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor) {\n  auto& output = outputs_.emplace_back();\n  output.monitor = std::move(monitor);\n  handleOutput(output);\n}\n\nvoid waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {\n  spdlog::debug(\"Output removed: {} {}\", monitor->get_manufacturer().c_str(),\n                monitor->get_model().c_str());\n  /* This event can be triggered from wl_display_roundtrip called by GTK or our code.\n   * Defer destruction of bars for the output to the next iteration of the event loop to avoid\n   * deleting objects referenced by currently executed code.\n   */\n  Glib::signal_idle().connect_once(\n      sigc::bind(sigc::mem_fun(*this, &Client::handleDeferredMonitorRemoval), monitor),\n      Glib::PRIORITY_HIGH_IDLE);\n}\n\nvoid waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor) {\n  for (auto it = bars.begin(); it != bars.end();) {\n    if ((*it)->output->monitor == monitor) {\n      auto output_name = (*it)->output->name;\n      (*it)->window.hide();\n      gtk_app->remove_window((*it)->window);\n      it = bars.erase(it);\n      spdlog::info(\"Bar removed from output: {}\", output_name);\n    } else {\n      ++it;\n    }\n  }\n  outputs_.remove_if([&monitor](const auto& output) { return output.monitor == monitor; });\n}\n\nconst std::string waybar::Client::getStyle(const std::string& style,\n                                           std::optional<Appearance> appearance = std::nullopt) {\n  auto gtk_settings = Gtk::Settings::get_default();\n  std::optional<std::string> css_file;\n\n  if (style.empty()) {\n    std::vector<std::string> search_files;\n    switch (appearance.value_or(portal->getAppearance())) {\n      case waybar::Appearance::LIGHT:\n        search_files.emplace_back(\"style-light.css\");\n        gtk_settings->property_gtk_application_prefer_dark_theme() = false;\n        break;\n      case waybar::Appearance::DARK:\n        search_files.emplace_back(\"style-dark.css\");\n        gtk_settings->property_gtk_application_prefer_dark_theme() = true;\n        break;\n      case waybar::Appearance::UNKNOWN:\n        break;\n    }\n    search_files.emplace_back(\"style.css\");\n    css_file = Config::findConfigPath(search_files);\n  } else {\n    css_file = style;\n  }\n\n  if (!css_file) {\n    throw std::runtime_error(\"Missing required resource files\");\n  }\n\n  spdlog::info(\"Using CSS file {}\", css_file.value());\n  return css_file.value();\n};\n\nauto waybar::Client::setupCss(const std::string& css_file) -> void {\n  auto screen = Gdk::Screen::get_default();\n  if (!screen) {\n    throw std::runtime_error(\"No default screen\");\n  }\n\n  if (css_provider_) {\n    Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_);\n    css_provider_.reset();\n  }\n\n  css_provider_ = Gtk::CssProvider::create();\n  if (!css_provider_->load_from_path(css_file)) {\n    css_provider_.reset();\n    throw std::runtime_error(\"Can't open style file\");\n  }\n\n  Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,\n                                             GTK_STYLE_PROVIDER_PRIORITY_USER);\n}\n\nvoid waybar::Client::bindInterfaces() {\n  registry = wl_display_get_registry(wl_display);\n  static const struct wl_registry_listener registry_listener = {\n      .global = handleGlobal,\n      .global_remove = handleGlobalRemove,\n  };\n  wl_registry_add_listener(registry, &registry_listener, this);\n  wl_display_roundtrip(wl_display);\n\n  if (gtk_layer_is_supported() == 0) {\n    throw std::runtime_error(\"The Wayland compositor does not support wlr-layer-shell protocol\");\n  }\n\n  if (xdg_output_manager == nullptr) {\n    throw std::runtime_error(\"Failed to acquire required resources.\");\n  }\n\n  // Disconnect previous signal handlers to prevent duplicate handlers on reload\n  monitor_added_connection_.disconnect();\n  monitor_removed_connection_.disconnect();\n\n  // Clear stale outputs from previous run\n  outputs_.clear();\n\n  // add existing outputs and subscribe to updates\n  for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) {\n    auto monitor = gdk_display->get_monitor(i);\n    handleMonitorAdded(monitor);\n  }\n  monitor_added_connection_ = gdk_display->signal_monitor_added().connect(\n      sigc::mem_fun(*this, &Client::handleMonitorAdded));\n  monitor_removed_connection_ = gdk_display->signal_monitor_removed().connect(\n      sigc::mem_fun(*this, &Client::handleMonitorRemoved));\n}\n\nint waybar::Client::main(int argc, char* argv[]) {\n  bool show_help = false;\n  bool show_version = false;\n  std::string config_opt;\n  std::string style_opt;\n  std::string log_level;\n  auto cli = clara::detail::Help(show_help) |\n             clara::detail::Opt(show_version)[\"-v\"][\"--version\"](\"Show version\") |\n             clara::detail::Opt(config_opt, \"config\")[\"-c\"][\"--config\"](\"Config path\") |\n             clara::detail::Opt(style_opt, \"style\")[\"-s\"][\"--style\"](\"Style path\") |\n             clara::detail::Opt(\n                 log_level,\n                 \"trace|debug|info|warning|error|critical|off\")[\"-l\"][\"--log-level\"](\"Log level\") |\n             clara::detail::Opt(bar_id, \"id\")[\"-b\"][\"--bar\"](\"Bar id\");\n  auto res = cli.parse(clara::detail::Args(argc, argv));\n  if (!res) {\n    spdlog::error(\"Error in command line: {}\", res.errorMessage());\n    return 1;\n  }\n  if (show_help) {\n    std::cout << cli << '\\n';\n    return 0;\n  }\n  if (show_version) {\n    std::cout << \"Waybar v\" << VERSION << '\\n';\n    return 0;\n  }\n  if (!log_level.empty()) {\n    spdlog::set_level(spdlog::level::from_str(log_level));\n  }\n  gtk_app = Gtk::Application::create(argc, argv, \"fr.arouillard.waybar\",\n                                     Gio::APPLICATION_HANDLES_COMMAND_LINE);\n\n  // Initialize Waybars GTK resources with our custom icons\n  auto theme = Gtk::IconTheme::get_default();\n  theme->add_resource_path(\"/fr/arouillard/waybar/icons\");\n\n  gdk_display = Gdk::Display::get_default();\n  if (!gdk_display) {\n    throw std::runtime_error(\"Can't find display\");\n  }\n  if (!GDK_IS_WAYLAND_DISPLAY(gdk_display->gobj())) {\n    throw std::runtime_error(\"Bar need to run under Wayland\");\n  }\n  wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());\n  config.load(config_opt);\n  if (!portal) {\n    portal = std::make_unique<waybar::Portal>();\n  }\n  m_cssFile = getStyle(style_opt);\n  setupCss(m_cssFile);\n  m_cssReloadHelper = std::make_unique<CssReloadHelper>(m_cssFile, [&]() { setupCss(m_cssFile); });\n  portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) {\n    auto css_file = getStyle(style_opt, appearance);\n    setupCss(css_file);\n  });\n\n  auto m_config = config.getConfig();\n  if (m_config.isObject() && m_config[\"reload_style_on_change\"].asBool()) {\n    m_cssReloadHelper->monitorChanges();\n  } else if (m_config.isArray()) {\n    for (const auto& conf : m_config) {\n      if (conf[\"reload_style_on_change\"].asBool()) {\n        m_cssReloadHelper->monitorChanges();\n        break;\n      }\n    }\n  }\n\n  bindInterfaces();\n  gtk_app->hold();\n  gtk_app->run();\n  m_cssReloadHelper.reset();  // stop watching css file\n  bars.clear();\n  return 0;\n}\n\nvoid waybar::Client::reset() {\n  gtk_app->quit();\n  // delete signal handler for css changes\n  portal->signal_appearance_changed().clear();\n}\n"
  },
  {
    "path": "src/config.cpp",
    "content": "#include \"config.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <unistd.h>\n#ifndef __OpenBSD__\n#include <wordexp.h>\n#else\n#include <glob.h>\n#endif\n\n#include <filesystem>\n#include <fstream>\n#include <stdexcept>\n\n#include \"util/json.hpp\"\n\nnamespace fs = std::filesystem;\n\nnamespace waybar {\n\nconst std::vector<std::string> Config::CONFIG_DIRS = {\n    \"$XDG_CONFIG_HOME/waybar/\", \"$HOME/.config/waybar/\",   \"$HOME/waybar/\",\n    \"/etc/xdg/waybar/\",         SYSCONFDIR \"/xdg/waybar/\", \"./resources/\",\n};\n\nconst char* Config::CONFIG_PATH_ENV = \"WAYBAR_CONFIG_DIR\";\n\nstd::vector<std::string> Config::tryExpandPath(const std::string& base,\n                                               const std::string& filename) {\n  fs::path path;\n\n  if (!filename.empty()) {\n    path = fs::path(base) / fs::path(filename);\n  } else {\n    path = fs::path(base);\n  }\n\n  spdlog::debug(\"Try expanding: {}\", path.string());\n\n  std::vector<std::string> results;\n#ifndef __OpenBSD__\n  wordexp_t p;\n  if (wordexp(path.c_str(), &p, 0) == 0) {\n    for (size_t i = 0; i < p.we_wordc; i++) {\n      if (access(p.we_wordv[i], F_OK) == 0) {\n        results.emplace_back(p.we_wordv[i]);\n        spdlog::debug(\"Found config file: {}\", p.we_wordv[i]);\n      }\n    }\n    wordfree(&p);\n  }\n#else\n  glob_t p;\n  if (glob(path.c_str(), 0, NULL, &p) == 0) {\n    for (size_t i = 0; i < p.gl_pathc; i++) {\n      if (access(p.gl_pathv[i], F_OK) == 0) {\n        results.emplace_back(p.gl_pathv[i]);\n        spdlog::debug(\"Found config file: {}\", p.gl_pathv[i]);\n      }\n    }\n    globfree(&p);\n  }\n#endif\n\n  return results;\n}\n\nstd::optional<std::string> Config::findConfigPath(const std::vector<std::string>& names,\n                                                  const std::vector<std::string>& dirs) {\n  if (const char* dir = std::getenv(Config::CONFIG_PATH_ENV)) {\n    for (const auto& name : names) {\n      if (auto res = tryExpandPath(dir, name); !res.empty()) {\n        return res.front();\n      }\n    }\n  }\n\n  for (const auto& dir : dirs) {\n    for (const auto& name : names) {\n      if (auto res = tryExpandPath(dir, name); !res.empty()) {\n        return res.front();\n      }\n    }\n  }\n  return std::nullopt;\n}\n\nvoid Config::setupConfig(Json::Value& dst, const std::string& config_file, int depth) {\n  if (depth > 100) {\n    throw std::runtime_error(\"Aborting due to likely recursive include in config files\");\n  }\n  std::ifstream file(config_file);\n  if (!file.is_open()) {\n    throw std::runtime_error(\"Can't open config file\");\n  }\n  std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());\n  util::JsonParser parser;\n  Json::Value tmp_config = parser.parse(str);\n  if (tmp_config.isArray()) {\n    for (auto& config_part : tmp_config) {\n      resolveConfigIncludes(config_part, depth);\n    }\n  } else {\n    resolveConfigIncludes(tmp_config, depth);\n  }\n  mergeConfig(dst, tmp_config);\n}\n\nstd::vector<std::string> Config::findIncludePath(const std::string& name,\n                                                 const std::vector<std::string>& dirs) {\n  auto match1 = tryExpandPath(name, \"\");\n  if (!match1.empty()) {\n    return match1;\n  }\n  if (const char* dir = std::getenv(Config::CONFIG_PATH_ENV)) {\n    if (auto res = tryExpandPath(dir, name); !res.empty()) {\n      return res;\n    }\n  }\n  for (const auto& dir : dirs) {\n    if (auto res = tryExpandPath(dir, name); !res.empty()) {\n      return res;\n    }\n  }\n\n  return {};\n}\n\nvoid Config::resolveConfigIncludes(Json::Value& config, int depth) {\n  Json::Value includes = config[\"include\"];\n  if (includes.isArray()) {\n    for (const auto& include : includes) {\n      spdlog::info(\"Including resource file: {}\", include.asString());\n      auto matches = findIncludePath(include.asString());\n      if (!matches.empty()) {\n        for (const auto& match : matches) {\n          setupConfig(config, match, depth + 1);\n        }\n      } else {\n        spdlog::warn(\"Unable to find resource file: {}\", include.asString());\n      }\n    }\n  } else if (includes.isString()) {\n    spdlog::info(\"Including resource file: {}\", includes.asString());\n    auto matches = findIncludePath(includes.asString());\n    if (!matches.empty()) {\n      for (const auto& match : matches) {\n        setupConfig(config, match, depth + 1);\n      }\n    } else {\n      spdlog::warn(\"Unable to find resource file: {}\", includes.asString());\n    }\n  }\n}\n\nvoid Config::mergeConfig(Json::Value& a_config_, Json::Value& b_config_) {\n  if (!a_config_) {\n    // For the first config\n    a_config_ = b_config_;\n  } else if (a_config_.isObject() && b_config_.isObject()) {\n    for (const auto& key : b_config_.getMemberNames()) {\n      // [] creates key with default value. Use `get` to avoid that.\n      if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&\n          b_config_[key].isObject()) {\n        mergeConfig(a_config_[key], b_config_[key]);\n      } else if (!a_config_.isMember(key)) {\n        // do not allow overriding value set by top or previously included config\n        a_config_[key] = b_config_[key];\n      } else {\n        spdlog::trace(\"Option {} is already set; ignoring value {}\", key, b_config_[key]);\n      }\n    }\n  } else {\n    spdlog::error(\"Cannot merge config, conflicting or invalid JSON types\");\n  }\n}\nbool isValidOutput(const Json::Value& config, const std::string& name,\n                   const std::string& identifier) {\n  if (config[\"output\"].isArray()) {\n    for (auto const& output_conf : config[\"output\"]) {\n      if (output_conf.isString()) {\n        auto config_output = output_conf.asString();\n        if (config_output.substr(0, 1) == \"!\") {\n          if (config_output.substr(1) == name || config_output.substr(1) == identifier) {\n            return false;\n          }\n\n          continue;\n        }\n        if (config_output == name || config_output == identifier) {\n          return true;\n        }\n        if (config_output.substr(0, 1) == \"*\") {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  if (config[\"output\"].isString()) {\n    auto config_output = config[\"output\"].asString();\n    if (!config_output.empty()) {\n      if (config_output.substr(0, 1) == \"!\") {\n        return config_output.substr(1) != name && config_output.substr(1) != identifier;\n      }\n      return config_output == name || config_output == identifier;\n    }\n  }\n\n  return true;\n}\n\nvoid Config::load(const std::string& config) {\n  auto file = config.empty() ? findConfigPath({\"config\", \"config.jsonc\"}) : config;\n  if (!file) {\n    throw std::runtime_error(\"Missing required resource files\");\n  }\n  config_file_ = file.value();\n  spdlog::info(\"Using configuration file {}\", config_file_);\n  config_ = Json::Value();\n  setupConfig(config_, config_file_, 0);\n}\n\nstd::vector<Json::Value> Config::getOutputConfigs(const std::string& name,\n                                                  const std::string& identifier) {\n  std::vector<Json::Value> configs;\n  if (config_.isArray()) {\n    for (auto const& config : config_) {\n      if (config.isObject() && isValidOutput(config, name, identifier)) {\n        configs.push_back(config);\n      }\n    }\n  } else if (isValidOutput(config_, name, identifier)) {\n    configs.push_back(config_);\n  }\n  return configs;\n}\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/factory.cpp",
    "content": "#include \"factory.hpp\"\n\n#include \"bar.hpp\"\n\n#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE)\n#include \"modules/clock.hpp\"\n#else\n#include \"modules/simpleclock.hpp\"\n#endif\n#ifdef HAVE_SWAY\n#include \"modules/sway/language.hpp\"\n#include \"modules/sway/mode.hpp\"\n#include \"modules/sway/scratchpad.hpp\"\n#include \"modules/sway/window.hpp\"\n#include \"modules/sway/workspaces.hpp\"\n#endif\n#ifdef HAVE_WLR_TASKBAR\n#include \"modules/wlr/taskbar.hpp\"\n#endif\n#ifdef HAVE_EXT_WORKSPACES\n#include \"modules/ext/workspace_manager.hpp\"\n#endif\n#ifdef HAVE_RIVER\n#include \"modules/river/layout.hpp\"\n#include \"modules/river/mode.hpp\"\n#include \"modules/river/tags.hpp\"\n#include \"modules/river/window.hpp\"\n#endif\n#ifdef HAVE_DWL\n#include \"modules/dwl/tags.hpp\"\n#include \"modules/dwl/window.hpp\"\n#endif\n#ifdef HAVE_HYPRLAND\n#include \"modules/hyprland/language.hpp\"\n#include \"modules/hyprland/submap.hpp\"\n#include \"modules/hyprland/window.hpp\"\n#include \"modules/hyprland/windowcount.hpp\"\n#include \"modules/hyprland/workspaces.hpp\"\n#endif\n#ifdef HAVE_NIRI\n#include \"modules/niri/language.hpp\"\n#include \"modules/niri/window.hpp\"\n#include \"modules/niri/workspaces.hpp\"\n#endif\n#ifdef HAVE_WAYFIRE\n#include \"modules/wayfire/window.hpp\"\n#include \"modules/wayfire/workspaces.hpp\"\n#endif\n#if defined(__FreeBSD__) || defined(__linux__)\n#include \"modules/battery.hpp\"\n#endif\n#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)\n#include \"modules/cpu.hpp\"\n#include \"modules/cpu_frequency.hpp\"\n#include \"modules/cpu_usage.hpp\"\n#include \"modules/load.hpp\"\n#endif\n#include \"modules/idle_inhibitor.hpp\"\n#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD)\n#include \"modules/memory.hpp\"\n#endif\n#include \"modules/disk.hpp\"\n#ifdef HAVE_DBUSMENU\n#include \"modules/sni/tray.hpp\"\n#endif\n#ifdef HAVE_MPRIS\n#include \"modules/mpris/mpris.hpp\"\n#endif\n#ifdef HAVE_LIBNL\n#include \"modules/network.hpp\"\n#endif\n#ifdef HAVE_LIBUDEV\n#include \"modules/backlight.hpp\"\n#include \"modules/backlight_slider.hpp\"\n#endif\n#ifdef HAVE_LIBEVDEV\n#include \"modules/keyboard_state.hpp\"\n#endif\n#ifdef HAVE_GAMEMODE\n#include \"modules/gamemode.hpp\"\n#endif\n#ifdef HAVE_UPOWER\n#include \"modules/upower.hpp\"\n#endif\n#ifdef HAVE_PIPEWIRE\n#include \"modules/privacy/privacy.hpp\"\n#endif\n#ifdef HAVE_LIBPULSE\n#include \"modules/pulseaudio.hpp\"\n#include \"modules/pulseaudio_slider.hpp\"\n#endif\n#ifdef HAVE_LIBMPDCLIENT\n#include \"modules/mpd/mpd.hpp\"\n#endif\n#ifdef HAVE_LIBSNDIO\n#include \"modules/sndio.hpp\"\n#endif\n#if defined(__linux__)\n#include \"modules/bluetooth.hpp\"\n#include \"modules/power_profiles_daemon.hpp\"\n#endif\n#ifdef HAVE_LOGIND_INHIBITOR\n#include \"modules/inhibitor.hpp\"\n#endif\n#ifdef HAVE_LIBJACK\n#include \"modules/jack.hpp\"\n#endif\n#ifdef HAVE_LIBWIREPLUMBER\n#include \"modules/wireplumber.hpp\"\n#endif\n#ifdef HAVE_SYSTEMD_MONITOR\n#include \"modules/systemd_failed_units.hpp\"\n#endif\n#ifdef HAVE_LIBGPS\n#include \"modules/gps.hpp\"\n#endif\n#include \"modules/cava/cava_frontend.hpp\"\n#include \"modules/cffi.hpp\"\n#include \"modules/custom.hpp\"\n#include \"modules/image.hpp\"\n#include \"modules/temperature.hpp\"\n#include \"modules/user.hpp\"\n\nwaybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {}\n\nwaybar::AModule* waybar::Factory::makeModule(const std::string& name,\n                                             const std::string& pos) const {\n  try {\n    auto hash_pos = name.find('#');\n    auto ref = name.substr(0, hash_pos);\n    auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : \"\";\n#if defined(__FreeBSD__) || defined(__linux__)\n    if (ref == \"battery\") {\n      return new waybar::modules::Battery(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_GAMEMODE\n    if (ref == \"gamemode\") {\n      return new waybar::modules::Gamemode(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_UPOWER\n    if (ref == \"upower\") {\n      return new waybar::modules::UPower(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_PIPEWIRE\n    if (ref == \"privacy\") {\n      return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos);\n    }\n#endif\n#ifdef HAVE_MPRIS\n    if (ref == \"mpris\") {\n      return new waybar::modules::mpris::Mpris(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_SWAY\n    if (ref == \"sway/mode\") {\n      return new waybar::modules::sway::Mode(id, config_[name]);\n    }\n    if (ref == \"sway/workspaces\") {\n      return new waybar::modules::sway::Workspaces(id, bar_, config_[name]);\n    }\n    if (ref == \"sway/window\") {\n      return new waybar::modules::sway::Window(id, bar_, config_[name]);\n    }\n    if (ref == \"sway/language\") {\n      return new waybar::modules::sway::Language(id, config_[name]);\n    }\n    if (ref == \"sway/scratchpad\") {\n      return new waybar::modules::sway::Scratchpad(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_WLR_TASKBAR\n    if (ref == \"wlr/taskbar\") {\n      return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_EXT_WORKSPACES\n    if (ref == \"ext/workspaces\") {\n      return new waybar::modules::ext::WorkspaceManager(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_RIVER\n    if (ref == \"river/mode\") {\n      return new waybar::modules::river::Mode(id, bar_, config_[name]);\n    }\n    if (ref == \"river/tags\") {\n      return new waybar::modules::river::Tags(id, bar_, config_[name]);\n    }\n    if (ref == \"river/window\") {\n      return new waybar::modules::river::Window(id, bar_, config_[name]);\n    }\n    if (ref == \"river/layout\") {\n      return new waybar::modules::river::Layout(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_DWL\n    if (ref == \"dwl/tags\") {\n      return new waybar::modules::dwl::Tags(id, bar_, config_[name]);\n    }\n    if (ref == \"dwl/window\") {\n      return new waybar::modules::dwl::Window(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_HYPRLAND\n    if (ref == \"hyprland/window\") {\n      return new waybar::modules::hyprland::Window(id, bar_, config_[name]);\n    }\n    if (ref == \"hyprland/windowcount\") {\n      return new waybar::modules::hyprland::WindowCount(id, bar_, config_[name]);\n    }\n    if (ref == \"hyprland/language\") {\n      return new waybar::modules::hyprland::Language(id, bar_, config_[name]);\n    }\n    if (ref == \"hyprland/submap\") {\n      return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);\n    }\n    if (ref == \"hyprland/workspaces\") {\n      return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_NIRI\n    if (ref == \"niri/language\") {\n      return new waybar::modules::niri::Language(id, bar_, config_[name]);\n    }\n    if (ref == \"niri/window\") {\n      return new waybar::modules::niri::Window(id, bar_, config_[name]);\n    }\n    if (ref == \"niri/workspaces\") {\n      return new waybar::modules::niri::Workspaces(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_WAYFIRE\n    if (ref == \"wayfire/window\") {\n      return new waybar::modules::wayfire::Window(id, bar_, config_[name]);\n    }\n    if (ref == \"wayfire/workspaces\") {\n      return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]);\n    }\n#endif\n    if (ref == \"idle_inhibitor\") {\n      return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);\n    }\n#if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD)\n    if (ref == \"memory\") {\n      return new waybar::modules::Memory(id, config_[name]);\n    }\n#endif\n#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)\n    if (ref == \"cpu\") {\n      return new waybar::modules::Cpu(id, config_[name]);\n    }\n#if defined(HAVE_CPU_LINUX)\n    if (ref == \"cpu_frequency\") {\n      return new waybar::modules::CpuFrequency(id, config_[name]);\n    }\n#endif\n    if (ref == \"cpu_usage\") {\n      return new waybar::modules::CpuUsage(id, config_[name]);\n    }\n    if (ref == \"load\") {\n      return new waybar::modules::Load(id, config_[name]);\n    }\n#endif\n    if (ref == \"clock\") {\n      return new waybar::modules::Clock(id, config_[name]);\n    }\n    if (ref == \"user\") {\n      return new waybar::modules::User(id, config_[name]);\n    }\n    if (ref == \"disk\") {\n      return new waybar::modules::Disk(id, config_[name]);\n    }\n    if (ref == \"image\") {\n      return new waybar::modules::Image(id, config_[name]);\n    }\n#ifdef HAVE_DBUSMENU\n    if (ref == \"tray\") {\n      return new waybar::modules::SNI::Tray(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBNL\n    if (ref == \"network\") {\n      return new waybar::modules::Network(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBUDEV\n    if (ref == \"backlight\") {\n      return new waybar::modules::Backlight(id, config_[name]);\n    }\n    if (ref == \"backlight/slider\") {\n      return new waybar::modules::BacklightSlider(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBEVDEV\n    if (ref == \"keyboard-state\") {\n      return new waybar::modules::KeyboardState(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBPULSE\n    if (ref == \"pulseaudio\") {\n      return new waybar::modules::Pulseaudio(id, config_[name]);\n    }\n    if (ref == \"pulseaudio/slider\") {\n      return new waybar::modules::PulseaudioSlider(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBMPDCLIENT\n    if (ref == \"mpd\") {\n      return new waybar::modules::MPD(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBSNDIO\n    if (ref == \"sndio\") {\n      return new waybar::modules::Sndio(id, config_[name]);\n    }\n#endif\n#if defined(__linux__)\n    if (ref == \"bluetooth\") {\n      return new waybar::modules::Bluetooth(id, config_[name]);\n    }\n    if (ref == \"power-profiles-daemon\") {\n      return new waybar::modules::PowerProfilesDaemon(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LOGIND_INHIBITOR\n    if (ref == \"inhibitor\") {\n      return new waybar::modules::Inhibitor(id, bar_, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBJACK\n    if (ref == \"jack\") {\n      return new waybar::modules::JACK(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBWIREPLUMBER\n    if (ref == \"wireplumber\") {\n      return new waybar::modules::Wireplumber(id, config_[name]);\n    }\n#endif\n    if (ref == \"cava\") {\n      return waybar::modules::cava::getModule(id, config_[name]);\n    }\n#ifdef HAVE_SYSTEMD_MONITOR\n    if (ref == \"systemd-failed-units\") {\n      return new waybar::modules::SystemdFailedUnits(id, config_[name]);\n    }\n#endif\n#ifdef HAVE_LIBGPS\n    if (ref == \"gps\") {\n      return new waybar::modules::Gps(id, config_[name]);\n    }\n#endif\n    if (ref == \"temperature\") {\n      return new waybar::modules::Temperature(id, config_[name]);\n    }\n    if (ref.compare(0, 7, \"custom/\") == 0 && ref.size() > 7) {\n      return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name);\n    }\n    if (ref.compare(0, 5, \"cffi/\") == 0 && ref.size() > 5) {\n      return new waybar::modules::CFFI(ref.substr(5), id, config_[name]);\n    }\n  } catch (const std::exception& e) {\n    auto err = fmt::format(\"Disabling module \\\"{}\\\", {}\", name, e.what());\n    throw std::runtime_error(err);\n  } catch (...) {\n    auto err = fmt::format(\"Disabling module \\\"{}\\\", Unknown reason\", name);\n    throw std::runtime_error(err);\n  }\n  throw std::runtime_error(\"Unknown module: \" + name);\n}\n"
  },
  {
    "path": "src/group.cpp",
    "content": "#include \"group.hpp\"\n\n#include <fmt/format.h>\n\n#include <util/command.hpp>\n\n#include \"gtkmm/enums.h\"\n#include \"gtkmm/widget.h\"\n\nnamespace waybar {\n\nGtk::RevealerTransitionType getPreferredTransitionType(bool is_vertical) {\n  /* The transition direction of a drawer is not actually determined by the transition type,\n   * but rather by the order of 'box' and 'revealer_box':\n   *   'REVEALER_TRANSITION_TYPE_SLIDE_LEFT' and 'REVEALER_TRANSITION_TYPE_SLIDE_RIGHT'\n   *   will result in the same thing.\n   * However: we still need to differentiate between vertical and horizontal transition types.\n   */\n\n  if (is_vertical) {\n    return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_UP;\n  }\n\n  return Gtk::RevealerTransitionType::REVEALER_TRANSITION_TYPE_SLIDE_LEFT;\n}\n\nGroup::Group(const std::string& name, const std::string& id, const Json::Value& config,\n             bool vertical)\n    : AModule(config, name, id, true, false),\n      box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},\n      revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {\n  box.set_name(name_);\n  if (!id.empty()) {\n    box.get_style_context()->add_class(id);\n  }\n\n  // default orientation: orthogonal to parent\n  auto orientation =\n      config_[\"orientation\"].empty() ? \"orthogonal\" : config_[\"orientation\"].asString();\n  if (orientation == \"inherit\") {\n    // keep orientation passed\n  } else if (orientation == \"orthogonal\") {\n    box.set_orientation(vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL);\n  } else if (orientation == \"vertical\") {\n    box.set_orientation(Gtk::ORIENTATION_VERTICAL);\n  } else if (orientation == \"horizontal\") {\n    box.set_orientation(Gtk::ORIENTATION_HORIZONTAL);\n  } else {\n    throw std::runtime_error(\"Invalid orientation value: \" + orientation);\n  }\n\n  if (config_[\"drawer\"].isObject()) {\n    is_drawer = true;\n\n    const auto& drawer_config = config_[\"drawer\"];\n    const int transition_duration =\n        (drawer_config[\"transition-duration\"].isInt() ? drawer_config[\"transition-duration\"].asInt()\n                                                      : 500);\n    add_class_to_drawer_children =\n        (drawer_config[\"children-class\"].isString() ? drawer_config[\"children-class\"].asString()\n                                                    : \"drawer-child\");\n    const bool left_to_right = (drawer_config[\"transition-left-to-right\"].isBool()\n                                    ? drawer_config[\"transition-left-to-right\"].asBool()\n                                    : true);\n    click_to_reveal = drawer_config[\"click-to-reveal\"].asBool();\n\n    const bool start_expanded =\n        (drawer_config[\"start-expanded\"].isBool() ? drawer_config[\"start-expanded\"].asBool()\n                                                  : false);\n\n    auto transition_type = getPreferredTransitionType(vertical);\n\n    revealer.set_transition_type(transition_type);\n    revealer.set_transition_duration(transition_duration);\n    revealer.set_reveal_child(start_expanded);\n\n    if (start_expanded) {\n      box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n    }\n\n    revealer.get_style_context()->add_class(\"drawer\");\n\n    revealer.add(revealer_box);\n\n    if (left_to_right) {\n      box.pack_end(revealer);\n    } else {\n      box.pack_start(revealer);\n    }\n  }\n\n  event_box_.add(box);\n}\n\nvoid Group::show_group() {\n  box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  revealer.set_reveal_child(true);\n}\n\nvoid Group::hide_group() {\n  box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  revealer.set_reveal_child(false);\n}\n\nbool Group::handleMouseEnter(GdkEventCrossing* const& e) {\n  if (!click_to_reveal) {\n    show_group();\n  }\n  return false;\n}\n\nbool Group::handleMouseLeave(GdkEventCrossing* const& e) {\n  if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) {\n    hide_group();\n  }\n  return false;\n}\n\nbool Group::handleToggle(GdkEventButton* const& e) {\n  if (!click_to_reveal || e->button != 1) {\n    return false;\n  }\n  if ((box.get_state_flags() & Gtk::StateFlags::STATE_FLAG_PRELIGHT) != 0U) {\n    hide_group();\n  } else {\n    show_group();\n  }\n  return true;\n}\n\nauto Group::update() -> void {\n  // noop\n}\n\nbool Group::handleScroll(GdkEventScroll* e) {\n  // no scroll.\n  return true;\n}\n\nGtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; }\n\nvoid Group::addWidget(Gtk::Widget& widget) {\n  getBox().pack_start(widget, false, false);\n\n  if (is_drawer && !is_first_widget) {\n    widget.get_style_context()->add_class(add_class_to_drawer_children);\n  }\n\n  is_first_widget = false;\n}\n\nGroup::operator Gtk::Widget&() { return event_box_; }\n\n}  // namespace waybar\n"
  },
  {
    "path": "src/main.cpp",
    "content": "#include <fcntl.h>\n#include <spdlog/spdlog.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n\n#include <csignal>\n#include <list>\n#include <mutex>\n\n#include \"bar.hpp\"\n#include \"client.hpp\"\n#include \"util/SafeSignal.hpp\"\n\nstd::mutex reap_mtx;\nstd::list<pid_t> reap;\n\nstatic int signal_pipe_write_fd;\n\n// Write a single signal to `signal_pipe_write_fd`.\n// This function is set as a signal handler, so it must be async-signal-safe.\nstatic void writeSignalToPipe(int signum) {\n  ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int));\n\n  // There's not much we can safely do inside of a signal handler.\n  // Let's just ignore any errors.\n  (void)amt;\n}\n\n// This initializes `signal_pipe_write_fd`, and sets up signal handlers.\n//\n// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`,\n// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received\n// to `signal_handler`.\nstatic void catchSignals(waybar::SafeSignal<int>& signal_handler) {\n  int fd[2];\n  if (pipe(fd) != 0) {\n    spdlog::error(\"Failed to create signal pipe: {}\", strerror(errno));\n    return;\n  }\n\n  int signal_pipe_read_fd = fd[0];\n  signal_pipe_write_fd = fd[1];\n\n  // This pipe should be able to buffer ~thousands of signals. If it fills up,\n  // we'll drop signals instead of blocking.\n\n  // We can't allow the write end to block because we'll be writing to it in a\n  // signal handler, which could interrupt the loop that's reading from it and\n  // deadlock.\n\n  fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK);\n\n  std::signal(SIGUSR1, writeSignalToPipe);\n  std::signal(SIGUSR2, writeSignalToPipe);\n  std::signal(SIGINT, writeSignalToPipe);\n  std::signal(SIGCHLD, writeSignalToPipe);\n\n  for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {\n    std::signal(sig, writeSignalToPipe);\n  }\n\n  while (true) {\n    int signum;\n    ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int));\n    if (amt < 0) {\n      spdlog::error(\"read from signal pipe failed with error {}, closing thread\", strerror(errno));\n      break;\n    }\n\n    if (amt != sizeof(int)) {\n      continue;\n    }\n\n    signal_handler.emit(signum);\n  }\n}\n\nwaybar::util::KillSignalAction getActionForBar(waybar::Bar* bar, int signal) {\n  switch (signal) {\n    case SIGUSR1:\n      return bar->getOnSigusr1Action();\n    case SIGUSR2:\n      return bar->getOnSigusr2Action();\n    default:\n      return waybar::util::KillSignalAction::NOOP;\n  }\n}\n\nvoid handleUserSignal(int signal, bool& reload) {\n  int i = 0;\n  for (auto& bar : waybar::Client::inst()->bars) {\n    switch (getActionForBar(bar.get(), signal)) {\n      case waybar::util::KillSignalAction::HIDE:\n        spdlog::debug(\"Visibility 'hide' for bar \", i);\n        bar->hide();\n        break;\n      case waybar::util::KillSignalAction::SHOW:\n        spdlog::debug(\"Visibility 'show' for bar \", i);\n        bar->show();\n        break;\n      case waybar::util::KillSignalAction::TOGGLE:\n        spdlog::debug(\"Visibility 'toggle' for bar \", i);\n        bar->toggle();\n        break;\n      case waybar::util::KillSignalAction::RELOAD:\n        spdlog::info(\"Reloading...\");\n        reload = true;\n        waybar::Client::inst()->reset();\n        return;\n      case waybar::util::KillSignalAction::NOOP:\n        break;\n    }\n    i++;\n  }\n}\n\n// Must be called on the main thread.\n//\n// If this signal should restart or close the bar, this function will write\n// `true` or `false`, respectively, into `reload`.\nstatic void handleSignalMainThread(int signum, bool& reload) {\n  if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) {\n    for (auto& bar : waybar::Client::inst()->bars) {\n      bar->handleSignal(signum);\n    }\n    return;\n  }\n\n  switch (signum) {\n    case SIGUSR1:\n      handleUserSignal(SIGUSR1, reload);\n      break;\n    case SIGUSR2:\n      handleUserSignal(SIGUSR2, reload);\n      break;\n    case SIGINT:\n      spdlog::info(\"Quitting.\");\n      reload = false;\n      waybar::Client::inst()->reset();\n      break;\n    case SIGCHLD:\n      spdlog::debug(\"Received SIGCHLD in signalThread\");\n      {\n        std::lock_guard<std::mutex> lock(reap_mtx);\n        for (auto it = reap.begin(); it != reap.end();) {\n          if (waitpid(*it, nullptr, WNOHANG) == *it) {\n            spdlog::debug(\"Reaped child with PID: {}\", *it);\n            it = reap.erase(it);\n          } else {\n            ++it;\n          }\n        }\n      }\n      break;\n    default:\n      spdlog::debug(\"Received signal with number {}, but not handling\", signum);\n      break;\n  }\n}\n\nint main(int argc, char* argv[]) {\n  try {\n    auto* client = waybar::Client::inst();\n\n    bool reload;\n\n    waybar::SafeSignal<int> posix_signal_received;\n    posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); });\n\n    std::thread signal_thread([&]() { catchSignals(posix_signal_received); });\n\n    // Every `std::thread` must be joined or detached.\n    // This thread should run forever, so detach it.\n    signal_thread.detach();\n\n    auto ret = 0;\n    do {\n      reload = false;\n      ret = client->main(argc, argv);\n    } while (reload);\n\n    std::signal(SIGUSR1, SIG_IGN);\n    std::signal(SIGUSR2, SIG_IGN);\n    std::signal(SIGINT, SIG_IGN);\n\n    delete client;\n    return ret;\n  } catch (const std::exception& e) {\n    spdlog::error(\"{}\", e.what());\n    return 1;\n  } catch (const Glib::Exception& e) {\n    spdlog::error(\"{}\", static_cast<std::string>(e.what()));\n    return 1;\n  }\n}\n"
  },
  {
    "path": "src/modules/backlight.cpp",
    "content": "#include \"modules/backlight.hpp\"\n\n#include <fmt/format.h>\n#include <libudev.h>\n#include <spdlog/spdlog.h>\n#include <sys/epoll.h>\n#include <unistd.h>\n\n#include <algorithm>\n#include <chrono>\n#include <memory>\n\n#include \"util/backend_common.hpp\"\n#include \"util/backlight_backend.hpp\"\n\nwaybar::modules::Backlight::Backlight(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"backlight\", id, \"{percent}%\", 2),\n      preferred_device_(config[\"device\"].isString() ? config[\"device\"].asString() : \"\"),\n      backend(interval_, [this] { dp.emit(); }) {\n  dp.emit();\n\n  // Set up scroll handler\n  event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n  event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));\n}\n\nauto waybar::modules::Backlight::update() -> void {\n  GET_BEST_DEVICE(best, backend, preferred_device_);\n\n  const auto previous_best_device = backend.get_previous_best_device();\n  if (best != nullptr) {\n    if (previous_best_device != nullptr && *previous_best_device == *best &&\n        !previous_format_.empty() && previous_format_ == format_) {\n      return;\n    }\n\n    if (best->get_powered()) {\n      event_box_.show();\n      const uint8_t percent =\n          best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());\n\n      // Get the state and apply state-specific format if available\n      auto state = getState(percent);\n      std::string current_format = format_;\n      if (!state.empty()) {\n        std::string state_format_name = \"format-\" + state;\n        if (config_[state_format_name].isString()) {\n          current_format = config_[state_format_name].asString();\n        }\n      }\n\n      std::string desc = fmt::format(fmt::runtime(current_format), fmt::arg(\"percent\", percent),\n                                     fmt::arg(\"icon\", getIcon(percent)));\n      label_.set_markup(desc);\n      if (tooltipEnabled()) {\n        std::string tooltip_format;\n        if (config_[\"tooltip-format\"].isString()) {\n          tooltip_format = config_[\"tooltip-format\"].asString();\n        }\n        if (!tooltip_format.empty()) {\n          label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),\n                                                fmt::arg(\"percent\", percent),\n                                                fmt::arg(\"icon\", getIcon(percent))));\n        } else {\n          label_.set_tooltip_markup(desc);\n        }\n      }\n    } else {\n      event_box_.hide();\n    }\n  } else {\n    if (previous_best_device == nullptr) {\n      return;\n    }\n    label_.set_markup(\"\");\n  }\n  backend.set_previous_best_device(best);\n  previous_format_ = format_;\n  ALabel::update();\n}\n\nbool waybar::modules::Backlight::handleScroll(GdkEventScroll* e) {\n  // Check if the user has set a custom command for scrolling\n  if (config_[\"on-scroll-up\"].isString() || config_[\"on-scroll-down\"].isString()) {\n    return AModule::handleScroll(e);\n  }\n\n  // Fail fast if the proxy could not be initialized\n  if (!backend.is_login_proxy_initialized()) {\n    return true;\n  }\n\n  // Check scroll direction\n  auto dir = AModule::getScrollDir(e);\n\n  // No worries, it will always be set because of the switch below. This is purely to suppress a\n  // warning\n  util::ChangeType ct = util::ChangeType::Increase;\n\n  switch (dir) {\n    case SCROLL_DIR::UP:\n      [[fallthrough]];\n    case SCROLL_DIR::RIGHT:\n      ct = util::ChangeType::Increase;\n      break;\n\n    case SCROLL_DIR::DOWN:\n      [[fallthrough]];\n    case SCROLL_DIR::LEFT:\n      ct = util::ChangeType::Decrease;\n      break;\n\n    case SCROLL_DIR::NONE:\n      return true;\n      break;\n  }\n\n  // Get scroll step\n  double step = 1;\n\n  if (config_[\"scroll-step\"].isDouble()) {\n    step = config_[\"scroll-step\"].asDouble();\n  }\n\n  double min_brightness = 0;\n  if (config_[\"min-brightness\"].isDouble()) {\n    min_brightness = config_[\"min-brightness\"].asDouble();\n  }\n  if (backend.get_scaled_brightness(preferred_device_) <= min_brightness &&\n      ct == util::ChangeType::Decrease) {\n    return true;\n  }\n  backend.set_brightness(preferred_device_, ct, step);\n\n  return true;\n}\n"
  },
  {
    "path": "src/modules/backlight_slider.cpp",
    "content": "#include \"modules/backlight_slider.hpp\"\n\n#include \"ASlider.hpp\"\n\nnamespace waybar::modules {\n\nBacklightSlider::BacklightSlider(const std::string& id, const Json::Value& config)\n    : ASlider(config, \"backlight-slider\", id),\n      interval_(config_[\"interval\"].isUInt() ? config_[\"interval\"].asUInt() : 1000),\n      preferred_device_(config[\"device\"].isString() ? config[\"device\"].asString() : \"\"),\n      backend(interval_, [this] { this->dp.emit(); }) {}\n\nvoid BacklightSlider::update() {\n  uint16_t brightness = backend.get_scaled_brightness(preferred_device_);\n  scale_.set_value(brightness);\n}\n\nvoid BacklightSlider::onValueChanged() {\n  auto brightness = scale_.get_value();\n  backend.set_scaled_brightness(preferred_device_, brightness);\n}\n\n}  // namespace waybar::modules"
  },
  {
    "path": "src/modules/battery.cpp",
    "content": "#include \"modules/battery.hpp\"\n\n#include <algorithm>\n#include <cctype>\n\n#include \"util/command.hpp\"\n#if defined(__FreeBSD__)\n#include <sys/sysctl.h>\n#endif\n#include <libudev.h>\n#include <poll.h>\n#include <spdlog/spdlog.h>\n#include <sys/signalfd.h>\n\nwaybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)\n    : ALabel(config, \"battery\", id, \"{capacity}%\", 60), last_event_(\"\"), bar_(bar) {\n#if defined(__linux__)\n  battery_watch_fd_ = inotify_init1(IN_CLOEXEC);\n  if (battery_watch_fd_ == -1) {\n    throw std::runtime_error(\"Unable to listen batteries.\");\n  }\n  udev_ = std::unique_ptr<udev, util::UdevDeleter>(udev_new());\n  if (udev_ == nullptr) {\n    throw std::runtime_error(\"udev_new failed\");\n  }\n  mon_ = std::unique_ptr<udev_monitor, util::UdevMonitorDeleter>(\n      udev_monitor_new_from_netlink(udev_.get(), \"kernel\"));\n  if (mon_ == nullptr) {\n    throw std::runtime_error(\"udev monitor new failed\");\n  }\n  if (udev_monitor_filter_add_match_subsystem_devtype(mon_.get(), \"power_supply\", nullptr) < 0) {\n    throw std::runtime_error(\"udev failed to add monitor filter\");\n  }\n  udev_monitor_enable_receiving(mon_.get());\n\n  if (config_[\"weighted-average\"].isBool()) weightedAverage_ = config_[\"weighted-average\"].asBool();\n#endif\n  spdlog::debug(\"battery: worker interval is {}\", interval_.count());\n  worker();\n}\n\nwaybar::modules::Battery::~Battery() {\n#if defined(__linux__)\n  std::lock_guard<std::mutex> guard(battery_list_mutex_);\n\n  for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) {\n    ++next_it;\n    auto watch_id = (*it).second;\n    if (watch_id >= 0) {\n      inotify_rm_watch(battery_watch_fd_, watch_id);\n    }\n    batteries_.erase(it);\n  }\n  close(battery_watch_fd_);\n#endif\n}\n\nvoid waybar::modules::Battery::worker() {\n#if defined(__FreeBSD__)\n  thread_timer_ = [this] {\n    dp.emit();\n    thread_timer_.sleep_for(interval_);\n  };\n#else\n  thread_timer_ = [this] {\n    // Make sure we eventually update the list of batteries even if we miss an\n    // inotify event for some reason\n    refreshBatteries();\n    dp.emit();\n    thread_timer_.sleep_for(interval_);\n  };\n  thread_ = [this] {\n    struct inotify_event event = {0};\n    int nbytes = read(battery_watch_fd_, &event, sizeof(event));\n    if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {\n      thread_.stop();\n      return;\n    }\n    dp.emit();\n  };\n  thread_battery_update_ = [this] {\n    poll_fds_[0].revents = 0;\n    poll_fds_[0].events = POLLIN;\n    poll_fds_[0].fd = udev_monitor_get_fd(mon_.get());\n    int ret = poll(poll_fds_.data(), poll_fds_.size(), -1);\n    if (ret < 0) {\n      thread_.stop();\n      return;\n    }\n    if ((poll_fds_[0].revents & POLLIN) != 0) {\n      signalfd_siginfo signal_info;\n      read(poll_fds_[0].fd, &signal_info, sizeof(signal_info));\n    }\n    refreshBatteries();\n    dp.emit();\n  };\n#endif\n}\n\nvoid waybar::modules::Battery::refreshBatteries() {\n#if defined(__linux__)\n  std::lock_guard<std::mutex> guard(battery_list_mutex_);\n  // Mark existing list of batteries as not necessarily found\n  std::map<fs::path, bool> check_map;\n  for (auto const& bat : batteries_) {\n    check_map[bat.first] = false;\n  }\n\n  try {\n    for (auto& node : fs::directory_iterator(data_dir_)) {\n      if (!fs::is_directory(node)) {\n        continue;\n      }\n      auto dir_name = node.path().filename();\n      auto bat_defined = config_[\"bat\"].isString();\n      bool bat_compatibility = config_[\"bat-compatibility\"].asBool();\n      if (((bat_defined && dir_name == config_[\"bat\"].asString()) || !bat_defined) &&\n          (fs::exists(node.path() / \"capacity\") || fs::exists(node.path() / \"charge_now\")) &&\n          fs::exists(node.path() / \"uevent\") &&\n          (fs::exists(node.path() / \"status\") || bat_compatibility) &&\n          fs::exists(node.path() / \"type\")) {\n        std::string type;\n        std::ifstream(node.path() / \"type\") >> type;\n\n        if (!type.compare(\"Battery\")) {\n          // Ignore non-system power supplies unless explicitly requested\n          if (!bat_defined && fs::exists(node.path() / \"scope\")) {\n            std::string scope;\n            std::ifstream(node.path() / \"scope\") >> scope;\n            if (g_ascii_strcasecmp(scope.data(), \"device\") == 0) {\n              continue;\n            }\n          }\n\n          check_map[node.path()] = true;\n          auto search = batteries_.find(node.path());\n          if (search == batteries_.end()) {\n            // We've found a new battery save it and start listening for events\n            auto event_path = (node.path() / \"uevent\");\n            auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);\n            if (wd < 0) {\n              spdlog::warn(\"Could not watch events for {} (device may have been removed)\",\n                           node.path().string());\n              continue;\n            }\n            batteries_[node.path()] = wd;\n          }\n        }\n      }\n      auto adap_defined = config_[\"adapter\"].isString();\n      if (((adap_defined && dir_name == config_[\"adapter\"].asString()) || !adap_defined) &&\n          (fs::exists(node.path() / \"online\") || fs::exists(node.path() / \"status\"))) {\n        adapter_ = node.path();\n      }\n    }\n  } catch (fs::filesystem_error& e) {\n    throw std::runtime_error(e.what());\n  }\n  if (warnFirstTime_ && batteries_.empty()) {\n    if (config_[\"bat\"].isString()) {\n      spdlog::warn(\"No battery named {0}\", config_[\"bat\"].asString());\n    } else {\n      spdlog::warn(\"No batteries.\");\n    }\n\n    warnFirstTime_ = false;\n  }\n\n  // Remove any batteries that are no longer present and unwatch them\n  for (auto const& check : check_map) {\n    if (!check.second) {\n      auto watch_id = batteries_[check.first];\n      if (watch_id >= 0) {\n        inotify_rm_watch(battery_watch_fd_, watch_id);\n      }\n      batteries_.erase(check.first);\n    }\n  }\n#endif\n}\n\n// Unknown > Full > Not charging > Discharging > Charging\nstatic bool status_gt(const std::string& a, const std::string& b) {\n  if (a == b)\n    return false;\n  else if (a == \"Unknown\")\n    return true;\n  else if (a == \"Full\" && b != \"Unknown\")\n    return true;\n  else if (a == \"Not charging\" && b != \"Unknown\" && b != \"Full\")\n    return true;\n  else if (a == \"Discharging\" && b != \"Unknown\" && b != \"Full\" && b != \"Not charging\")\n    return true;\n  return false;\n}\n\nstd::tuple<uint8_t, float, std::string, float, uint16_t, float>\nwaybar::modules::Battery::getInfos() {\n  std::lock_guard<std::mutex> guard(battery_list_mutex_);\n\n  try {\n#if defined(__FreeBSD__)\n    /* Allocate state of battery units reported via ACPI. */\n    int battery_units = 0;\n    size_t battery_units_size = sizeof battery_units;\n    if (sysctlbyname(\"hw.acpi.battery.units\", &battery_units, &battery_units_size, NULL, 0) != 0) {\n      throw std::runtime_error(\"sysctl hw.acpi.battery.units failed\");\n    }\n\n    if (battery_units < 0) {\n      throw std::runtime_error(\"No battery units\");\n    }\n\n    int capacity;\n    size_t size_capacity = sizeof capacity;\n    if (sysctlbyname(\"hw.acpi.battery.life\", &capacity, &size_capacity, NULL, 0) != 0) {\n      throw std::runtime_error(\"sysctl hw.acpi.battery.life failed\");\n    }\n    int time;\n    size_t size_time = sizeof time;\n    if (sysctlbyname(\"hw.acpi.battery.time\", &time, &size_time, NULL, 0) != 0) {\n      throw std::runtime_error(\"sysctl hw.acpi.battery.time failed\");\n    }\n    int rate;\n    size_t size_rate = sizeof rate;\n    if (sysctlbyname(\"hw.acpi.battery.rate\", &rate, &size_rate, NULL, 0) != 0) {\n      throw std::runtime_error(\"sysctl hw.acpi.battery.rate failed\");\n    }\n\n    auto status = getAdapterStatus(capacity);\n    // Handle full-at\n    if (config_[\"full-at\"].isUInt()) {\n      auto full_at = config_[\"full-at\"].asUInt();\n      if (full_at < 100) {\n        capacity = 100.f * capacity / full_at;\n      }\n    }\n    if (capacity > 100.f) {\n      // This can happen when the battery is calibrating and goes above 100%\n      // Handle it gracefully by clamping at 100%\n      capacity = 100.f;\n    }\n    uint8_t cap = round(capacity);\n    if (cap == 100 && status == \"Plugged\") {\n      // If we've reached 100% just mark as full as some batteries can stay\n      // stuck reporting they're still charging but not yet done\n      status = \"Full\";\n    }\n\n    // spdlog::info(\"{} {} {} {}\", capacity,time,status,rate);\n    return {capacity, time / 60.0, status, rate, 0, 0.0F};\n\n#elif defined(__linux__)\n    uint32_t total_power = 0;  // μW\n    bool total_power_exists = false;\n    uint32_t total_energy = 0;  // μWh\n    bool total_energy_exists = false;\n    uint32_t total_energy_full = 0;\n    bool total_energy_full_exists = false;\n    uint32_t total_energy_full_design = 0;\n    bool total_energy_full_design_exists = false;\n    uint32_t total_capacity = 0;\n    bool total_capacity_exists = false;\n    uint32_t time_to_empty_now = 0;\n    bool time_to_empty_now_exists = false;\n    uint32_t time_to_full_now = 0;\n    bool time_to_full_now_exists = false;\n\n    uint32_t largestDesignCapacity = 0;\n    uint16_t mainBatCycleCount = 0;\n    float mainBatHealthPercent = 0.0F;\n\n    std::string status = \"Unknown\";\n    for (auto const& item : batteries_) {\n      auto bat = item.first;\n      std::string _status;\n\n      /* Check for adapter status if battery is not available */\n      if (!std::ifstream(bat / \"status\")) {\n        std::getline(std::ifstream(adapter_ / \"status\"), _status);\n      } else {\n        std::getline(std::ifstream(bat / \"status\"), _status);\n      }\n\n      // Some battery will report current and charge in μA/μAh.\n      // Scale these by the voltage to get μW/μWh.\n\n      uint32_t current_now = 0;\n      int32_t _current_now_int = 0;\n      bool current_now_exists = false;\n      if (fs::exists(bat / \"current_now\")) {\n        current_now_exists = true;\n        std::ifstream(bat / \"current_now\") >> _current_now_int;\n      } else if (fs::exists(bat / \"current_avg\")) {\n        current_now_exists = true;\n        std::ifstream(bat / \"current_avg\") >> _current_now_int;\n      }\n      // Documentation ABI allows a negative value when discharging, positive\n      // value when charging.\n      current_now = std::abs(_current_now_int);\n\n      if (fs::exists(bat / \"time_to_empty_now\")) {\n        time_to_empty_now_exists = true;\n        std::ifstream(bat / \"time_to_empty_now\") >> time_to_empty_now;\n      }\n\n      if (fs::exists(bat / \"time_to_full_now\")) {\n        time_to_full_now_exists = true;\n        std::ifstream(bat / \"time_to_full_now\") >> time_to_full_now;\n      }\n\n      uint32_t voltage_now = 0;\n      bool voltage_now_exists = false;\n      if (fs::exists(bat / \"voltage_now\")) {\n        voltage_now_exists = true;\n        std::ifstream(bat / \"voltage_now\") >> voltage_now;\n      } else if (fs::exists(bat / \"voltage_avg\")) {\n        voltage_now_exists = true;\n        std::ifstream(bat / \"voltage_avg\") >> voltage_now;\n      }\n\n      uint32_t charge_full = 0;\n      bool charge_full_exists = false;\n      if (fs::exists(bat / \"charge_full\")) {\n        charge_full_exists = true;\n        std::ifstream(bat / \"charge_full\") >> charge_full;\n      }\n\n      uint32_t charge_full_design = 0;\n      bool charge_full_design_exists = false;\n      if (fs::exists(bat / \"charge_full_design\")) {\n        charge_full_design_exists = true;\n        std::ifstream(bat / \"charge_full_design\") >> charge_full_design;\n      }\n\n      uint32_t charge_now = 0;\n      bool charge_now_exists = false;\n      if (fs::exists(bat / \"charge_now\")) {\n        charge_now_exists = true;\n        std::ifstream(bat / \"charge_now\") >> charge_now;\n      }\n\n      uint32_t power_now = 0;\n      int32_t _power_now_int = 0;\n      bool power_now_exists = false;\n      if (fs::exists(bat / \"power_now\")) {\n        power_now_exists = true;\n        std::ifstream(bat / \"power_now\") >> _power_now_int;\n      }\n      // Some drivers (example: Qualcomm) exposes use a negative value when\n      // discharging, positive value when charging.\n      power_now = std::abs(_power_now_int);\n\n      uint32_t energy_now = 0;\n      bool energy_now_exists = false;\n      if (fs::exists(bat / \"energy_now\")) {\n        energy_now_exists = true;\n        std::ifstream(bat / \"energy_now\") >> energy_now;\n      }\n\n      uint32_t energy_full = 0;\n      bool energy_full_exists = false;\n      if (fs::exists(bat / \"energy_full\")) {\n        energy_full_exists = true;\n        std::ifstream(bat / \"energy_full\") >> energy_full;\n      }\n\n      uint32_t energy_full_design = 0;\n      bool energy_full_design_exists = false;\n      if (fs::exists(bat / \"energy_full_design\")) {\n        energy_full_design_exists = true;\n        std::ifstream(bat / \"energy_full_design\") >> energy_full_design;\n      }\n\n      uint16_t cycleCount = 0;\n      if (fs::exists(bat / \"cycle_count\")) {\n        std::ifstream(bat / \"cycle_count\") >> cycleCount;\n      }\n      if (charge_full_design >= largestDesignCapacity) {\n        largestDesignCapacity = charge_full_design;\n\n        if (cycleCount > mainBatCycleCount) {\n          mainBatCycleCount = cycleCount;\n        }\n\n        if (charge_full_exists && charge_full_design_exists) {\n          float batHealthPercent = ((float)charge_full / charge_full_design) * 100;\n          if (mainBatHealthPercent == 0.0F || batHealthPercent < mainBatHealthPercent) {\n            mainBatHealthPercent = batHealthPercent;\n          }\n        } else if (energy_full_exists && energy_full_design_exists) {\n          float batHealthPercent = ((float)energy_full / energy_full_design) * 100;\n          if (mainBatHealthPercent == 0.0F || batHealthPercent < mainBatHealthPercent) {\n            mainBatHealthPercent = batHealthPercent;\n          }\n        }\n      }\n\n      uint32_t capacity = 0;\n      bool capacity_exists = false;\n      if (charge_now_exists && charge_full_exists && charge_full != 0) {\n        capacity_exists = true;\n        capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full;\n      } else if (energy_now_exists && energy_full_exists && energy_full != 0) {\n        capacity_exists = true;\n        capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;\n      } else if (fs::exists(bat / \"capacity\")) {\n        capacity_exists = true;\n        std::ifstream(bat / \"capacity\") >> capacity;\n      }\n\n      if (!voltage_now_exists) {\n        if (power_now_exists && current_now_exists && current_now != 0) {\n          voltage_now_exists = true;\n          voltage_now = 1000000 * (uint64_t)power_now / (uint64_t)current_now;\n        } else if (energy_full_design_exists && charge_full_design_exists &&\n                   charge_full_design != 0) {\n          voltage_now_exists = true;\n          voltage_now = 1000000 * (uint64_t)energy_full_design / (uint64_t)charge_full_design;\n        } else if (energy_now_exists) {\n          if (charge_now_exists && charge_now != 0) {\n            voltage_now_exists = true;\n            voltage_now = 1000000 * (uint64_t)energy_now / (uint64_t)charge_now;\n          } else if (capacity_exists && charge_full_exists) {\n            charge_now_exists = true;\n            charge_now = (uint64_t)charge_full * (uint64_t)capacity / 100;\n            if (charge_full != 0 && capacity != 0) {\n              voltage_now_exists = true;\n              voltage_now =\n                  1000000 * (uint64_t)energy_now * 100 / (uint64_t)charge_full / (uint64_t)capacity;\n            }\n          }\n        } else if (energy_full_exists) {\n          if (charge_full_exists && charge_full != 0) {\n            voltage_now_exists = true;\n            voltage_now = 1000000 * (uint64_t)energy_full / (uint64_t)charge_full;\n          } else if (charge_now_exists && capacity_exists) {\n            if (capacity != 0) {\n              charge_full_exists = true;\n              charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;\n            }\n            if (charge_now != 0) {\n              voltage_now_exists = true;\n              voltage_now =\n                  10000 * (uint64_t)energy_full * (uint64_t)capacity / (uint64_t)charge_now;\n            }\n          }\n        }\n      }\n\n      if (!capacity_exists) {\n        if (charge_now_exists && energy_full_exists && voltage_now_exists) {\n          if (!charge_full_exists && voltage_now != 0) {\n            charge_full_exists = true;\n            charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;\n          }\n          if (energy_full != 0) {\n            capacity_exists = true;\n            capacity = (uint64_t)charge_now * (uint64_t)voltage_now / 10000 / (uint64_t)energy_full;\n          }\n        } else if (charge_full_exists && energy_now_exists && voltage_now_exists) {\n          if (!charge_now_exists && voltage_now != 0) {\n            charge_now_exists = true;\n            charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;\n          }\n          if (voltage_now != 0 && charge_full != 0) {\n            capacity_exists = true;\n            capacity = 100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now /\n                       (uint64_t)charge_full;\n          }\n        }\n      }\n\n      if (!energy_now_exists && voltage_now_exists) {\n        if (charge_now_exists) {\n          energy_now_exists = true;\n          energy_now = (uint64_t)charge_now * (uint64_t)voltage_now / 1000000;\n        } else if (capacity_exists && charge_full_exists) {\n          charge_now_exists = true;\n          charge_now = (uint64_t)capacity * (uint64_t)charge_full / 100;\n          energy_now_exists = true;\n          energy_now =\n              (uint64_t)voltage_now * (uint64_t)capacity * (uint64_t)charge_full / 1000000 / 100;\n        } else if (capacity_exists && energy_full) {\n          if (voltage_now != 0) {\n            charge_full_exists = true;\n            charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;\n            charge_now_exists = true;\n            charge_now = (uint64_t)capacity * 10000 * (uint64_t)energy_full / (uint64_t)voltage_now;\n          }\n          energy_now_exists = true;\n          energy_now = (uint64_t)capacity * (uint64_t)energy_full / 100;\n        }\n      }\n\n      if (!energy_full_exists && voltage_now_exists) {\n        if (charge_full_exists) {\n          energy_full_exists = true;\n          energy_full = (uint64_t)charge_full * (uint64_t)voltage_now / 1000000;\n        } else if (charge_now_exists && capacity_exists && capacity != 0) {\n          charge_full_exists = true;\n          charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;\n          energy_full_exists = true;\n          energy_full = (uint64_t)charge_now * (uint64_t)voltage_now / (uint64_t)capacity / 10000;\n        } else if (capacity_exists && energy_now) {\n          if (voltage_now != 0) {\n            charge_now_exists = true;\n            charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;\n          }\n          if (capacity != 0) {\n            energy_full_exists = true;\n            energy_full = 100 * (uint64_t)energy_now / (uint64_t)capacity;\n            if (voltage_now != 0) {\n              charge_full_exists = true;\n              charge_full =\n                  100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now / (uint64_t)capacity;\n            }\n          }\n        }\n      }\n\n      if (!power_now_exists && voltage_now_exists && current_now_exists) {\n        power_now_exists = true;\n        power_now = (uint64_t)voltage_now * (uint64_t)current_now / 1000000;\n      }\n\n      if (!energy_full_design_exists && voltage_now_exists && charge_full_design_exists) {\n        energy_full_design_exists = true;\n        energy_full_design = (uint64_t)voltage_now * (uint64_t)charge_full_design / 1000000;\n      }\n\n      // Show the \"smallest\" status among all batteries\n      if (status_gt(status, _status)) status = _status;\n\n      if (power_now_exists) {\n        total_power_exists = true;\n        total_power += power_now;\n      }\n      if (energy_now_exists) {\n        total_energy_exists = true;\n        total_energy += energy_now;\n      }\n      if (energy_full_exists) {\n        total_energy_full_exists = true;\n        total_energy_full += energy_full;\n      }\n      if (energy_full_design_exists) {\n        total_energy_full_design_exists = true;\n        total_energy_full_design += energy_full_design;\n      }\n      if (capacity_exists) {\n        total_capacity_exists = true;\n        total_capacity += capacity;\n      }\n    }\n\n    // Give `Plugged` higher priority over `Not charging`.\n    // So in a setting where TLP is used, `Plugged` is shown when the threshold is reached\n    if (!adapter_.empty() && (status == \"Discharging\" || status == \"Not charging\")) {\n      bool online;\n      std::string current_status;\n      std::ifstream(adapter_ / \"online\") >> online;\n      std::getline(std::ifstream(adapter_ / \"status\"), current_status);\n      if (online && current_status != \"Discharging\") status = \"Plugged\";\n    }\n\n    float time_remaining{0.0f};\n    if (status == \"Discharging\" && time_to_empty_now_exists) {\n      if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;\n    } else if (status == \"Discharging\" && total_power_exists && total_energy_exists) {\n      if (total_power != 0) time_remaining = (float)total_energy / total_power;\n    } else if (status == \"Charging\" && time_to_full_now_exists) {\n      if (time_to_full_now_exists && (time_to_full_now != 0))\n        time_remaining = -(float)time_to_full_now / 3600.0f;\n      // If we've turned positive it means the battery is past 100% and so just report that as no\n      // time remaining\n      if (time_remaining > 0.0f) time_remaining = 0.0f;\n    } else if (status == \"Charging\" && total_energy_exists && total_energy_full_exists &&\n               total_power_exists) {\n      if (total_power != 0)\n        time_remaining = -(float)(total_energy_full - total_energy) / total_power;\n      // If we've turned positive it means the battery is past 100% and so just report that as no\n      // time remaining\n      if (time_remaining > 0.0f) time_remaining = 0.0f;\n    }\n\n    float calculated_capacity{0.0f};\n    if (total_capacity_exists) {\n      if (total_capacity > 0.0f)\n        calculated_capacity = (float)total_capacity / batteries_.size();\n      else if (total_energy_full_exists && total_energy_exists) {\n        if (total_energy_full > 0.0f)\n          calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);\n      }\n    }\n\n    // Handle weighted-average\n    if (weightedAverage_ && total_energy_exists && total_energy_full_exists) {\n      if (total_energy_full > 0.0f)\n        calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);\n    }\n\n    // Handle design-capacity\n    if ((config_[\"design-capacity\"].isBool() ? config_[\"design-capacity\"].asBool() : false) &&\n        total_energy_exists && total_energy_full_design_exists) {\n      if (total_energy_full_design > 0.0f)\n        calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design);\n    }\n\n    // Handle full-at\n    if (config_[\"full-at\"].isUInt()) {\n      auto full_at = config_[\"full-at\"].asUInt();\n      if (full_at < 100) calculated_capacity = 100.f * calculated_capacity / full_at;\n    }\n\n    // Handle it gracefully by clamping at 100%\n    // This can happen when the battery is calibrating and goes above 100%\n    if (calculated_capacity > 100.f) calculated_capacity = 100.f;\n\n    uint8_t cap = round(calculated_capacity);\n    // If we've reached 100% just mark as full as some batteries can stay stuck reporting they're\n    // still charging but not yet done\n    if (cap == 100 && status == \"Charging\") status = \"Full\";\n\n    return {\n        cap, time_remaining, status, total_power / 1e6, mainBatCycleCount, mainBatHealthPercent};\n#endif\n  } catch (const std::exception& e) {\n    spdlog::error(\"Battery: {}\", e.what());\n    return {0, 0, \"Unknown\", 0, 0, 0.0f};\n  }\n}\n\nconst std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {\n#if defined(__FreeBSD__)\n  int state;\n  size_t size_state = sizeof state;\n  if (sysctlbyname(\"hw.acpi.battery.state\", &state, &size_state, NULL, 0) != 0) {\n    throw std::runtime_error(\"sysctl hw.acpi.battery.state failed\");\n  }\n  bool online = state == 2;\n  std::string status{\"Unknown\"};  // TODO: add status in FreeBSD\n  {\n#else\n  if (!adapter_.empty()) {\n    bool online;\n    std::string status;\n    std::ifstream(adapter_ / \"online\") >> online;\n    std::getline(std::ifstream(adapter_ / \"status\"), status);\n#endif\n    if (capacity == 100) {\n      return \"Full\";\n    }\n    if (online && status != \"Discharging\") {\n      return \"Plugged\";\n    }\n    return \"Discharging\";\n  }\n  return \"Unknown\";\n}\n\nconst std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) {\n  hoursRemaining = std::fabs(hoursRemaining);\n  uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);\n  uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));\n  auto format = std::string(\"{H} h {M} min\");\n  if (full_hours == 0 && minutes == 0) {\n    // Migh as well not show \"0h 0min\"\n    return \"\";\n  }\n  if (config_[\"format-time\"].isString()) {\n    format = config_[\"format-time\"].asString();\n  }\n  std::string zero_pad_minutes = fmt::format(\"{:02d}\", minutes);\n  return fmt::format(fmt::runtime(format), fmt::arg(\"H\", full_hours), fmt::arg(\"M\", minutes),\n                     fmt::arg(\"m\", zero_pad_minutes));\n}\n\nauto waybar::modules::Battery::update() -> void {\n#if defined(__linux__)\n  if (batteries_.empty()) {\n    event_box_.hide();\n    return;\n  }\n#endif\n  auto [capacity, time_remaining, status, power, cycles, health] = getInfos();\n  if (status == \"Unknown\") {\n    status = getAdapterStatus(capacity);\n  }\n  auto status_pretty = status;\n\n  // Transform to lowercase  and replace space with dash\n  std::ranges::transform(status.begin(), status.end(), status.begin(),\n                         [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });\n  auto format = format_;\n  auto state = getState(capacity, true);\n  processEvents(state, status, capacity);\n  setBarClass(state);\n  auto time_remaining_formatted = formatTimeRemaining(time_remaining);\n  if (tooltipEnabled()) {\n    std::string tooltip_text_default;\n    std::string tooltip_format = \"{timeTo}\";\n    if (time_remaining != 0) {\n      if (time_remaining > 0) {\n        tooltip_text_default = std::string(\"Empty in \") + time_remaining_formatted;\n      } else {\n        tooltip_text_default = std::string(\"Full in \") + time_remaining_formatted;\n      }\n    } else {\n      tooltip_text_default = status_pretty;\n    }\n    if (!state.empty() && config_[\"tooltip-format-\" + status + \"-\" + state].isString()) {\n      tooltip_format = config_[\"tooltip-format-\" + status + \"-\" + state].asString();\n    } else if (config_[\"tooltip-format-\" + status].isString()) {\n      tooltip_format = config_[\"tooltip-format-\" + status].asString();\n    } else if (!state.empty() && config_[\"tooltip-format-\" + state].isString()) {\n      tooltip_format = config_[\"tooltip-format-\" + state].asString();\n    } else if (config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    label_.set_tooltip_markup(\n        fmt::format(fmt::runtime(tooltip_format), fmt::arg(\"timeTo\", tooltip_text_default),\n                    fmt::arg(\"power\", power), fmt::arg(\"capacity\", capacity),\n                    fmt::arg(\"time\", time_remaining_formatted), fmt::arg(\"cycles\", cycles),\n                    fmt::arg(\"health\", fmt::format(\"{:.3}\", health))));\n  }\n  if (!old_status_.empty()) {\n    label_.get_style_context()->remove_class(old_status_);\n  }\n  label_.get_style_context()->add_class(status);\n  old_status_ = status;\n  if (!state.empty() && config_[\"format-\" + status + \"-\" + state].isString()) {\n    format = config_[\"format-\" + status + \"-\" + state].asString();\n  } else if (config_[\"format-\" + status].isString()) {\n    format = config_[\"format-\" + status].asString();\n  } else if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    auto icons = std::vector<std::string>{status + \"-\" + state, status, state};\n    label_.set_markup(fmt::format(\n        fmt::runtime(format), fmt::arg(\"capacity\", capacity), fmt::arg(\"power\", power),\n        fmt::arg(\"icon\", getIcon(capacity, icons)), fmt::arg(\"time\", time_remaining_formatted),\n        fmt::arg(\"cycles\", cycles), fmt::arg(\"health\", fmt::format(\"{:.3}\", health))));\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nvoid waybar::modules::Battery::setBarClass(std::string& state) {\n  auto classes = bar_.window.get_style_context()->list_classes();\n  const std::string prefix = \"battery-\";\n\n  auto old_class_it = std::find_if(classes.begin(), classes.end(), [&prefix](auto classname) {\n    return classname.rfind(prefix, 0) == 0;\n  });\n\n  auto new_class = prefix + state;\n\n  // If the bar doesn't have any `battery-` class\n  if (old_class_it == classes.end()) {\n    if (!state.empty()) {\n      bar_.window.get_style_context()->add_class(new_class);\n    }\n    return;\n  }\n\n  auto old_class = *old_class_it;\n\n  // If the bar has a `battery-` class,\n  // but `state` is empty\n  if (state.empty()) {\n    bar_.window.get_style_context()->remove_class(old_class);\n    return;\n  }\n\n  // If the bar has a `battery-` class,\n  // and `state` is NOT empty\n  if (old_class != new_class) {\n    bar_.window.get_style_context()->remove_class(old_class);\n    bar_.window.get_style_context()->add_class(new_class);\n  }\n}\n\nvoid waybar::modules::Battery::processEvents(std::string& state, std::string& status,\n                                             uint8_t capacity) {\n  // There are no events specified, skip\n  auto events = config_[\"events\"];\n  if (!events.isObject() || events.empty()) {\n    return;\n  }\n  auto exec = [](Json::Value const& event) {\n    if (!event.isString()) return;\n    if (auto command = event.asString(); !command.empty()) {\n      util::command::exec(command, \"\");\n    }\n  };\n  std::string status_name = status == \"discharging\" ? \"on-discharging\" : \"on-charging\";\n  std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state);\n  if (last_event_ != event_name) {\n    spdlog::debug(\"battery: triggering event {}\", event_name);\n    exec(events[event_name]);\n    if (!last_event_.empty() && last_event_[3] != event_name[3]) {\n      exec(events[status_name]);\n    }\n    last_event_ = event_name;\n  }\n}\n"
  },
  {
    "path": "src/modules/bluetooth.cpp",
    "content": "#include \"modules/bluetooth.hpp\"\n\n#include <fmt/format.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <sstream>\n\n#include \"util/scope_guard.hpp\"\n\nnamespace {\n\nusing GDBusManager = std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)>;\n\nauto generateManager() -> GDBusManager {\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([error]() {\n    if (error) {\n      g_error_free(error);\n    }\n  });\n  GDBusObjectManager* manager = g_dbus_object_manager_client_new_for_bus_sync(\n      G_BUS_TYPE_SYSTEM,\n      GDBusObjectManagerClientFlags::G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,\n      \"org.bluez\", \"/\", NULL, NULL, NULL, NULL, &error);\n\n  if (error) {\n    spdlog::error(\"g_dbus_object_manager_client_new_for_bus_sync() failed: {}\", error->message);\n  }\n\n  auto destructor = [](GDBusObjectManager* manager) {\n    if (manager) {\n      g_object_unref(manager);\n    }\n  };\n\n  return GDBusManager{manager, destructor};\n}\n\nauto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {\n  auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);\n  if (gvar) {\n    bool property_value = g_variant_get_boolean(gvar);\n    g_variant_unref(gvar);\n    return property_value;\n  }\n\n  spdlog::error(\"getBoolProperty() failed: doesn't have property {}\", property_name);\n  return false;\n}\n\nauto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name)\n    -> std::optional<std::string> {\n  auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);\n  if (gvar) {\n    std::string property_value = g_variant_get_string(gvar, NULL);\n    g_variant_unref(gvar);\n    return property_value;\n  }\n\n  return std::nullopt;\n}\n\nauto getStringProperty(GDBusProxy* proxy, const char* property_name) -> std::string {\n  auto property_value = getOptionalStringProperty(proxy, property_name);\n  if (!property_value.has_value()) {\n    spdlog::error(\"getStringProperty() failed: doesn't have property {}\", property_name);\n  }\n  return property_value.value_or(\"\");\n}\n\nauto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned char {\n  auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);\n  if (gvar) {\n    unsigned char property_value;\n    g_variant_get(gvar, \"y\", &property_value);\n    g_variant_unref(gvar);\n\n    return property_value;\n  }\n\n  spdlog::error(\"getUcharProperty() failed: doesn't have property {}\", property_name);\n  return 0;\n}\n\n}  // namespace\n\nwaybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"bluetooth\", id, \" {status}\", 10),\n#ifdef WANT_RFKILL\n      rfkill_{RFKILL_TYPE_BLUETOOTH},\n#endif\n      manager_(generateManager()) {\n\n  if (config_[\"format-device-preference\"].isArray()) {\n    std::transform(config_[\"format-device-preference\"].begin(),\n                   config_[\"format-device-preference\"].end(),\n                   std::back_inserter(device_preference_), [](auto x) { return x.asString(); });\n  }\n\n  if (cur_controller_ = findCurController(); !cur_controller_) {\n    if (config_[\"controller-alias\"].isString()) {\n      spdlog::warn(\"no bluetooth controller found with alias '{}'\",\n                   config_[\"controller-alias\"].asString());\n    } else {\n      spdlog::warn(\"no bluetooth controller found\");\n    }\n    update();\n  } else {\n    // This call only make sense if a controller could be found\n    findConnectedDevices(cur_controller_->path, connected_devices_);\n  }\n\n  if (manager_) {\n    g_signal_connect(manager_.get(), \"object-added\", G_CALLBACK(onObjectAdded), this);\n    g_signal_connect(manager_.get(), \"object-removed\", G_CALLBACK(onObjectRemoved), this);\n    g_signal_connect(manager_.get(), \"interface-proxy-properties-changed\",\n                     G_CALLBACK(onInterfaceProxyPropertiesChanged), this);\n    g_signal_connect(manager_.get(), \"interface-added\", G_CALLBACK(onInterfaceAddedOrRemoved),\n                     this);\n    g_signal_connect(manager_.get(), \"interface-removed\", G_CALLBACK(onInterfaceAddedOrRemoved),\n                     this);\n  }\n\n#ifdef WANT_RFKILL\n  rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));\n#endif\n\n  dp.emit();\n}\n\nauto waybar::modules::Bluetooth::update() -> void {\n  // focussed device is either:\n  // - the first device in the device_preference_ list that is connected to the\n  //   current controller (if none fallback to last connected device)\n  // - it is the last device that connected to the current controller\n  if (!connected_devices_.empty()) {\n    bool preferred_device_connected = false;\n    if (!device_preference_.empty()) {\n      for (const std::string& device_alias : device_preference_) {\n        auto it =\n            std::find_if(connected_devices_.begin(), connected_devices_.end(),\n                         [device_alias](auto device) { return device_alias == device.alias; });\n        if (it != connected_devices_.end()) {\n          preferred_device_connected = true;\n          cur_focussed_device_ = *it;\n          break;\n        }\n      }\n    }\n    if (!preferred_device_connected) {\n      cur_focussed_device_ = connected_devices_.back();\n    }\n  }\n\n  std::string state;\n  std::string tooltip_format;\n  if (cur_controller_) {\n    if (!cur_controller_->powered)\n      state = \"off\";\n    else if (!connected_devices_.empty())\n      state = \"connected\";\n    else\n      state = \"on\";\n  } else {\n    state = \"no-controller\";\n  }\n#ifdef WANT_RFKILL\n  if (rfkill_.getState()) state = \"disabled\";\n#endif\n  bool battery_available =\n      state == \"connected\" && cur_focussed_device_.battery_percentage.has_value();\n\n#ifdef WANT_RFKILL\n  // also adds enabled icon if icon for state is not defined\n  std::vector<std::string> states = {state, rfkill_.getState() ? \"disabled\" : \"enabled\"};\n  std::string icon = getIcon(0, states);\n#else\n  std::string icon = getIcon(0, state);\n#endif\n  std::string icon_label = icon;\n  std::string icon_tooltip = icon;\n\n  if (!alt_) {\n    if (battery_available && config_[\"format-connected-battery\"].isString()) {\n      format_ = config_[\"format-connected-battery\"].asString();\n      icon_label = getIcon(cur_focussed_device_.battery_percentage.value_or(0));\n    } else if (config_[\"format-\" + state].isString()) {\n      format_ = config_[\"format-\" + state].asString();\n    } else if (config_[\"format\"].isString()) {\n      format_ = config_[\"format\"].asString();\n    } else {\n      format_ = default_format_;\n    }\n  }\n  if (battery_available && config_[\"tooltip-format-connected-battery\"].isString()) {\n    tooltip_format = config_[\"tooltip-format-connected-battery\"].asString();\n    icon_tooltip = getIcon(cur_focussed_device_.battery_percentage.value_or(0));\n  } else if (config_[\"tooltip-format-\" + state].isString()) {\n    tooltip_format = config_[\"tooltip-format-\" + state].asString();\n  } else if (config_[\"tooltip-format\"].isString()) {\n    tooltip_format = config_[\"tooltip-format\"].asString();\n  }\n\n  auto update_style_context = [this](const std::string& style_class, bool in_next_state) {\n    if (in_next_state && !label_.get_style_context()->has_class(style_class)) {\n      label_.get_style_context()->add_class(style_class);\n    } else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {\n      label_.get_style_context()->remove_class(style_class);\n    }\n  };\n  update_style_context(\"discoverable\", cur_controller_ ? cur_controller_->discoverable : false);\n  update_style_context(\"discovering\", cur_controller_ ? cur_controller_->discovering : false);\n  update_style_context(\"pairable\", cur_controller_ ? cur_controller_->pairable : false);\n  if (!state_.empty()) {\n    update_style_context(state_, false);\n  }\n  update_style_context(state, true);\n  state_ = state;\n\n  if (format_.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    label_.set_markup(fmt::format(\n        fmt::runtime(format_), fmt::arg(\"status\", state_),\n        fmt::arg(\"num_connections\", connected_devices_.size()),\n        fmt::arg(\"controller_address\", cur_controller_ ? cur_controller_->address : \"null\"),\n        fmt::arg(\"controller_address_type\",\n                 cur_controller_ ? cur_controller_->address_type : \"null\"),\n        fmt::arg(\"controller_alias\", cur_controller_ ? cur_controller_->alias : \"null\"),\n        fmt::arg(\"device_address\", cur_focussed_device_.address),\n        fmt::arg(\"device_address_type\", cur_focussed_device_.address_type),\n        fmt::arg(\"device_alias\", cur_focussed_device_.alias), fmt::arg(\"icon\", icon_label),\n        fmt::arg(\"device_battery_percentage\",\n                 cur_focussed_device_.battery_percentage.value_or(0))));\n  }\n\n  if (tooltipEnabled()) {\n    bool tooltip_enumerate_connections_ = config_[\"tooltip-format-enumerate-connected\"].isString();\n    bool tooltip_enumerate_connections_battery_ =\n        config_[\"tooltip-format-enumerate-connected-battery\"].isString();\n    if (tooltip_enumerate_connections_ || tooltip_enumerate_connections_battery_) {\n      std::stringstream ss;\n      for (DeviceInfo dev : connected_devices_) {\n        if ((tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) ||\n            tooltip_enumerate_connections_) {\n          ss << \"\\n\";\n          std::string enumerate_format;\n          std::string enumerate_icon;\n          if (tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) {\n            enumerate_format = config_[\"tooltip-format-enumerate-connected-battery\"].asString();\n            enumerate_icon = getIcon(dev.battery_percentage.value_or(0));\n          } else {\n            enumerate_format = config_[\"tooltip-format-enumerate-connected\"].asString();\n          }\n          ss << fmt::format(\n              fmt::runtime(enumerate_format), fmt::arg(\"device_address\", dev.address),\n              fmt::arg(\"device_address_type\", dev.address_type),\n              fmt::arg(\"device_alias\", dev.alias), fmt::arg(\"icon\", enumerate_icon),\n              fmt::arg(\"device_battery_percentage\", dev.battery_percentage.value_or(0)));\n        }\n      }\n      device_enumerate_ = ss.str();\n      // don't start the connected devices text with a new line\n      if (!device_enumerate_.empty()) {\n        device_enumerate_.erase(0, 1);\n      }\n    }\n    label_.set_tooltip_markup(fmt::format(\n        fmt::runtime(tooltip_format), fmt::arg(\"status\", state_),\n        fmt::arg(\"num_connections\", connected_devices_.size()),\n        fmt::arg(\"controller_address\", cur_controller_ ? cur_controller_->address : \"null\"),\n        fmt::arg(\"controller_address_type\",\n                 cur_controller_ ? cur_controller_->address_type : \"null\"),\n        fmt::arg(\"controller_alias\", cur_controller_ ? cur_controller_->alias : \"null\"),\n        fmt::arg(\"device_address\", cur_focussed_device_.address),\n        fmt::arg(\"device_address_type\", cur_focussed_device_.address_type),\n        fmt::arg(\"device_alias\", cur_focussed_device_.alias), fmt::arg(\"icon\", icon_tooltip),\n        fmt::arg(\"device_battery_percentage\", cur_focussed_device_.battery_percentage.value_or(0)),\n        fmt::arg(\"device_enumerate\", device_enumerate_)));\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nauto waybar::modules::Bluetooth::onObjectAdded(GDBusObjectManager* manager, GDBusObject* object,\n                                               gpointer user_data) -> void {\n  ControllerInfo info;\n  Bluetooth* bt = static_cast<Bluetooth*>(user_data);\n\n  if (!bt->cur_controller_.has_value() && bt->getControllerProperties(object, info) &&\n      (!bt->config_[\"controller-alias\"].isString() ||\n       bt->config_[\"controller-alias\"].asString() == info.alias)) {\n    bt->cur_controller_ = std::move(info);\n    bt->dp.emit();\n  }\n}\n\nauto waybar::modules::Bluetooth::onObjectRemoved(GDBusObjectManager* manager, GDBusObject* object,\n                                                 gpointer user_data) -> void {\n  Bluetooth* bt = static_cast<Bluetooth*>(user_data);\n  GDBusProxy* proxy_controller;\n\n  if (!bt->cur_controller_.has_value()) {\n    return;\n  }\n\n  proxy_controller = G_DBUS_PROXY(g_dbus_object_get_interface(object, \"org.bluez.Adapter1\"));\n\n  if (proxy_controller != NULL) {\n    std::string object_path = g_dbus_object_get_object_path(object);\n\n    if (object_path == bt->cur_controller_->path) {\n      bt->cur_controller_ = bt->findCurController();\n      if (bt->cur_controller_.has_value()) {\n        bt->connected_devices_.clear();\n        bt->findConnectedDevices(bt->cur_controller_->path, bt->connected_devices_);\n      }\n      bt->dp.emit();\n    }\n\n    g_object_unref(proxy_controller);\n  }\n}\n\n// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is\n// connected/disconnected\nauto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* manager,\n                                                           GDBusObject* object,\n                                                           GDBusInterface* interface,\n                                                           gpointer user_data) -> void {\n  std::string interface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface));\n  std::string object_path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(interface));\n  if (interface_name == \"org.bluez.Battery1\") {\n    Bluetooth* bt = static_cast<Bluetooth*>(user_data);\n    if (bt->cur_controller_.has_value()) {\n      auto device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(),\n                                 [object_path](auto d) { return d.path == object_path; });\n      if (device != bt->connected_devices_.end()) {\n        device->battery_percentage = bt->getDeviceBatteryPercentage(object);\n        bt->dp.emit();\n      }\n    }\n  }\n}\n\nauto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(\n    GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy,\n    GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data)\n    -> void {\n  std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy);\n  std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));\n\n  Bluetooth* bt = static_cast<Bluetooth*>(user_data);\n\n  if (!bt->cur_controller_.has_value()) {\n    return;\n  }\n\n  if (interface_name == \"org.bluez.Adapter1\") {\n    if (object_path == bt->cur_controller_->path) {\n      bt->getControllerProperties(G_DBUS_OBJECT(object_proxy), *bt->cur_controller_);\n      bt->dp.emit();\n    }\n  } else if (interface_name == \"org.bluez.Device1\" || interface_name == \"org.bluez.Battery1\") {\n    DeviceInfo device;\n    bt->getDeviceProperties(G_DBUS_OBJECT(object_proxy), device);\n    auto cur_device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(),\n                                   [device](auto d) { return d.path == device.path; });\n    if (cur_device == bt->connected_devices_.end()) {\n      if (device.connected) {\n        bt->connected_devices_.push_back(device);\n        bt->dp.emit();\n      }\n    } else {\n      if (!device.connected) {\n        bt->connected_devices_.erase(cur_device);\n      } else {\n        *cur_device = device;\n      }\n      bt->dp.emit();\n    }\n  }\n}\n\nauto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object)\n    -> std::optional<unsigned char> {\n  GDBusProxy* proxy_device_bat =\n      G_DBUS_PROXY(g_dbus_object_get_interface(object, \"org.bluez.Battery1\"));\n  if (proxy_device_bat != NULL) {\n    unsigned char battery_percentage = getUcharProperty(proxy_device_bat, \"Percentage\");\n    g_object_unref(proxy_device_bat);\n\n    return battery_percentage;\n  }\n  return std::nullopt;\n}\n\nauto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info)\n    -> bool {\n  GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, \"org.bluez.Device1\"));\n\n  if (proxy_device != NULL) {\n    device_info.path = g_dbus_object_get_object_path(object);\n    device_info.paired_controller = getStringProperty(proxy_device, \"Adapter\");\n    device_info.address = getStringProperty(proxy_device, \"Address\");\n    device_info.address_type = getStringProperty(proxy_device, \"AddressType\");\n    device_info.alias = getStringProperty(proxy_device, \"Alias\");\n    device_info.icon = getOptionalStringProperty(proxy_device, \"Icon\");\n    device_info.paired = getBoolProperty(proxy_device, \"Paired\");\n    device_info.trusted = getBoolProperty(proxy_device, \"Trusted\");\n    device_info.blocked = getBoolProperty(proxy_device, \"Blocked\");\n    device_info.connected = getBoolProperty(proxy_device, \"Connected\");\n    device_info.services_resolved = getBoolProperty(proxy_device, \"ServicesResolved\");\n\n    g_object_unref(proxy_device);\n\n    device_info.battery_percentage = getDeviceBatteryPercentage(object);\n\n    return true;\n  }\n  return false;\n}\n\nauto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object,\n                                                         ControllerInfo& controller_info) -> bool {\n  GDBusProxy* proxy_controller =\n      G_DBUS_PROXY(g_dbus_object_get_interface(object, \"org.bluez.Adapter1\"));\n\n  if (proxy_controller != NULL) {\n    controller_info.path = g_dbus_object_get_object_path(object);\n    controller_info.address = getStringProperty(proxy_controller, \"Address\");\n    controller_info.address_type = getStringProperty(proxy_controller, \"AddressType\");\n    controller_info.alias = getStringProperty(proxy_controller, \"Alias\");\n    controller_info.powered = getBoolProperty(proxy_controller, \"Powered\");\n    controller_info.discoverable = getBoolProperty(proxy_controller, \"Discoverable\");\n    controller_info.pairable = getBoolProperty(proxy_controller, \"Pairable\");\n    controller_info.discovering = getBoolProperty(proxy_controller, \"Discovering\");\n\n    g_object_unref(proxy_controller);\n\n    return true;\n  }\n  return false;\n}\n\nauto waybar::modules::Bluetooth::findCurController() -> std::optional<ControllerInfo> {\n  std::optional<ControllerInfo> controller_info;\n\n  if (!manager_) {\n    return controller_info;\n  }\n\n  GList* objects = g_dbus_object_manager_get_objects(manager_.get());\n  for (GList* l = objects; l != NULL; l = l->next) {\n    GDBusObject* object = G_DBUS_OBJECT(l->data);\n    ControllerInfo info;\n    if (getControllerProperties(object, info) &&\n        (!config_[\"controller-alias\"].isString() ||\n         config_[\"controller-alias\"].asString() == info.alias)) {\n      controller_info = std::move(info);\n      break;\n    }\n  }\n  g_list_free_full(objects, g_object_unref);\n\n  return controller_info;\n}\n\nauto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path,\n                                                      std::vector<DeviceInfo>& connected_devices)\n    -> void {\n  if (!manager_) {\n    return;\n  }\n  GList* objects = g_dbus_object_manager_get_objects(manager_.get());\n  for (GList* l = objects; l != NULL; l = l->next) {\n    GDBusObject* object = G_DBUS_OBJECT(l->data);\n    DeviceInfo device;\n    if (getDeviceProperties(object, device) && device.connected &&\n        device.paired_controller == cur_controller_->path) {\n      connected_devices.push_back(device);\n    }\n  }\n  g_list_free_full(objects, g_object_unref);\n}\n"
  },
  {
    "path": "src/modules/cava/cavaGLSL.cpp",
    "content": "#include \"modules/cava/cavaGLSL.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <fstream>\n\nwaybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config)\n    : AModule(config, \"cavaGLSL\", id, false, false),\n      backend_{waybar::modules::cava::CavaBackend::inst(config)} {\n  set_name(name_);\n  if (config_[\"hide_on_silence\"].isBool()) hide_on_silence_ = config_[\"hide_on_silence\"].asBool();\n  if (!id.empty()) {\n    get_style_context()->add_class(id);\n  }\n  get_style_context()->add_class(MODULE_CLASS);\n\n  set_use_es(true);\n  //  set_auto_render(true);\n  signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize));\n  signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false);\n\n  // Get parameters_config struct from the backend\n  prm_ = *backend_->getPrm();\n\n  // Set widget length\n  int length{0};\n  if (config_[\"min-length\"].isUInt())\n    length = config_[\"min-length\"].asUInt();\n  else if (config_[\"max-length\"].isUInt())\n    length = config_[\"max-length\"].asUInt();\n  else\n    length = prm_.sdl_width;\n\n  set_size_request(length, prm_.sdl_height);\n\n  // Subscribe for changes\n  backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate));\n  // Subscribe for silence\n  backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence));\n  event_box_.add(*this);\n}\n\nauto waybar::modules::cava::CavaGLSL::onUpdate(const ::cava::audio_raw& input) -> void {\n  Glib::signal_idle().connect_once([this, input]() {\n    m_data_ = std::make_shared<::cava::audio_raw>(input);\n    if (silence_) {\n      get_style_context()->remove_class(\"silent\");\n      if (!get_style_context()->has_class(\"updated\")) get_style_context()->add_class(\"updated\");\n      show();\n      silence_ = false;\n    }\n\n    queue_render();\n  });\n}\n\nauto waybar::modules::cava::CavaGLSL::onSilence() -> void {\n  Glib::signal_idle().connect_once([this]() {\n    if (!silence_) {\n      if (get_style_context()->has_class(\"updated\")) get_style_context()->remove_class(\"updated\");\n\n      if (hide_on_silence_) hide();\n      silence_ = true;\n      get_style_context()->add_class(\"silent\");\n      // Set clear color to black\n      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n      queue_render();\n    }\n  });\n}\n\nbool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr<Gdk::GLContext>& context) {\n  if (!m_data_) return true;\n  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n\n  glActiveTexture(GL_TEXTURE0);\n  glBindTexture(GL_TEXTURE_2D, texture_);\n  glUniform1i(glGetUniformLocation(shaderProgram_, \"inputTexture\"), 0);\n\n  glUniform1fv(uniform_bars_, m_data_->number_of_bars, m_data_->bars_raw);\n  glUniform1fv(uniform_previous_bars_, m_data_->number_of_bars, m_data_->previous_bars_raw);\n  glUniform1i(uniform_bars_count_, m_data_->number_of_bars);\n  ++frame_counter;\n  glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3);\n\n  //  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);\n\n  glBindFramebuffer(GL_FRAMEBUFFER, fbo_);\n  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n  return true;\n}\n\nvoid waybar::modules::cava::CavaGLSL::onRealize() {\n  make_current();\n  initShaders();\n  initGLSL();\n  initSurface();\n}\n\nstruct colors {\n  uint16_t R;\n  uint16_t G;\n  uint16_t B;\n};\n\nstatic void parse_color(char* color_string, struct colors* color) {\n  if (color_string[0] == '#') {\n    sscanf(++color_string, \"%02hx%02hx%02hx\", &color->R, &color->G, &color->B);\n  }\n}\n\nvoid waybar::modules::cava::CavaGLSL::initGLSL() {\n  GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, \"vertexPosition_modelspace\")};\n  if (gVertexPos2DLocation == -1) {\n    spdlog::error(\"{0}. Could not find vertex position shader variable\", name_);\n  }\n\n  glClearColor(0.f, 0.f, 0.f, 1.f);\n\n  GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};\n  GLint indexData[]{0, 1, 2, 3};\n\n  GLuint gVBO{0};\n  glGenBuffers(1, &gVBO);\n  glBindBuffer(GL_ARRAY_BUFFER, gVBO);\n  glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW);\n\n  GLuint gIBO{0};\n  glGenBuffers(1, &gIBO);\n  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);\n  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW);\n\n  GLuint gVAO{0};\n  glGenVertexArrays(1, &gVAO);\n  glBindVertexArray(gVAO);\n  glEnableVertexAttribArray(gVertexPos2DLocation);\n\n  glBindBuffer(GL_ARRAY_BUFFER, gVBO);\n  glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);\n\n  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO);\n\n  glGenFramebuffers(1, &fbo_);\n  glBindFramebuffer(GL_FRAMEBUFFER, fbo_);\n\n  // Create a texture to attach the framebuffer\n  glGenTextures(1, &texture_);\n  glBindTexture(GL_TEXTURE_2D, texture_);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, prm_.sdl_width, prm_.sdl_height, 0, GL_RGBA,\n               GL_UNSIGNED_BYTE, nullptr);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0);\n\n  // Check is framebuffer is complete\n  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {\n    spdlog::error(\"{0}. Framebuffer not complete\", name_);\n  }\n\n  // Unbind the framebuffer\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n  uniform_bars_ = glGetUniformLocation(shaderProgram_, \"bars\");\n  uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, \"previous_bars\");\n  uniform_bars_count_ = glGetUniformLocation(shaderProgram_, \"bars_count\");\n  uniform_time_ = glGetUniformLocation(shaderProgram_, \"shader_time\");\n\n  GLuint err{glGetError()};\n  if (err != 0) {\n    spdlog::error(\"{0}. Error on initGLSL: {1}\", name_, err);\n  }\n}\n\nvoid waybar::modules::cava::CavaGLSL::initSurface() {\n  colors color = {0};\n  GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, \"bg_color\")};\n  parse_color(prm_.bcolor, &color);\n  glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0,\n              (float)color.B / 255.0);\n  GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, \"fg_color\")};\n  parse_color(prm_.color, &color);\n  glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0,\n              (float)color.B / 255.0);\n  GLint uniform_res{glGetUniformLocation(shaderProgram_, \"u_resolution\")};\n  glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f);\n  GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, \"bar_width\")};\n  glUniform1i(uniform_bar_width, prm_.bar_width);\n  GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, \"bar_spacing\")};\n  glUniform1i(uniform_bar_spacing, prm_.bar_spacing);\n  GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, \"gradient_count\")};\n  glUniform1i(uniform_gradient_count, prm_.gradient_count);\n  GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, \"gradient_colors\")};\n  GLfloat gradient_colors[8][3];\n  for (int i{0}; i < prm_.gradient_count; ++i) {\n    parse_color(prm_.gradient_colors[i], &color);\n    gradient_colors[i][0] = (float)color.R / 255.0;\n    gradient_colors[i][1] = (float)color.G / 255.0;\n    gradient_colors[i][2] = (float)color.B / 255.0;\n  }\n  glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors);\n\n  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr);\n}\n\nvoid waybar::modules::cava::CavaGLSL::initShaders() {\n  shaderProgram_ = glCreateProgram();\n\n  GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)};\n  GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)};\n\n  glAttachShader(shaderProgram_, vertexShader);\n  glAttachShader(shaderProgram_, fragmentShader);\n\n  glLinkProgram(shaderProgram_);\n\n  glDeleteShader(vertexShader);\n  glDeleteShader(fragmentShader);\n\n  // Check for linking errors\n  GLint success, len;\n  glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success);\n  if (!success) {\n    glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len);\n    GLchar* infoLog{(char*)'\\0'};\n    glGetProgramInfoLog(shaderProgram_, len, &len, infoLog);\n    spdlog::error(\"{0}. Shader linking error: {1}\", name_, infoLog);\n  }\n\n  glReleaseShaderCompiler();\n  glUseProgram(shaderProgram_);\n}\n\nGLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) {\n  spdlog::debug(\"{0}. loadShader: {1}\", name_, fileName);\n\n  // Read shader source code from the file\n  std::ifstream shaderFile{fileName};\n\n  if (!shaderFile.is_open()) {\n    spdlog::error(\"{0}. Could not open shader file: {1}\", name_, fileName);\n  }\n\n  std::ostringstream buffer;\n  buffer << shaderFile.rdbuf();  // read file content into stringstream\n  std::string str{buffer.str()};\n  const char* shaderSource = str.c_str();\n  shaderFile.close();\n\n  GLuint shaderID{glCreateShader(type)};\n  if (shaderID == 0) spdlog::error(\"{0}. Error creating shader type: {0}\", type);\n  glShaderSource(shaderID, 1, &shaderSource, nullptr);\n  glCompileShader(shaderID);\n\n  // Check for compilation errors\n  GLint success, len;\n\n  glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);\n\n  if (!success) {\n    glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len);\n\n    GLchar* infoLog{(char*)'\\0'};\n    glGetShaderInfoLog(shaderID, len, nullptr, infoLog);\n    spdlog::error(\"{0}. Shader compilation error in {1}: {2}\", name_, fileName, infoLog);\n  }\n\n  return shaderID;\n}\n"
  },
  {
    "path": "src/modules/cava/cavaRaw.cpp",
    "content": "#include \"modules/cava/cavaRaw.hpp\"\n\n#include <spdlog/spdlog.h>\n\nwaybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"cava\", id, \"{}\", 60, false, false, false),\n      backend_{waybar::modules::cava::CavaBackend::inst(config)} {\n  if (config_[\"hide_on_silence\"].isBool()) hide_on_silence_ = config_[\"hide_on_silence\"].asBool();\n  if (config_[\"format_silent\"].isString()) format_silent_ = config_[\"format_silent\"].asString();\n\n  ascii_range_ = backend_->getAsciiRange();\n  backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate));\n  backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence));\n  backend_->Update();\n}\n\nauto waybar::modules::cava::Cava::doAction(const std::string& name) -> void {\n  if ((actionMap_[name])) {\n    (this->*actionMap_[name])();\n  } else\n    spdlog::error(\"Cava. Unsupported action \\\"{0}\\\"\", name);\n}\n\n// Cava actions\nvoid waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); }\nauto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void {\n  Glib::signal_idle().connect_once([this, input]() {\n    if (silence_) {\n      silence_ = false;\n      label_.get_style_context()->remove_class(\"silent\");\n      if (!label_.get_style_context()->has_class(\"updated\"))\n        label_.get_style_context()->add_class(\"updated\");\n    }\n    label_text_.clear();\n    for (auto& ch : input)\n      label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, \"\", ascii_range_ + 1));\n\n    label_.set_markup(label_text_);\n    label_.show();\n    ALabel::update();\n  });\n}\n\nauto waybar::modules::cava::Cava::onSilence() -> void {\n  Glib::signal_idle().connect_once([this]() {\n    if (!silence_) {\n      if (label_.get_style_context()->has_class(\"updated\"))\n        label_.get_style_context()->remove_class(\"updated\");\n\n      if (hide_on_silence_) {\n        // Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout\n        label_.set_markup(\"\");\n        label_.hide();\n      } else if (config_[\"format_silent\"].isString())\n        label_.set_markup(format_silent_);\n      silence_ = true;\n      label_.get_style_context()->add_class(\"silent\");\n    }\n  });\n}\n"
  },
  {
    "path": "src/modules/cava/cava_backend.cpp",
    "content": "#include \"modules/cava/cava_backend.hpp\"\n\n#include <spdlog/spdlog.h>\n\nstd::shared_ptr<waybar::modules::cava::CavaBackend> waybar::modules::cava::CavaBackend::inst(\n    const Json::Value& config) {\n  static auto* backend = new CavaBackend(config);\n  static std::shared_ptr<CavaBackend> backend_ptr{backend};\n  return backend_ptr;\n}\n\nwaybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(config) {\n  // Load waybar module config\n  loadConfig();\n  // Read audio source trough cava API. Cava orginizes this process via infinity loop\n  read_thread_ = [this] {\n    try {\n      input_source_(&audio_data_);\n    } catch (const std::runtime_error& e) {\n      spdlog::warn(\"Cava backend. Read source error: {0}\", e.what());\n    }\n    read_thread_.sleep_for(fetch_input_delay_);\n    loadConfig();\n  };\n  // Write outcoming data. Emit signals\n  out_thread_ = [this] {\n    doUpdate(false);\n    out_thread_.sleep_for(frame_time_milsec_);\n  };\n}\n\nwaybar::modules::cava::CavaBackend::~CavaBackend() {\n  out_thread_.stop();\n  read_thread_.stop();\n\n  freeBackend();\n}\n\nstatic bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {\n  if (delta == std::chrono::seconds{0}) {\n    delta += std::chrono::seconds{1};\n    delay += delta;\n    return true;\n  }\n  return false;\n}\n\nstatic bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {\n  if (delta > std::chrono::seconds{0}) {\n    delay -= delta;\n    delta -= std::chrono::seconds{1};\n    return true;\n  }\n  return false;\n}\n\nbool waybar::modules::cava::CavaBackend::isSilence() {\n  for (int i{0}; i < audio_data_.input_buffer_size; ++i) {\n    if (audio_data_.cava_in[i]) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nint waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; }\n\n// Process: execute cava\nvoid waybar::modules::cava::CavaBackend::invoke() {\n  pthread_mutex_lock(&audio_data_.lock);\n  ::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out,\n                       plan_);\n  if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;\n  pthread_mutex_unlock(&audio_data_.lock);\n}\n\n// Do transformation under raw data\nvoid waybar::modules::cava::CavaBackend::execute() {\n  invoke();\n  audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_);\n\n  if (re_paint_ == 1) {\n    output_.clear();\n    for (int i{0}; i < audio_raw_.number_of_bars; ++i) {\n      audio_raw_.previous_frame[i] = audio_raw_.bars[i];\n      output_.push_back(audio_raw_.bars[i]);\n      if (prm_.bar_delim != 0) output_.push_back(prm_.bar_delim);\n    }\n  }\n}\n\nvoid waybar::modules::cava::CavaBackend::doPauseResume() {\n  pthread_mutex_lock(&audio_data_.lock);\n  if (audio_data_.suspendFlag) {\n    audio_data_.suspendFlag = false;\n    pthread_cond_broadcast(&audio_data_.resumeCond);\n    downThreadDelay(frame_time_milsec_, suspend_silence_delay_);\n  } else {\n    audio_data_.suspendFlag = true;\n    upThreadDelay(frame_time_milsec_, suspend_silence_delay_);\n  }\n  pthread_mutex_unlock(&audio_data_.lock);\n  Update();\n}\n\nwaybar::modules::cava::CavaBackend::type_signal_update\nwaybar::modules::cava::CavaBackend::signal_update() {\n  return m_signal_update_;\n}\n\nwaybar::modules::cava::CavaBackend::type_signal_audio_raw_update\nwaybar::modules::cava::CavaBackend::signal_audio_raw_update() {\n  return m_signal_audio_raw_;\n}\n\nwaybar::modules::cava::CavaBackend::type_signal_silence\nwaybar::modules::cava::CavaBackend::signal_silence() {\n  return m_signal_silence_;\n}\n\nvoid waybar::modules::cava::CavaBackend::Update() { doUpdate(true); }\n\nvoid waybar::modules::cava::CavaBackend::doUpdate(bool force) {\n  if (audio_data_.suspendFlag && !force) return;\n\n  silence_ = isSilence();\n  if (!silence_) sleep_counter_ = 0;\n\n  if (silence_ && prm_.sleep_timer != 0) {\n    if (sleep_counter_ <=\n        (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {\n      ++sleep_counter_;\n      silence_ = false;\n    }\n  }\n\n  if (!silence_ || prm_.sleep_timer == 0) {\n    if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();\n    execute();\n    if (re_paint_ == 1 || force || prm_.continuous_rendering) {\n      m_signal_update_.emit(output_);\n      m_signal_audio_raw_.emit(audio_raw_);\n    }\n  } else {\n    if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update();\n    if (silence_ != silence_prev_ || force) m_signal_silence_.emit();\n  }\n  silence_prev_ = silence_;\n}\n\nvoid waybar::modules::cava::CavaBackend::freeBackend() {\n  if (plan_ != NULL) {\n    cava_destroy(plan_);\n    plan_ = NULL;\n  }\n\n  audio_raw_clean(&audio_raw_);\n  pthread_mutex_lock(&audio_data_.lock);\n  audio_data_.terminate = 1;\n  pthread_mutex_unlock(&audio_data_.lock);\n  free_config(&prm_);\n  free(audio_data_.source);\n  free(audio_data_.cava_in);\n}\n\nvoid waybar::modules::cava::CavaBackend::loadConfig() {\n  freeBackend();\n  // Load waybar module config\n  char cfgPath[PATH_MAX];\n  cfgPath[0] = '\\0';\n\n  if (config_[\"cava_config\"].isString()) strcpy(cfgPath, config_[\"cava_config\"].asString().data());\n  // Load cava config\n  error_.length = 0;\n\n  if (!load_config(cfgPath, &prm_, &error_)) {\n    spdlog::error(\"cava backend. Error loading config. {0}\", error_.message);\n    exit(EXIT_FAILURE);\n  }\n\n  // Override cava parameters by the user config\n  prm_.inAtty = 0;\n  auto const output{prm_.output};\n  // prm_.output = ::cava::output_method::OUTPUT_RAW;\n  if (prm_.data_format) free(prm_.data_format);\n  // Default to ascii for format-icons output; allow user override\n  prm_.data_format = strdup(\n      config_[\"data_format\"].isString() ? config_[\"data_format\"].asString().c_str() : \"ascii\");\n  if (config_[\"raw_target\"].isString()) {\n    if (prm_.raw_target) free(prm_.raw_target);\n    prm_.raw_target = strdup(config_[\"raw_target\"].asString().c_str());\n  }\n  prm_.ascii_range = config_[\"format-icons\"].size() - 1;\n\n  if (config_[\"bar_spacing\"].isInt()) prm_.bar_spacing = config_[\"bar_spacing\"].asInt();\n  if (config_[\"bar_width\"].isInt()) prm_.bar_width = config_[\"bar_width\"].asInt();\n  if (config_[\"bar_height\"].isInt()) prm_.bar_height = config_[\"bar_height\"].asInt();\n  prm_.orientation = ::cava::ORIENT_TOP;\n  prm_.xaxis = ::cava::xaxis_scale::NONE;\n  prm_.mono_opt = ::cava::AVERAGE;\n  prm_.autobars = 0;\n  if (config_[\"gravity\"].isInt()) prm_.gravity = config_[\"gravity\"].asInt();\n  if (config_[\"integral\"].isInt()) prm_.integral = config_[\"integral\"].asInt();\n\n  if (config_[\"framerate\"].isInt()) prm_.framerate = config_[\"framerate\"].asInt();\n  // Calculate delay for Update() thread\n  frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));\n  if (config_[\"autosens\"].isInt()) prm_.autosens = config_[\"autosens\"].asInt();\n  if (config_[\"sensitivity\"].isInt()) prm_.sens = config_[\"sensitivity\"].asInt();\n  if (config_[\"bars\"].isInt()) prm_.fixedbars = config_[\"bars\"].asInt();\n  if (config_[\"lower_cutoff_freq\"].isNumeric())\n    prm_.lower_cut_off = config_[\"lower_cutoff_freq\"].asLargestInt();\n  if (config_[\"higher_cutoff_freq\"].isNumeric())\n    prm_.upper_cut_off = config_[\"higher_cutoff_freq\"].asLargestInt();\n  if (config_[\"sleep_timer\"].isInt()) prm_.sleep_timer = config_[\"sleep_timer\"].asInt();\n  if (config_[\"method\"].isString())\n    prm_.input = ::cava::input_method_by_name(config_[\"method\"].asString().c_str());\n  if (config_[\"source\"].isString()) {\n    if (prm_.audio_source) free(prm_.audio_source);\n    prm_.audio_source = strdup(config_[\"source\"].asString().c_str());\n  }\n  if (config_[\"sample_rate\"].isNumeric()) prm_.samplerate = config_[\"sample_rate\"].asLargestInt();\n  if (config_[\"sample_bits\"].isInt()) prm_.samplebits = config_[\"sample_bits\"].asInt();\n  if (config_[\"stereo\"].isBool()) prm_.stereo = config_[\"stereo\"].asBool();\n  if (config_[\"reverse\"].isBool()) prm_.reverse = config_[\"reverse\"].asBool();\n  if (config_[\"bar_delimiter\"].isInt()) prm_.bar_delim = config_[\"bar_delimiter\"].asInt();\n  if (config_[\"monstercat\"].isBool()) prm_.monstercat = config_[\"monstercat\"].asBool();\n  if (config_[\"waves\"].isBool()) prm_.waves = config_[\"waves\"].asBool();\n  if (config_[\"noise_reduction\"].isDouble())\n    prm_.noise_reduction = config_[\"noise_reduction\"].asDouble();\n  if (config_[\"input_delay\"].isInt())\n    fetch_input_delay_ = std::chrono::seconds(config_[\"input_delay\"].asInt());\n  if (config_[\"gradient\"].isInt()) prm_.gradient = config_[\"gradient\"].asInt();\n  if (prm_.gradient == 0)\n    prm_.gradient_count = 0;\n  else if (config_[\"gradient_count\"].isInt())\n    prm_.gradient_count = config_[\"gradient_count\"].asInt();\n  if (config_[\"sdl_width\"].isInt()) prm_.sdl_width = config_[\"sdl_width\"].asInt();\n  if (config_[\"sdl_height\"].isInt()) prm_.sdl_height = config_[\"sdl_height\"].asInt();\n\n  audio_raw_.height = prm_.ascii_range;\n  audio_data_.format = -1;\n  audio_data_.rate = 0;\n  audio_data_.samples_counter = 0;\n  audio_data_.channels = 2;\n  audio_data_.IEEE_FLOAT = 0;\n  audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;\n  audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;\n  audio_data_.terminate = 0;\n  audio_data_.suspendFlag = false;\n  input_source_ = get_input(&audio_data_, &prm_);\n\n  if (!input_source_) {\n    spdlog::error(\"cava backend API didn't provide input audio source method\");\n    exit(EXIT_FAILURE);\n  }\n\n  prm_.output = ::cava::output_method::OUTPUT_RAW;\n\n  // Make cava parameters configuration\n  // Init cava plan, audio_raw structure\n  audio_raw_init(&audio_data_, &audio_raw_, &prm_, &plan_);\n  if (!plan_) spdlog::error(\"cava backend plan is not provided\");\n  audio_raw_.previous_frame[0] = -1;  // For first Update() call need to rePaint text message\n\n  prm_.output = output;\n}\n\nconst struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; }\nstd::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() {\n  return frame_time_milsec_;\n};\n"
  },
  {
    "path": "src/modules/cffi.cpp",
    "content": "#include \"modules/cffi.hpp\"\n\n#include <dlfcn.h>\n#include <json/value.h>\n\n#include <algorithm>\n#include <iostream>\n#include <type_traits>\n\nnamespace waybar::modules {\n\nCFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& config)\n    : AModule(config, name, id, true, true) {\n  const auto dynlib_path = config_[\"module_path\"].asString();\n  if (dynlib_path.empty()) {\n    throw std::runtime_error{\"Missing or empty 'module_path' in module config\"};\n  }\n\n  void* handle = dlopen(dynlib_path.c_str(), RTLD_LAZY);\n  if (handle == nullptr) {\n    throw std::runtime_error{std::string{\"Failed to load CFFI module: \"} + dlerror()};\n  }\n\n  // Fetch ABI version\n  auto wbcffi_version = reinterpret_cast<size_t*>(dlsym(handle, \"wbcffi_version\"));\n  if (wbcffi_version == nullptr) {\n    throw std::runtime_error{std::string{\"Missing wbcffi_version function: \"} + dlerror()};\n  }\n\n  // Fetch functions\n  if (*wbcffi_version == 1 || *wbcffi_version == 2) {\n    // Mandatory functions\n    hooks_.init = reinterpret_cast<InitFn*>(dlsym(handle, \"wbcffi_init\"));\n    if (!hooks_.init) {\n      throw std::runtime_error{std::string{\"Missing wbcffi_init function: \"} + dlerror()};\n    }\n    hooks_.deinit = reinterpret_cast<DenitFn*>(dlsym(handle, \"wbcffi_deinit\"));\n    if (!hooks_.deinit) {\n      throw std::runtime_error{std::string{\"Missing wbcffi_deinit function: \"} + dlerror()};\n    }\n    // Optional functions\n    if (auto fn = reinterpret_cast<UpdateFn*>(dlsym(handle, \"wbcffi_update\"))) {\n      hooks_.update = fn;\n    }\n    if (auto fn = reinterpret_cast<RefreshFn*>(dlsym(handle, \"wbcffi_refresh\"))) {\n      hooks_.refresh = fn;\n    }\n    if (auto fn = reinterpret_cast<DoActionFn*>(dlsym(handle, \"wbcffi_doaction\"))) {\n      hooks_.doAction = fn;\n    }\n  } else {\n    throw std::runtime_error{\"Unknown wbcffi_version \" + std::to_string(*wbcffi_version)};\n  }\n\n  // Prepare init() arguments\n  // Convert JSON values to string\n  std::vector<std::string> config_entries_stringstor;\n  const auto& keys = config.getMemberNames();\n  for (size_t i = 0; i < keys.size(); i++) {\n    const auto& value = config[keys[i]];\n    if (*wbcffi_version == 1) {\n      if (value.isConvertibleTo(Json::ValueType::stringValue)) {\n        config_entries_stringstor.push_back(value.asString());\n      } else {\n        config_entries_stringstor.push_back(value.toStyledString());\n      }\n    } else {\n      config_entries_stringstor.push_back(value.toStyledString());\n    }\n  }\n\n  // Prepare config_entries array\n  std::vector<ffi::wbcffi_config_entry> config_entries;\n  config_entries.reserve(keys.size());\n  for (size_t i = 0; i < keys.size(); i++) {\n    config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()});\n  }\n\n  ffi::wbcffi_init_info init_info = {\n      .obj = (ffi::wbcffi_module*)this,\n      .waybar_version = VERSION,\n      .get_root_widget =\n          [](ffi::wbcffi_module* obj) {\n            return dynamic_cast<Gtk::Container*>(&((CFFI*)obj)->event_box_)->gobj();\n          },\n      .queue_update = [](ffi::wbcffi_module* obj) { ((CFFI*)obj)->dp.emit(); },\n  };\n\n  // Call init\n  cffi_instance_ = hooks_.init(&init_info, config_entries.data(), config_entries.size());\n\n  // Handle init failures\n  if (cffi_instance_ == nullptr) {\n    throw std::runtime_error{\"Failed to initialize C ABI module\"};\n  }\n}\n\nCFFI::~CFFI() {\n  if (cffi_instance_ != nullptr) {\n    hooks_.deinit(cffi_instance_);\n  }\n}\n\nauto CFFI::update() -> void {\n  assert(cffi_instance_ != nullptr);\n  hooks_.update(cffi_instance_);\n\n  // Execute the on-update command set in config\n  AModule::update();\n}\n\nauto CFFI::refresh(int signal) -> void {\n  assert(cffi_instance_ != nullptr);\n  hooks_.refresh(cffi_instance_, signal);\n}\n\nauto CFFI::doAction(const std::string& name) -> void {\n  assert(cffi_instance_ != nullptr);\n  if (!name.empty()) {\n    hooks_.doAction(cffi_instance_, name.c_str());\n  }\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/clock.cpp",
    "content": "#include \"modules/clock.hpp\"\n\n#include <glib.h>\n#include <gtkmm/tooltip.h>\n#include <spdlog/spdlog.h>\n\n#include <chrono>\n#include <iomanip>\n#include <regex>\n#include <sstream>\n\n#include \"util/ustring_clen.hpp\"\n\n#ifdef HAVE_LANGINFO_1STDAY\n#include <langinfo.h>\n\n#include <clocale>\n#endif\n\nusing namespace date;\nnamespace fmt_lib = waybar::util::date::format;\n\nwaybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"clock\", id, \"{:%H:%M}\", 60, false, false, true),\n      m_locale_{std::locale(config_[\"locale\"].isString() ? config_[\"locale\"].asString() : \"\")},\n      m_tlpFmt_{(config_[\"tooltip-format\"].isString()) ? config_[\"tooltip-format\"].asString() : \"\"},\n      m_tooltip_{new Gtk::Label()},\n      cldInTooltip_{m_tlpFmt_.find(\"{\" + kCldPlaceholder + \"}\") != std::string::npos},\n      cldYearShift_{January / 1 / 1900},\n      cldMonShift_{year(1900) / January},\n      tzInTooltip_{m_tlpFmt_.find(\"{\" + kTZPlaceholder + \"}\") != std::string::npos},\n      tzCurrIdx_{0},\n      tzTooltipFormat_{config_[\"timezone-tooltip-format\"].isString()\n                           ? config_[\"timezone-tooltip-format\"].asString()\n                           : \"\"},\n      ordInTooltip_{m_tlpFmt_.find(\"{\" + kOrdPlaceholder + \"}\") != std::string::npos} {\n  m_tlpText_ = m_tlpFmt_;\n\n  if (config_[\"timezones\"].isArray() && !config_[\"timezones\"].empty()) {\n    for (const auto& zone_name : config_[\"timezones\"]) {\n      if (!zone_name.isString()) continue;\n      if (zone_name.asString().empty())\n        // local time should be shown\n        tzList_.push_back(nullptr);\n      else\n        try {\n          tzList_.push_back(locate_zone(zone_name.asString()));\n        } catch (const std::exception& e) {\n          spdlog::warn(\"Timezone: {0}. {1}\", zone_name.asString(), e.what());\n        }\n    }\n  } else if (config_[\"timezone\"].isString()) {\n    if (config_[\"timezone\"].asString().empty())\n      // local time should be shown\n      tzList_.push_back(nullptr);\n    else\n      try {\n        tzList_.push_back(locate_zone(config_[\"timezone\"].asString()));\n      } catch (const std::exception& e) {\n        spdlog::warn(\"Timezone: {0}. {1}\", config_[\"timezone\"].asString(), e.what());\n      }\n  }\n  if (!tzList_.size()) tzList_.push_back(nullptr);\n\n  // Calendar properties\n  if (cldInTooltip_) {\n    if (config_[kCldPlaceholder][\"mode\"].isString()) {\n      const std::string cfgMode{config_[kCldPlaceholder][\"mode\"].asString()};\n      const std::map<std::string, CldMode> monthModes{{\"month\", CldMode::MONTH},\n                                                      {\"year\", CldMode::YEAR}};\n      if (monthModes.find(cfgMode) != monthModes.end())\n        cldMode_ = monthModes.at(cfgMode);\n      else\n        spdlog::warn(\n            \"Clock calendar configuration mode \\\"{0}\\\" is not recognized. Mode = \\\"month\\\" is \"\n            \"using instead\",\n            cfgMode);\n    }\n\n    if (config_[kCldPlaceholder][\"iso8601\"].isBool()) {\n      iso8601Calendar_ = config_[kCldPlaceholder][\"iso8601\"].asBool();\n    }\n\n    if (config_[kCldPlaceholder][\"weeks-pos\"].isString()) {\n      if (config_[kCldPlaceholder][\"weeks-pos\"].asString() == \"left\") cldWPos_ = WS::LEFT;\n      if (config_[kCldPlaceholder][\"weeks-pos\"].asString() == \"right\") cldWPos_ = WS::RIGHT;\n    }\n    if (config_[kCldPlaceholder][\"format\"][\"months\"].isString())\n      fmtMap_.insert({0, config_[kCldPlaceholder][\"format\"][\"months\"].asString()});\n    else\n      fmtMap_.insert({0, \"{}\"});\n    if (config_[kCldPlaceholder][\"format\"][\"weekdays\"].isString())\n      fmtMap_.insert({1, config_[kCldPlaceholder][\"format\"][\"weekdays\"].asString()});\n    else\n      fmtMap_.insert({1, \"{}\"});\n\n    if (config_[kCldPlaceholder][\"format\"][\"days\"].isString())\n      fmtMap_.insert({2, config_[kCldPlaceholder][\"format\"][\"days\"].asString()});\n    else\n      fmtMap_.insert({2, \"{}\"});\n    if (config_[kCldPlaceholder][\"format\"][\"today\"].isString()) {\n      fmtMap_.insert({3, config_[kCldPlaceholder][\"format\"][\"today\"].asString()});\n      auto local_time = zoned_time{local_zone(), system_clock::now()}.get_local_time();\n      cldBaseDay_ = year_month_day{floor<days>(local_time)}.day();\n    } else\n      fmtMap_.insert({3, \"{}\"});\n    if (config_[kCldPlaceholder][\"format\"][\"weeks\"].isString() && cldWPos_ != WS::HIDDEN) {\n      const auto defaultFmt =\n          iso8601Calendar_ ? \"{:%V}\" : ((first_day_of_week() == Monday) ? \"{:%W}\" : \"{:%U}\");\n      fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder][\"format\"][\"weeks\"].asString(),\n                                            std::regex(\"\\\\{\\\\}\"), defaultFmt)});\n      Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex(\"</?[^>]+>|\\\\{.*\\\\}\"), \"\")};\n      cldWnLen_ += tmp.size();\n    } else {\n      if (cldWPos_ != WS::HIDDEN) {\n        const auto defaultFmt =\n            iso8601Calendar_ ? \"{:%V}\" : ((first_day_of_week() == Monday) ? \"{:%W}\" : \"{:%U}\");\n        fmtMap_.insert({4, defaultFmt});\n      } else {\n        cldWnLen_ = 0;\n      }\n    }\n    if (config_[kCldPlaceholder][\"mode-mon-col\"].isInt()) {\n      cldMonCols_ = config_[kCldPlaceholder][\"mode-mon-col\"].asInt();\n      if (cldMonCols_ == 0u || (12 % cldMonCols_) != 0u) {\n        spdlog::warn(\n            \"Clock calendar configuration mode-mon-col = {0} must be one of [1, 2, 3, 4, 6, 12]. \"\n            \"Value 3 is using instead\",\n            cldMonCols_);\n        cldMonCols_ = 3u;\n      }\n    } else\n      cldMonCols_ = 1;\n    if (config_[kCldPlaceholder][\"on-scroll\"].isInt()) {\n      cldShift_ = config_[kCldPlaceholder][\"on-scroll\"].asInt();\n      event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);\n      event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {\n        cldCurrShift_ = months{0};\n        return false;\n      });\n    }\n  }\n\n  if (tooltipEnabled()) {\n    label_.set_has_tooltip(true);\n    label_.signal_query_tooltip().connect(sigc::mem_fun(*this, &Clock::query_tlp_cb));\n  }\n\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_ - system_clock::now().time_since_epoch() % interval_);\n  };\n}\n\nbool waybar::modules::Clock::query_tlp_cb(int, int, bool,\n                                          const Glib::RefPtr<Gtk::Tooltip>& tooltip) {\n  tooltip->set_custom(*m_tooltip_.get());\n  return true;\n}\n\nauto waybar::modules::Clock::update() -> void {\n  const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : local_zone();\n  const zoned_time now{tz, floor<seconds>(system_clock::now())};\n\n  label_.set_markup(fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(now)));\n\n  if (tooltipEnabled()) {\n    const year_month_day today{floor<days>(now.get_local_time())};\n    const auto shiftedDay{today + cldCurrShift_};\n    const zoned_time shiftedNow{\n        tz, local_days(shiftedDay) + (now.get_local_time() - floor<days>(now.get_local_time()))};\n\n    if (tzInTooltip_) tzText_ = getTZtext(now.get_sys_time());\n    if (cldInTooltip_) cldText_ = get_calendar(today, shiftedDay, tz);\n    if (ordInTooltip_) ordText_ = get_ordinal_date(shiftedDay);\n    if (tzInTooltip_ || cldInTooltip_ || ordInTooltip_) {\n      // std::vformat doesn't support named arguments.\n      m_tlpText_ =\n          std::regex_replace(m_tlpFmt_, std::regex(\"\\\\{\" + kTZPlaceholder + \"\\\\}\"), tzText_);\n      m_tlpText_ = std::regex_replace(\n          m_tlpText_, std::regex(\"\\\\{\" + kCldPlaceholder + \"\\\\}\"),\n          fmt_lib::vformat(m_locale_, cldText_, fmt_lib::make_format_args(shiftedNow)));\n      m_tlpText_ =\n          std::regex_replace(m_tlpText_, std::regex(\"\\\\{\" + kOrdPlaceholder + \"\\\\}\"), ordText_);\n    } else {\n      m_tlpText_ = m_tlpFmt_;\n    }\n\n    m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(now));\n    m_tooltip_->set_markup(m_tlpText_);\n    label_.trigger_tooltip_query();\n  }\n\n  ALabel::update();\n}\n\nauto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string {\n  if (tzList_.size() == 1) return \"\";\n\n  std::stringstream os;\n  bool first = true;\n  for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) {\n    // Skip local timezone (nullptr) - never show it in tooltip\n    if (tzList_[tz_idx] == nullptr) continue;\n\n    // Skip current timezone unless timezone-tooltip-format is specified\n    if (static_cast<int>(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue;\n\n    const auto* tz = tzList_[tz_idx];\n    auto zt{zoned_time{tz, now}};\n\n    // Add newline before each entry except the first\n    if (!first) {\n      os << '\\n';\n    }\n    first = false;\n\n    // Use timezone-tooltip-format if specified, otherwise use format_\n    const std::string& fmt = tzTooltipFormat_.empty() ? format_ : tzTooltipFormat_;\n    os << fmt_lib::vformat(m_locale_, fmt, fmt_lib::make_format_args(zt));\n  }\n\n  return os.str();\n}\n\nconst unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) {\n  return 2u + ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count();\n}\n\nauto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line)\n    -> const year_month_weekday {\n  const unsigned idx = line - 2;\n  const auto indexed_first_day_of_week =\n      weekday{ym / 1} == firstdow ? firstdow[idx + 1] : firstdow[idx];\n\n  return ym / indexed_first_day_of_week;\n}\n\nauto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line,\n                     const weekday& firstdow, const std::locale* const m_locale_) -> std::string {\n  std::ostringstream os;\n\n  switch (line) {\n    // Print month and year title\n    case 0: {\n      os << date::format(*m_locale_, \"{:L%B %Y}\", ym);\n      break;\n    }\n    // Print weekday names title\n    case 1: {\n      auto wd{firstdow};\n      Glib::ustring wdStr;\n      Glib::ustring::size_type wdLen{0};\n      int clen{0};\n      do {\n        wdStr = date::format(*m_locale_, \"{:L%a}\", wd);\n        clen = ustring_clen(wdStr);\n        wdLen = wdStr.length();\n        while (clen > 2) {\n          wdStr = wdStr.substr(0, wdLen - 1);\n          --wdLen;\n          clen = ustring_clen(wdStr);\n        }\n        const std::string pad(2 - clen, ' ');\n\n        if (wd != firstdow) os << ' ';\n\n        os << pad << wdStr;\n      } while (++wd != firstdow);\n      break;\n    }\n    // Print first week prefixed with spaces if necessary\n    case 2: {\n      auto d{day{1}};\n      auto wd{weekday{ym / 1}};\n      os << std::string((wd - firstdow).count() * 3, ' ');\n\n      if (currDate != ym / d)\n        os << date::format(*m_locale_, \"{:L%e}\", d);\n      else\n        os << \"{today}\";\n\n      while (++wd != firstdow) {\n        ++d;\n\n        if (currDate != ym / d)\n          os << date::format(*m_locale_, \" {:L%e}\", d);\n        else\n          os << \" {today}\";\n      }\n      break;\n    }\n    // Print non-first week\n    default: {\n      const auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)};\n      if (ymdTmp.ok()) {\n        auto d{year_month_day{ymdTmp}.day()};\n        const auto dlast{(ym / last).day()};\n        auto wd{firstdow};\n\n        if (currDate != ym / d)\n          os << date::format(*m_locale_, \"{:L%e}\", d);\n        else\n          os << \"{today}\";\n\n        while (++wd != firstdow && ++d <= dlast) {\n          if (currDate != ym / d)\n            os << date::format(*m_locale_, \" {:L%e}\", d);\n          else\n            os << \" {today}\";\n        }\n        // Append row with spaces if the week was not completed\n        os << std::string((firstdow - wd).count() * 3, ' ');\n      }\n      break;\n    }\n  }\n\n  return os.str();\n}\n\nauto waybar::modules::Clock::get_calendar(const year_month_day& today, const year_month_day& ymd,\n                                          const time_zone* tz) -> const std::string {\n  const auto firstdow{first_day_of_week()};\n  const auto maxRows{12 / cldMonCols_};\n  const auto ym{ymd.year() / ymd.month()};\n  const auto y{ymd.year()};\n  const auto d{ymd.day()};\n\n  std::ostringstream os;\n  std::ostringstream tmp;\n\n  if (cldMode_ == CldMode::YEAR) {\n    if (y / month{1} / 1 == cldYearShift_)\n      if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)\n        return cldYearCached_;\n      else\n        cldBaseDay_ = d;\n    else\n      cldYearShift_ = y / month{1} / 1;\n  }\n  if (cldMode_ == CldMode::MONTH) {\n    if (ym == cldMonShift_)\n      if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)\n        return cldMonCached_;\n      else\n        cldBaseDay_ = d;\n    else\n      cldMonShift_ = ym;\n  }\n  // Pad object\n  const std::string pads(cldWnLen_, ' ');\n  // Compute number of lines needed for each calendar month\n  unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};\n\n  for (auto& m : ml) {\n    if (cldMode_ == CldMode::YEAR || m == static_cast<unsigned>(ymd.month()))\n      m = cldRowsInMonth(y / month{m}, firstdow);\n    else\n      m = 0u;\n  }\n\n  for (auto row{0u}; row < maxRows; ++row) {\n    const auto lines{*std::max_element(std::begin(ml) + (row * cldMonCols_),\n                                       std::begin(ml) + ((row + 1) * cldMonCols_))};\n    for (auto line{0u}; line < lines; ++line) {\n      for (auto col{0u}; col < cldMonCols_; ++col) {\n        const auto mon{month{row * cldMonCols_ + col + 1}};\n        if (cldMode_ == CldMode::YEAR || y / mon == ym) {\n          const year_month ymTmp{y / mon};\n          if (col != 0 && cldMode_ == CldMode::YEAR) os << std::string(3, ' ');\n\n          // Week numbers on the left\n          if (cldWPos_ == WS::LEFT && line > 0) {\n            if (line > 1) {\n              if (line < ml[(unsigned)ymTmp.month() - 1u]) {\n                os << fmt_lib::vformat(\n                          m_locale_, fmtMap_[4],\n                          fmt_lib::make_format_args(\n                              (line == 2)\n                                  ? static_cast<const zoned_seconds&&>(\n                                        zoned_seconds{tz, local_days{ymTmp / 1}})\n                                  : static_cast<const zoned_seconds&&>(zoned_seconds{\n                                        tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}})))\n                   << ' ';\n              } else {\n                os << pads;\n              }\n            }\n          }\n\n          // Count wide characters to avoid extra padding\n          size_t wideCharCount = 0;\n          std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_);\n          if (line < 2) {\n            for (gchar *data = calendarLine.data(), *end = data + calendarLine.size();\n                 data != nullptr;) {\n              gunichar c = g_utf8_get_char_validated(data, end - data);\n              if (g_unichar_iswide(c)) {\n                wideCharCount++;\n              }\n              data = g_utf8_find_next_char(data, end);\n            }\n          }\n          os << Glib::ustring::format(\n              (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '),\n              std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)),\n              calendarLine);\n\n          // Week numbers on the right\n          if (cldWPos_ == WS::RIGHT && line > 0) {\n            if (line > 1) {\n              if (line < ml[(unsigned)ymTmp.month() - 1u])\n                os << ' '\n                   << fmt_lib::vformat(\n                          m_locale_, fmtMap_[4],\n                          fmt_lib::make_format_args(\n                              (line == 2) ? static_cast<const zoned_seconds&&>(\n                                                zoned_seconds{tz, local_days{ymTmp / 1}})\n                                          : static_cast<const zoned_seconds&&>(\n                                                zoned_seconds{tz, local_days{cldGetWeekForLine(\n                                                                      ymTmp, firstdow, line)}})));\n              else\n                os << pads;\n            }\n          }\n        }\n      }\n      // Apply user's formats\n      if (line < 2)\n        tmp << fmt_lib::vformat(\n            m_locale_, fmtMap_[line],\n            fmt_lib::make_format_args(static_cast<const std::string_view&&>(os.str())));\n      else\n        tmp << os.str();\n      // Clear ostringstream\n      std::ostringstream().swap(os);\n      if (line + 1u != lines || (row + 1u != maxRows && cldMode_ == CldMode::YEAR)) tmp << '\\n';\n    }\n    if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\\n';\n  }\n\n  os << std::regex_replace(\n      fmt_lib::vformat(m_locale_, fmtMap_[2],\n                       fmt_lib::make_format_args(static_cast<const std::string_view&&>(tmp.str()))),\n      std::regex(\"\\\\{today\\\\}\"),\n      fmt_lib::vformat(m_locale_, fmtMap_[3],\n                       fmt_lib::make_format_args(\n                           static_cast<const std::string_view&&>(date::format(\"{:L%e}\", d)))));\n\n  if (cldMode_ == CldMode::YEAR)\n    cldYearCached_ = os.str();\n  else\n    cldMonCached_ = os.str();\n\n  return os.str();\n}\n\nauto waybar::modules::Clock::local_zone() -> const time_zone* {\n  const char* tz_name = getenv(\"TZ\");\n  if (tz_name) {\n    try {\n      return locate_zone(tz_name);\n    } catch (const std::runtime_error& e) {\n      spdlog::warn(\"Timezone: {0}. {1}\", tz_name, e.what());\n    }\n  }\n  return current_zone();\n}\n\n// Actions handler\nauto waybar::modules::Clock::doAction(const std::string& name) -> void {\n  if (actionMap_[name]) {\n    (this->*actionMap_[name])();\n  } else\n    spdlog::error(\"Clock. Unsupported action \\\"{0}\\\"\", name);\n}\n\n// Module actions\nvoid waybar::modules::Clock::cldModeSwitch() {\n  cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;\n}\nvoid waybar::modules::Clock::cldShift_up() {\n  cldCurrShift_ += (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;\n}\nvoid waybar::modules::Clock::cldShift_down() {\n  cldCurrShift_ -= (months)((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;\n}\nvoid waybar::modules::Clock::cldShift_reset() { cldCurrShift_ = (months)0; }\nvoid waybar::modules::Clock::tz_up() {\n  const auto tzSize{tzList_.size()};\n  if (tzSize == 1) return;\n  size_t newIdx{tzCurrIdx_ + 1lu};\n  tzCurrIdx_ = (newIdx == tzSize) ? 0 : newIdx;\n}\nvoid waybar::modules::Clock::tz_down() {\n  const auto tzSize{tzList_.size()};\n  if (tzSize == 1) return;\n  tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1;\n}\n\n#ifdef HAVE_LANGINFO_1STDAY\ntemplate <auto fn>\nusing deleter_from_fn = std::integral_constant<decltype(fn), fn>;\n\ntemplate <typename T, auto fn>\nusing deleting_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;\n#endif\n\n// Computations done similarly to Linux cal utility.\nauto waybar::modules::Clock::first_day_of_week() -> weekday {\n  if (iso8601Calendar_) {\n    return Monday;\n  }\n#ifdef HAVE_LANGINFO_1STDAY\n  deleting_unique_ptr<std::remove_pointer<locale_t>::type, freelocale> posix_locale{\n      newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)};\n  if (posix_locale) {\n    const auto i{(int)((std::intptr_t)nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get()))};\n    const weekday wd{year_month_day{year(i / 10000) / month(i / 100 % 100) / day(i % 100)}};\n    const auto j{(uint8_t)*nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get())};\n    return wd + days{j - 1};\n  }\n#endif\n  return Sunday;\n}\n\nauto waybar::modules::Clock::get_ordinal_date(const year_month_day& today) -> std::string {\n  auto day = static_cast<unsigned int>(today.day());\n  std::stringstream res;\n  res << day;\n  if (day >= 11 && day <= 13) {\n    res << \"th\";\n    return res.str();\n  }\n\n  switch (day % 10) {\n    case 1:\n      res << \"st\";\n      break;\n    case 2:\n      res << \"nd\";\n      break;\n    case 3:\n      res << \"rd\";\n      break;\n    default:\n      res << \"th\";\n  }\n  return res.str();\n}\n"
  },
  {
    "path": "src/modules/cpu.cpp",
    "content": "#include \"modules/cpu.hpp\"\n\n#include \"modules/cpu_frequency.hpp\"\n#include \"modules/cpu_usage.hpp\"\n#include \"modules/load.hpp\"\n\n// In the 80000 version of fmt library authors decided to optimize imports\n// and moved declarations required for fmt::dynamic_format_arg_store in new\n// header fmt/args.h\n#if (FMT_VERSION >= 80000)\n#include <fmt/args.h>\n#else\n#include <fmt/core.h>\n#endif\n\nwaybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"cpu\", id, \"{usage}%\", 10) {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::Cpu::update() -> void {\n  // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both\n  auto [load1, load5, load15] = Load::getLoad();\n  auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);\n  auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();\n\n  auto format = format_;\n  auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];\n  auto state = getState(total_usage);\n  if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    auto icons = std::vector<std::string>{state};\n    fmt::dynamic_format_arg_store<fmt::format_context> store;\n    store.push_back(fmt::arg(\"load\", load1));\n    store.push_back(fmt::arg(\"usage\", total_usage));\n    store.push_back(fmt::arg(\"icon\", getIcon(total_usage, icons)));\n    store.push_back(fmt::arg(\"max_frequency\", max_frequency));\n    store.push_back(fmt::arg(\"min_frequency\", min_frequency));\n    store.push_back(fmt::arg(\"avg_frequency\", avg_frequency));\n    std::vector<std::string> arg_names;\n    arg_names.reserve(cpu_usage.size() * 2);\n    for (size_t i = 1; i < cpu_usage.size(); ++i) {\n      auto core_i = i - 1;\n      arg_names.push_back(fmt::format(\"usage{}\", core_i));\n      store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));\n      arg_names.push_back(fmt::format(\"icon{}\", core_i));\n      store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));\n    }\n    label_.set_markup(fmt::vformat(format, store));\n\n    if (tooltipEnabled()) {\n      if (config_[\"tooltip-format\"].isString()) {\n        tooltip = config_[\"tooltip-format\"].asString();\n        label_.set_tooltip_markup(fmt::vformat(tooltip, store));\n      } else {\n        label_.set_tooltip_markup(tooltip);\n      }\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n"
  },
  {
    "path": "src/modules/cpu_frequency/bsd.cpp",
    "content": "#include <spdlog/spdlog.h>\n#include <sys/sysctl.h>\n\n#include \"modules/cpu_frequency.hpp\"\n\nstd::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {\n  std::vector<float> frequencies;\n  size_t len;\n  int32_t freq;\n\n#ifndef __OpenBSD__\n  char buffer[256];\n  uint32_t i = 0;\n  while (true) {\n    len = 4;\n    snprintf(buffer, 256, \"dev.cpu.%u.freq\", i);\n    if (sysctlbyname(buffer, &freq, &len, NULL, 0) == -1 || len <= 0) break;\n    frequencies.push_back(freq);\n    ++i;\n  }\n#else\n  int getMhz[] = {CTL_HW, HW_CPUSPEED};\n  len = sizeof(freq);\n  sysctl(getMhz, 2, &freq, &len, NULL, 0);\n  frequencies.push_back((float)freq);\n#endif\n\n  if (frequencies.empty()) {\n    spdlog::warn(\"cpu/bsd: parseCpuFrequencies failed, not found in sysctl\");\n    frequencies.push_back(NAN);\n  }\n\n  return frequencies;\n}\n"
  },
  {
    "path": "src/modules/cpu_frequency/common.cpp",
    "content": "#include \"modules/cpu_frequency.hpp\"\n\n// In the 80000 version of fmt library authors decided to optimize imports\n// and moved declarations required for fmt::dynamic_format_arg_store in new\n// header fmt/args.h\n#if (FMT_VERSION >= 80000)\n#include <fmt/args.h>\n#else\n#include <fmt/core.h>\n#endif\n\nwaybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"cpu_frequency\", id, \"{avg_frequency}\", 10) {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::CpuFrequency::update() -> void {\n  // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both\n  auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency();\n\n  auto format = format_;\n  auto state = getState(avg_frequency);\n  if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    auto icons = std::vector<std::string>{state};\n    fmt::dynamic_format_arg_store<fmt::format_context> store;\n    store.push_back(fmt::arg(\"icon\", getIcon(avg_frequency, icons)));\n    store.push_back(fmt::arg(\"max_frequency\", max_frequency));\n    store.push_back(fmt::arg(\"min_frequency\", min_frequency));\n    store.push_back(fmt::arg(\"avg_frequency\", avg_frequency));\n    label_.set_markup(fmt::vformat(format, store));\n\n    if (tooltipEnabled()) {\n      std::string tooltip;\n      if (config_[\"tooltip-format\"].isString()) {\n        tooltip = config_[\"tooltip-format\"].asString();\n        label_.set_tooltip_markup(fmt::vformat(tooltip, store));\n      } else {\n        tooltip = \"Minimum frequency: {}\\nAverage frequency: {}\\nMaximum frequency: {}\\n\";\n        label_.set_tooltip_markup(\n            fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency));\n      }\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nstd::tuple<float, float, float> waybar::modules::CpuFrequency::getCpuFrequency() {\n  std::vector<float> frequencies = CpuFrequency::parseCpuFrequencies();\n  if (frequencies.empty()) {\n    return {0.f, 0.f, 0.f};\n  }\n  auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));\n  float avg_frequency =\n      std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();\n\n  // Round frequencies with double decimal precision to get GHz\n  float max_frequency = std::ceil(*max / 10.0) / 100.0;\n  float min_frequency = std::ceil(*min / 10.0) / 100.0;\n  avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;\n\n  return {max_frequency, min_frequency, avg_frequency};\n}\n"
  },
  {
    "path": "src/modules/cpu_frequency/linux.cpp",
    "content": "#include <filesystem>\n\n#include \"modules/cpu_frequency.hpp\"\n\nstd::vector<float> waybar::modules::CpuFrequency::parseCpuFrequencies() {\n  const std::string file_path_ = \"/proc/cpuinfo\";\n  std::ifstream info(file_path_);\n  if (!info.is_open()) {\n    throw std::runtime_error(\"Can't open \" + file_path_);\n  }\n  std::vector<float> frequencies;\n  std::string line;\n  while (getline(info, line)) {\n    if (line.substr(0, 7).compare(\"cpu MHz\") != 0) {\n      continue;\n    }\n\n    std::string frequency_str = line.substr(line.find(\":\") + 2);\n    float frequency = std::strtol(frequency_str.c_str(), nullptr, 10);\n    frequencies.push_back(frequency);\n  }\n  info.close();\n\n  if (frequencies.size() <= 0) {\n    std::string cpufreq_dir = \"/sys/devices/system/cpu/cpufreq\";\n    if (std::filesystem::exists(cpufreq_dir)) {\n      std::vector<std::string> frequency_files = {\"/cpuinfo_min_freq\", \"/cpuinfo_max_freq\"};\n      for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) {\n        for (const auto& freq_file : frequency_files) {\n          std::string freq_file_path = p.path().string() + freq_file;\n          if (std::filesystem::exists(freq_file_path)) {\n            std::string freq_value;\n            std::ifstream freq(freq_file_path);\n            if (freq.is_open()) {\n              getline(freq, freq_value);\n              float frequency = std::strtol(freq_value.c_str(), nullptr, 10);\n              frequencies.push_back(frequency / 1000);\n              freq.close();\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return frequencies;\n}\n"
  },
  {
    "path": "src/modules/cpu_usage/bsd.cpp",
    "content": "#include <spdlog/spdlog.h>\n// clang-format off\n#include <sys/types.h>\n#include <sys/sysctl.h>\n// clang-format on\n#include <unistd.h>  // sysconf\n\n#include <cmath>    // NAN\n#include <cstdlib>  // malloc\n\n#include \"modules/cpu_usage.hpp\"\n#include \"util/scope_guard.hpp\"\n\n#if defined(__NetBSD__) || defined(__OpenBSD__)\n#include <sys/sched.h>\n#else\n#include <sys/resource.h>\n#endif\n\n#if defined(__NetBSD__)\ntypedef uint64_t cp_time_t;\n#else\ntypedef long cp_time_t;\n#endif\n#if defined(__NetBSD__) || defined(__OpenBSD__)\ntypedef uint64_t pcp_time_t;\n#else\ntypedef long pcp_time_t;\n#endif\n\nstd::vector<std::tuple<size_t, size_t>> waybar::modules::CpuUsage::parseCpuinfo() {\n  cp_time_t sum_cp_time[CPUSTATES];\n  size_t sum_sz = sizeof(sum_cp_time);\n  int ncpu = sysconf(_SC_NPROCESSORS_CONF);\n  size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t);\n  pcp_time_t *cp_time = static_cast<pcp_time_t*>(malloc(sz)), *pcp_time = cp_time;\n  waybar::util::ScopeGuard cp_time_deleter([cp_time]() {\n    if (cp_time) {\n      free(cp_time);\n    }\n  });\n#if defined(__NetBSD__)\n  int mib[] = {\n      CTL_KERN,\n      KERN_CP_TIME,\n  };\n  if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl kern.cp_time failed\");\n  }\n  for (int state = 0; state < CPUSTATES; state++) {\n    cp_time[state] = sum_cp_time[state];\n  }\n  pcp_time += CPUSTATES;\n  if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), pcp_time, &sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl kern.cp_time failed\");\n  }\n#elif defined(__OpenBSD__)\n  {\n    int mib[] = {\n        CTL_KERN,\n        KERN_CPTIME,\n    };\n    if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) {\n      throw std::runtime_error(\"sysctl kern.cp_time failed\");\n    }\n  }\n  for (int state = 0; state < CPUSTATES; state++) {\n    cp_time[state] = sum_cp_time[state];\n  }\n  pcp_time = cp_time;\n  sz /= ncpu + 1;\n  {\n    int mib[] = {\n        CTL_KERN,\n        KERN_CPTIME2,\n        0,\n    };\n    for (int cpu = 0; cpu < ncpu; cpu++) {\n      mib[2] = cpu;\n      pcp_time += CPUSTATES;\n      if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), pcp_time, &sz, NULL, 0)) {\n        throw std::runtime_error(\"sysctl kern.cp_time2 failed\");\n      }\n    }\n  }\n#else\n  if (sysctlbyname(\"kern.cp_time\", sum_cp_time, &sum_sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl kern.cp_time failed\");\n  }\n  for (int state = 0; state < CPUSTATES; state++) {\n    cp_time[state] = sum_cp_time[state];\n  }\n  pcp_time += CPUSTATES;\n  if (sysctlbyname(\"kern.cp_times\", pcp_time, &sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl kern.cp_times failed\");\n  }\n#endif\n  std::vector<std::tuple<size_t, size_t>> cpuinfo;\n  for (int cpu = 0; cpu < ncpu + 1; cpu++) {\n    pcp_time_t total = 0, *single_cp_time = &cp_time[cpu * CPUSTATES];\n    for (int state = 0; state < CPUSTATES; state++) {\n      total += single_cp_time[state];\n    }\n    cpuinfo.emplace_back(single_cp_time[CP_IDLE], total);\n  }\n  return cpuinfo;\n}\n"
  },
  {
    "path": "src/modules/cpu_usage/common.cpp",
    "content": "#include \"modules/cpu_usage.hpp\"\n\n// In the 80000 version of fmt library authors decided to optimize imports\n// and moved declarations required for fmt::dynamic_format_arg_store in new\n// header fmt/args.h\n#if (FMT_VERSION >= 80000)\n#include <fmt/args.h>\n#else\n#include <fmt/core.h>\n#endif\n\nwaybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"cpu_usage\", id, \"{usage}%\", 10) {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::CpuUsage::update() -> void {\n  // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both\n  auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_);\n\n  auto format = format_;\n  auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];\n  auto state = getState(total_usage);\n  if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    auto icons = std::vector<std::string>{state};\n    fmt::dynamic_format_arg_store<fmt::format_context> store;\n    store.push_back(fmt::arg(\"usage\", total_usage));\n    store.push_back(fmt::arg(\"icon\", getIcon(total_usage, icons)));\n    std::vector<std::string> arg_names;\n    arg_names.reserve(cpu_usage.size() * 2);\n    for (size_t i = 1; i < cpu_usage.size(); ++i) {\n      auto core_i = i - 1;\n      arg_names.push_back(fmt::format(\"usage{}\", core_i));\n      store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i]));\n      arg_names.push_back(fmt::format(\"icon{}\", core_i));\n      store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons)));\n    }\n    label_.set_markup(fmt::vformat(format, store));\n\n    if (tooltipEnabled()) {\n      if (config_[\"tooltip-format\"].isString()) {\n        tooltip = config_[\"tooltip-format\"].asString();\n        label_.set_tooltip_markup(fmt::vformat(tooltip, store));\n      } else {\n        label_.set_tooltip_markup(tooltip);\n      }\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nstd::tuple<std::vector<uint16_t>, std::string> waybar::modules::CpuUsage::getCpuUsage(\n    std::vector<std::tuple<size_t, size_t>>& prev_times) {\n  if (prev_times.empty()) {\n    prev_times = CpuUsage::parseCpuinfo();\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n  }\n  std::vector<std::tuple<size_t, size_t>> curr_times = CpuUsage::parseCpuinfo();\n  std::string tooltip;\n  std::vector<uint16_t> usage;\n\n  if (curr_times.size() != prev_times.size()) {\n    // The number of CPUs has changed, eg. due to CPU hotplug\n    // We don't know which CPU came up or went down\n    // so only give total usage (if we can)\n    if (!curr_times.empty() && !prev_times.empty()) {\n      auto [curr_idle, curr_total] = curr_times[0];\n      auto [prev_idle, prev_total] = prev_times[0];\n      const float delta_idle = curr_idle - prev_idle;\n      const float delta_total = curr_total - prev_total;\n      uint16_t tmp =\n          (delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;\n      tooltip = fmt::format(\"Total: {}%\\nCores: (pending)\", tmp);\n      usage.push_back(tmp);\n    } else {\n      tooltip = \"(pending)\";\n      usage.push_back(0);\n    }\n    prev_times = curr_times;\n    return {usage, tooltip};\n  }\n\n  for (size_t i = 0; i < curr_times.size(); ++i) {\n    auto [curr_idle, curr_total] = curr_times[i];\n    auto [prev_idle, prev_total] = prev_times[i];\n    if (i > 0 && (curr_total == 0 || prev_total == 0)) {\n      // This CPU is offline\n      tooltip = tooltip + fmt::format(\"\\nCore{}: offline\", i - 1);\n      usage.push_back(0);\n      continue;\n    }\n    const float delta_idle = curr_idle - prev_idle;\n    const float delta_total = curr_total - prev_total;\n    uint16_t tmp =\n        (delta_total > 0) ? static_cast<uint16_t>(100 * (1 - delta_idle / delta_total)) : 0;\n    if (i == 0) {\n      tooltip = fmt::format(\"Total: {}%\", tmp);\n    } else {\n      tooltip = tooltip + fmt::format(\"\\nCore{}: {}%\", i - 1, tmp);\n    }\n    usage.push_back(tmp);\n  }\n  prev_times = curr_times;\n  return {usage, tooltip};\n}\n"
  },
  {
    "path": "src/modules/cpu_usage/linux.cpp",
    "content": "#include <filesystem>\n\n#include \"modules/cpu_usage.hpp\"\n\nstd::vector<std::tuple<size_t, size_t>> waybar::modules::CpuUsage::parseCpuinfo() {\n  // Get the \"existing CPU count\" from /sys/devices/system/cpu/present\n  // Probably this is what the user wants the offline CPUs accounted from\n  // For further details see:\n  // https://www.kernel.org/doc/html/latest/core-api/cpu_hotplug.html\n  const std::string sys_cpu_present_path = \"/sys/devices/system/cpu/present\";\n  size_t cpu_present_last = 0;\n  std::ifstream cpu_present_file(sys_cpu_present_path);\n  std::string cpu_present_text;\n  if (cpu_present_file.is_open()) {\n    getline(cpu_present_file, cpu_present_text);\n    // This is a comma-separated list of ranges, eg. 0,2-4,7\n    size_t last_separator = cpu_present_text.find_last_of(\"-,\");\n    if (last_separator < cpu_present_text.size()) {\n      std::stringstream(cpu_present_text.substr(last_separator + 1)) >> cpu_present_last;\n    }\n  }\n\n  const std::string data_dir_ = \"/proc/stat\";\n  std::ifstream info(data_dir_);\n  if (!info.is_open()) {\n    throw std::runtime_error(\"Can't open \" + data_dir_);\n  }\n  std::vector<std::tuple<size_t, size_t>> cpuinfo;\n  std::string line;\n  size_t current_cpu_number = -1;  // First line is total, second line is cpu 0\n  while (getline(info, line)) {\n    if (line.substr(0, 3).compare(\"cpu\") != 0) {\n      break;\n    }\n    size_t line_cpu_number;\n    if (current_cpu_number >= 0) {\n      std::stringstream(line.substr(3)) >> line_cpu_number;\n      while (line_cpu_number > current_cpu_number) {\n        // Fill in 0 for offline CPUs missing inside the lines of /proc/stat\n        cpuinfo.emplace_back(0, 0);\n        current_cpu_number++;\n      }\n    }\n    std::stringstream sline(line.substr(5));\n    std::vector<size_t> times;\n    for (size_t time = 0; sline >> time; times.push_back(time));\n\n    size_t idle_time = 0;\n    size_t total_time = 0;\n    if (times.size() >= 5) {\n      // idle + iowait\n      idle_time = times[3] + times[4];\n      total_time = std::accumulate(times.begin(), times.end(), 0);\n    }\n    cpuinfo.emplace_back(idle_time, total_time);\n    current_cpu_number++;\n  }\n\n  while (cpu_present_last >= current_cpu_number) {\n    // Fill in 0 for offline CPUs missing after the lines of /proc/stat\n    cpuinfo.emplace_back(0, 0);\n    current_cpu_number++;\n  }\n\n  return cpuinfo;\n}\n"
  },
  {
    "path": "src/modules/custom.cpp",
    "content": "#include \"modules/custom.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <utility>\n\n#include \"util/scope_guard.hpp\"\n\nwaybar::modules::Custom::Custom(const std::string& name, const std::string& id,\n                                const Json::Value& config, const std::string& output_name)\n    : ALabel(config, \"custom-\" + name, id, \"{}\"),\n      name_(name),\n      output_name_(output_name),\n      id_(id),\n      tooltip_format_enabled_{config_[\"tooltip-format\"].isString()},\n      percentage_(0),\n      fp_(nullptr),\n      pid_(-1) {\n  if (config.isNull()) {\n    spdlog::warn(\"There is no configuration for 'custom/{}', element will be hidden\", name);\n  }\n  dp.emit();\n  if (!config_[\"signal\"].empty() && config_[\"interval\"].empty() &&\n      config_[\"restart-interval\"].empty()) {\n    waitingWorker();\n  } else if (interval_.count() > 0) {\n    delayWorker();\n  } else if (config_[\"exec\"].isString()) {\n    continuousWorker();\n  }\n}\n\nwaybar::modules::Custom::~Custom() {\n  if (pid_ != -1) {\n    killpg(pid_, SIGTERM);\n    waitpid(pid_, NULL, 0);\n    pid_ = -1;\n  }\n}\n\nvoid waybar::modules::Custom::delayWorker() {\n  thread_ = [this] {\n    for (int i : this->pid_children_) {\n      int status;\n      waitpid(i, &status, 0);\n    }\n\n    this->pid_children_.clear();\n\n    bool can_update = true;\n    if (config_[\"exec-if\"].isString()) {\n      output_ = util::command::execNoRead(config_[\"exec-if\"].asString());\n      if (output_.exit_code != 0) {\n        can_update = false;\n        dp.emit();\n      }\n    }\n    if (can_update) {\n      if (config_[\"exec\"].isString()) {\n        output_ = util::command::exec(config_[\"exec\"].asString(), output_name_);\n      }\n      dp.emit();\n    }\n    thread_.sleep_for(interval_);\n  };\n}\n\nvoid waybar::modules::Custom::continuousWorker() {\n  auto cmd = config_[\"exec\"].asString();\n  pid_ = -1;\n  fp_ = util::command::open(cmd, pid_, output_name_);\n  if (!fp_) {\n    throw std::runtime_error(\"Unable to open \" + cmd);\n  }\n  thread_ = [this, cmd] {\n    char* buff = nullptr;\n    waybar::util::ScopeGuard buff_deleter([&buff]() {\n      if (buff) {\n        free(buff);\n      }\n    });\n    size_t len = 0;\n    if (getline(&buff, &len, fp_) == -1) {\n      int exit_code = 1;\n      if (fp_) {\n        exit_code = WEXITSTATUS(util::command::close(fp_, pid_));\n        fp_ = nullptr;\n      }\n      if (exit_code != 0) {\n        output_ = {exit_code, \"\"};\n        dp.emit();\n        spdlog::error(\"{} stopped unexpectedly, is it endless?\", name_);\n      }\n      if (config_[\"restart-interval\"].isNumeric()) {\n        pid_ = -1;\n        thread_.sleep_for(std::chrono::milliseconds(\n            std::max(1L,  // Minimum 1ms due to millisecond precision\n                     static_cast<long>(config_[\"restart-interval\"].asDouble() * 1000))));\n        fp_ = util::command::open(cmd, pid_, output_name_);\n        if (!fp_) {\n          throw std::runtime_error(\"Unable to open \" + cmd);\n        }\n      } else {\n        thread_.stop();\n        return;\n      }\n    } else {\n      std::string output = buff;\n\n      // Remove last newline\n      if (!output.empty() && output[output.length() - 1] == '\\n') {\n        output.erase(output.length() - 1);\n      }\n      output_ = {0, output};\n      dp.emit();\n    }\n  };\n}\n\nvoid waybar::modules::Custom::waitingWorker() {\n  thread_ = [this] {\n    bool can_update = true;\n    if (config_[\"exec-if\"].isString()) {\n      output_ = util::command::execNoRead(config_[\"exec-if\"].asString());\n      if (output_.exit_code != 0) {\n        can_update = false;\n        dp.emit();\n      }\n    }\n    if (can_update) {\n      if (config_[\"exec\"].isString()) {\n        output_ = util::command::exec(config_[\"exec\"].asString(), output_name_);\n      }\n      dp.emit();\n    }\n    thread_.sleep();\n  };\n}\n\nvoid waybar::modules::Custom::refresh(int sig) {\n  if (config_[\"signal\"].isInt() && sig == SIGRTMIN + config_[\"signal\"].asInt()) {\n    thread_.wake_up();\n  }\n}\n\nvoid waybar::modules::Custom::handleEvent() {\n  if (!config_[\"exec-on-event\"].isBool() || config_[\"exec-on-event\"].asBool()) {\n    thread_.wake_up();\n  }\n}\n\nbool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {\n  auto ret = ALabel::handleScroll(e);\n  handleEvent();\n  return ret;\n}\n\nbool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {\n  auto ret = ALabel::handleToggle(e);\n  handleEvent();\n  return ret;\n}\n\nauto waybar::modules::Custom::update() -> void {\n  // Hide label if output is empty\n  if ((config_[\"exec\"].isString() || config_[\"exec-if\"].isString()) &&\n      (output_.out.empty() || output_.exit_code != 0)) {\n    event_box_.hide();\n  } else {\n    if (config_[\"return-type\"].asString() == \"json\") {\n      parseOutputJson();\n    } else {\n      parseOutputRaw();\n    }\n\n    try {\n      auto str = fmt::format(fmt::runtime(format_), fmt::arg(\"text\", text_), fmt::arg(\"alt\", alt_),\n                             fmt::arg(\"icon\", getIcon(percentage_, alt_)),\n                             fmt::arg(\"percentage\", percentage_));\n      if ((config_[\"hide-empty-text\"].asBool() && text_.empty()) || str.empty()) {\n        event_box_.hide();\n      } else {\n        label_.set_markup(str);\n        if (tooltipEnabled()) {\n          std::string tooltip_markup;\n          if (tooltip_format_enabled_) {\n            auto tooltip = config_[\"tooltip-format\"].asString();\n            tooltip_markup = fmt::format(fmt::runtime(tooltip), fmt::arg(\"text\", text_),\n                                         fmt::arg(\"tooltip\", tooltip_), fmt::arg(\"alt\", alt_),\n                                         fmt::arg(\"icon\", getIcon(percentage_, alt_)),\n                                         fmt::arg(\"percentage\", percentage_));\n          } else if (text_ == tooltip_) {\n            tooltip_markup = str;\n          } else {\n            tooltip_markup = tooltip_;\n          }\n\n          if (last_tooltip_markup_ != tooltip_markup) {\n            label_.set_tooltip_markup(tooltip_markup);\n            last_tooltip_markup_ = std::move(tooltip_markup);\n          }\n        }\n        auto style = label_.get_style_context();\n        auto classes = style->list_classes();\n        for (auto const& c : classes) {\n          if (c == id_) continue;\n          style->remove_class(c);\n        }\n        for (auto const& c : class_) {\n          style->add_class(c);\n        }\n        style->add_class(\"flat\");\n        style->add_class(\"text-button\");\n        style->add_class(MODULE_CLASS);\n        event_box_.show();\n      }\n    } catch (const fmt::format_error& e) {\n      if (std::strcmp(e.what(), \"cannot switch from manual to automatic argument indexing\") != 0)\n        throw;\n\n      throw fmt::format_error(\n          \"mixing manual and automatic argument indexing is no longer supported; \"\n          \"try replacing \\\"{}\\\" with \\\"{text}\\\" in your format specifier\");\n    }\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nvoid waybar::modules::Custom::parseOutputRaw() {\n  std::istringstream output(output_.out);\n  std::string line;\n  int i = 0;\n  while (getline(output, line)) {\n    Glib::ustring validated_line = line;\n    if (!validated_line.validate()) {\n      validated_line = validated_line.make_valid();\n    }\n\n    if (i == 0) {\n      if (config_[\"escape\"].isBool() && config_[\"escape\"].asBool()) {\n        text_ = Glib::Markup::escape_text(validated_line);\n        tooltip_ = Glib::Markup::escape_text(validated_line);\n      } else {\n        text_ = validated_line;\n        tooltip_ = validated_line;\n      }\n      tooltip_ = validated_line;\n      class_.clear();\n    } else if (i == 1) {\n      if (config_[\"escape\"].isBool() && config_[\"escape\"].asBool()) {\n        tooltip_ = Glib::Markup::escape_text(validated_line);\n      } else {\n        tooltip_ = validated_line;\n      }\n    } else if (i == 2) {\n      class_.push_back(validated_line);\n    } else {\n      break;\n    }\n    i++;\n  }\n}\n\nvoid waybar::modules::Custom::parseOutputJson() {\n  std::istringstream output(output_.out);\n  std::string line;\n  class_.clear();\n  while (getline(output, line)) {\n    auto parsed = parser_.parse(line);\n    if (config_[\"escape\"].isBool() && config_[\"escape\"].asBool()) {\n      text_ = Glib::Markup::escape_text(parsed[\"text\"].asString());\n    } else {\n      text_ = parsed[\"text\"].asString();\n    }\n    if (config_[\"escape\"].isBool() && config_[\"escape\"].asBool()) {\n      alt_ = Glib::Markup::escape_text(parsed[\"alt\"].asString());\n    } else {\n      alt_ = parsed[\"alt\"].asString();\n    }\n    if (config_[\"escape\"].isBool() && config_[\"escape\"].asBool()) {\n      tooltip_ = Glib::Markup::escape_text(parsed[\"tooltip\"].asString());\n    } else {\n      tooltip_ = parsed[\"tooltip\"].asString();\n    }\n    if (parsed[\"class\"].isString()) {\n      class_.push_back(parsed[\"class\"].asString());\n    } else if (parsed[\"class\"].isArray()) {\n      for (auto const& c : parsed[\"class\"]) {\n        class_.push_back(c.asString());\n      }\n    }\n    if (!parsed[\"percentage\"].asString().empty() && parsed[\"percentage\"].isNumeric()) {\n      percentage_ = (int)lround(parsed[\"percentage\"].asFloat());\n    } else {\n      percentage_ = 0;\n    }\n    break;\n  }\n}\n"
  },
  {
    "path": "src/modules/disk.cpp",
    "content": "#include \"modules/disk.hpp\"\n\nusing namespace waybar::util;\n\nwaybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"disk\", id, \"{}%\", 30), path_(\"/\") {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n  if (config[\"path\"].isString()) {\n    path_ = config[\"path\"].asString();\n  }\n  if (config[\"unit\"].isString()) {\n    unit_ = config[\"unit\"].asString();\n  }\n}\n\nauto waybar::modules::Disk::update() -> void {\n  struct statvfs /* {\n      unsigned long  f_bsize;    // filesystem block size\n      unsigned long  f_frsize;   // fragment size\n      fsblkcnt_t     f_blocks;   // size of fs in f_frsize units\n      fsblkcnt_t     f_bfree;    // # free blocks\n      fsblkcnt_t     f_bavail;   // # free blocks for unprivileged users\n      fsfilcnt_t     f_files;    // # inodes\n      fsfilcnt_t     f_ffree;    // # free inodes\n      fsfilcnt_t     f_favail;   // # free inodes for unprivileged users\n      unsigned long  f_fsid;     // filesystem ID\n      unsigned long  f_flag;     // mount flags\n      unsigned long  f_namemax;  // maximum filename length\n  }; */\n      stats;\n  int err = statvfs(path_.c_str(), &stats);\n\n  /* Conky options\n    fs_bar - Bar that shows how much space is used\n    fs_free - Free space on a file system\n    fs_free_perc - Free percentage of space\n    fs_size - File system size\n    fs_used - File system used space\n  */\n\n  if (err != 0 || stats.f_blocks == 0) {\n    event_box_.hide();\n    return;\n  }\n\n  float specific_free, specific_used, specific_total, divisor;\n\n  divisor = calc_specific_divisor(unit_);\n  specific_free = (stats.f_bavail * stats.f_frsize) / divisor;\n  specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor;\n  specific_total = (stats.f_blocks * stats.f_frsize) / divisor;\n\n  auto free = pow_format(stats.f_bavail * stats.f_frsize, \"B\", true);\n  auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, \"B\", true);\n  auto total = pow_format(stats.f_blocks * stats.f_frsize, \"B\", true);\n  auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;\n\n  auto format = format_;\n  auto state = getState(percentage_used);\n  if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    label_.set_markup(fmt::format(\n        fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg(\"free\", free),\n        fmt::arg(\"percentage_free\", stats.f_bavail * 100 / stats.f_blocks), fmt::arg(\"used\", used),\n        fmt::arg(\"percentage_used\", percentage_used), fmt::arg(\"total\", total),\n        fmt::arg(\"path\", path_), fmt::arg(\"specific_free\", specific_free),\n        fmt::arg(\"specific_used\", specific_used), fmt::arg(\"specific_total\", specific_total)));\n  }\n\n  if (tooltipEnabled()) {\n    std::string tooltip_format = \"{used} used out of {total} on {path} ({percentage_used}%)\";\n    if (config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    label_.set_tooltip_markup(fmt::format(\n        fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg(\"free\", free),\n        fmt::arg(\"percentage_free\", stats.f_bavail * 100 / stats.f_blocks), fmt::arg(\"used\", used),\n        fmt::arg(\"percentage_used\", percentage_used), fmt::arg(\"total\", total),\n        fmt::arg(\"path\", path_), fmt::arg(\"specific_free\", specific_free),\n        fmt::arg(\"specific_used\", specific_used), fmt::arg(\"specific_total\", specific_total)));\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nfloat waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) {\n  if (divisor == \"kB\") {\n    return 1000.0;\n  } else if (divisor == \"kiB\") {\n    return 1024.0;\n  } else if (divisor == \"MB\") {\n    return 1000.0 * 1000.0;\n  } else if (divisor == \"MiB\") {\n    return 1024.0 * 1024.0;\n  } else if (divisor == \"GB\") {\n    return 1000.0 * 1000.0 * 1000.0;\n  } else if (divisor == \"GiB\") {\n    return 1024.0 * 1024.0 * 1024.0;\n  } else if (divisor == \"TB\") {\n    return 1000.0 * 1000.0 * 1000.0 * 1000.0;\n  } else if (divisor == \"TiB\") {\n    return 1024.0 * 1024.0 * 1024.0 * 1024.0;\n  } else {  // default to Bytes if it is anything that we don't recongnise\n    return 1.0;\n  }\n}\n"
  },
  {
    "path": "src/modules/dwl/tags.cpp",
    "content": "#include \"modules/dwl/tags.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n#include <wayland-client.h>\n\n#include <algorithm>\n\n#include \"client.hpp\"\n#include \"dwl-ipc-unstable-v2-client-protocol.h\"\n\n#define TAG_INACTIVE 0\n#define TAG_ACTIVE 1\n#define TAG_URGENT 2\n\nnamespace waybar::modules::dwl {\n\n/* dwl stuff */\nwl_array tags, layouts;\n\nstatic uint num_tags = 0;\n\nstatic void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {\n  // Intentionally empty\n}\n\nstatic void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {\n  // Intentionally empty\n}\n\nstatic void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,\n                    uint32_t clients, uint32_t focused) {\n  static_cast<Tags*>(data)->handle_view_tags(tag, state, clients, focused);\n\n  num_tags = (state & ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) ? num_tags | (1 << tag)\n                                                           : num_tags & ~(1 << tag);\n}\n\nstatic void set_layout_symbol(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* layout) {\n  // Intentionally empty\n}\n\nstatic void title(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* title) {\n  // Intentionally empty\n}\n\nstatic void dwl_frame(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {\n  // Intentionally empty\n}\n\nstatic void set_layout(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t layout) {\n  // Intentionally empty\n}\n\nstatic void appid(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* appid) {\n  // Intentionally empty\n};\n\nstatic const zdwl_ipc_output_v2_listener output_status_listener_impl{\n    .toggle_visibility = toggle_visibility,\n    .active = active,\n    .tag = set_tag,\n    .layout = set_layout,\n    .title = title,\n    .appid = appid,\n    .layout_symbol = set_layout_symbol,\n    .frame = dwl_frame,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {\n    static_cast<Tags*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(\n        (zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));\n  }\n  if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 1);\n    static_cast<Tags*>(data)->seat_ =\n        static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  /* Ignore event */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nTags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::AModule(config, \"tags\", id, false, false),\n      status_manager_{nullptr},\n      seat_{nullptr},\n      bar_(bar),\n      box_{bar.orientation, 0},\n      output_status_{nullptr} {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  if (!status_manager_) {\n    spdlog::error(\"dwl_status_manager_v2 not advertised\");\n    return;\n  }\n\n  if (!seat_) {\n    spdlog::error(\"wl_seat not advertised\");\n  }\n\n  box_.set_name(\"tags\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  // Default to 9 tags, cap at 32\n  const uint32_t num_tags =\n      config[\"num-tags\"].isUInt() ? std::min<uint32_t>(32, config_[\"num-tags\"].asUInt()) : 9;\n\n  std::vector<std::string> tag_labels(num_tags);\n  for (uint32_t tag = 0; tag < num_tags; ++tag) {\n    tag_labels[tag] = std::to_string(tag + 1);\n  }\n  const Json::Value custom_labels = config[\"tag-labels\"];\n  if (custom_labels.isArray() && !custom_labels.empty()) {\n    for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {\n      tag_labels[tag] = custom_labels[tag].asString();\n    }\n  }\n\n  uint32_t i = 1;\n  for (const auto& tag_label : tag_labels) {\n    Gtk::Button& button = buttons_.emplace_back(tag_label);\n    button.set_relief(Gtk::RELIEF_NONE);\n    box_.pack_start(button, false, false, 0);\n    if (!config_[\"disable-click\"].asBool()) {\n      button.signal_clicked().connect(\n          sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));\n      button.signal_button_press_event().connect(\n          sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));\n    }\n    button.show();\n    i <<= 1;\n  }\n\n  struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n  output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);\n  zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);\n\n  zdwl_ipc_manager_v2_destroy(status_manager_);\n  status_manager_ = nullptr;\n}\n\nTags::~Tags() {\n  if (output_status_) {\n    zdwl_ipc_output_v2_destroy(output_status_);\n  }\n  if (status_manager_) {\n    zdwl_ipc_manager_v2_destroy(status_manager_);\n  }\n}\n\nvoid Tags::handle_primary_clicked(uint32_t tag) {\n  if (!output_status_) return;\n\n  zdwl_ipc_output_v2_set_tags(output_status_, tag, 1);\n}\n\nbool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {\n  if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {\n    if (!output_status_) return true;\n    zdwl_ipc_output_v2_set_tags(output_status_, num_tags ^ tag, 0);\n  }\n  return true;\n}\n\nvoid Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {\n  if (tag >= buttons_.size()) return;\n  // First clear all occupied state\n  auto& button = buttons_[tag];\n  if (clients) {\n    button.get_style_context()->add_class(\"occupied\");\n  } else {\n    button.get_style_context()->remove_class(\"occupied\");\n  }\n\n  if (clients & TAG_INACTIVE) {\n    button.get_style_context()->remove_class(\"empty\");\n  } else {\n    button.get_style_context()->add_class(\"empty\");\n  }\n\n  if (state & TAG_ACTIVE) {\n    button.get_style_context()->add_class(\"focused\");\n  } else {\n    button.get_style_context()->remove_class(\"focused\");\n  }\n\n  if (state & TAG_URGENT) {\n    button.get_style_context()->add_class(\"urgent\");\n  } else {\n    button.get_style_context()->remove_class(\"urgent\");\n  }\n}\n\n} /* namespace waybar::modules::dwl */\n"
  },
  {
    "path": "src/modules/dwl/window.cpp",
    "content": "#include \"modules/dwl/window.hpp\"\n\n#include <gdkmm/pixbuf.h>\n#include <glibmm/fileutils.h>\n#include <glibmm/keyfile.h>\n#include <glibmm/miscutils.h>\n#include <gtkmm/enums.h>\n#include <spdlog/spdlog.h>\n\n#include \"client.hpp\"\n#include \"dwl-ipc-unstable-v2-client-protocol.h\"\n#include \"glibmm/markup.h\"\n#include \"util/rewrite_string.hpp\"\n\nnamespace waybar::modules::dwl {\n\nstatic void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {\n  // Intentionally empty\n}\n\nstatic void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) {\n  // Intentionally empty\n}\n\nstatic void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state,\n                    uint32_t clients, uint32_t focused) {\n  // Intentionally empty\n}\n\nstatic void set_layout_symbol(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* layout) {\n  static_cast<Window*>(data)->handle_layout_symbol(layout);\n}\n\nstatic void title(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* title) {\n  static_cast<Window*>(data)->handle_title(title);\n}\n\nstatic void dwl_frame(void* data, zdwl_ipc_output_v2* zdwl_output_v2) {\n  static_cast<Window*>(data)->handle_frame();\n}\n\nstatic void set_layout(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t layout) {\n  static_cast<Window*>(data)->handle_layout(layout);\n}\n\nstatic void appid(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* appid) {\n  static_cast<Window*>(data)->handle_appid(appid);\n};\n\nstatic const zdwl_ipc_output_v2_listener output_status_listener_impl{\n    .toggle_visibility = toggle_visibility,\n    .active = active,\n    .tag = set_tag,\n    .layout = set_layout,\n    .title = title,\n    .appid = appid,\n    .layout_symbol = set_layout_symbol,\n    .frame = dwl_frame,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {\n    static_cast<Window*>(data)->status_manager_ = static_cast<struct zdwl_ipc_manager_v2*>(\n        (zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  /* Ignore event */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nWindow::Window(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"window\", id, \"{}\", 0, true), bar_(bar) {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  if (status_manager_ == nullptr) {\n    spdlog::error(\"dwl_status_manager_v2 not advertised\");\n    return;\n  }\n\n  struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n  output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);\n  zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);\n  zdwl_ipc_manager_v2_destroy(status_manager_);\n}\n\nWindow::~Window() {\n  if (output_status_ != nullptr) {\n    zdwl_ipc_output_v2_destroy(output_status_);\n  }\n}\n\nvoid Window::handle_title(const char* title) { title_ = Glib::Markup::escape_text(title); }\n\nvoid Window::handle_appid(const char* appid) { appid_ = Glib::Markup::escape_text(appid); }\n\nvoid Window::handle_layout_symbol(const char* layout_symbol) {\n  layout_symbol_ = Glib::Markup::escape_text(layout_symbol);\n}\n\nvoid Window::handle_layout(const uint32_t layout) { layout_ = layout; }\n\nvoid Window::handle_frame() {\n  label_.set_markup(waybar::util::rewriteString(\n      fmt::format(fmt::runtime(format_), fmt::arg(\"title\", title_),\n                  fmt::arg(\"layout\", layout_symbol_), fmt::arg(\"app_id\", appid_)),\n      config_[\"rewrite\"]));\n  updateAppIconName(appid_, \"\");\n  updateAppIcon();\n  if (tooltipEnabled()) {\n    label_.set_tooltip_markup(title_);\n  }\n}\n\n}  // namespace waybar::modules::dwl\n"
  },
  {
    "path": "src/modules/ext/workspace_manager.cpp",
    "content": "#include \"modules/ext/workspace_manager.hpp\"\n\n#include <gdk/gdkwayland.h>\n#include <gtkmm.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <iostream>\n#include <vector>\n\n#include \"client.hpp\"\n#include \"gtkmm/widget.h\"\n#include \"modules/ext/workspace_manager_binding.hpp\"\n\nnamespace waybar::modules::ext {\n\n// WorkspaceManager\n\nuint32_t WorkspaceManager::group_global_id = 0;\nuint32_t WorkspaceManager::workspace_global_id = 0;\nstd::map<std::string, std::string> Workspace::icon_map_;\n\nWorkspaceManager::WorkspaceManager(const std::string& id, const waybar::Bar& bar,\n                                   const Json::Value& config)\n    : waybar::AModule(config, \"workspaces\", id, false, false), bar_(bar), box_(bar.orientation, 0) {\n  add_registry_listener(this);\n\n  // parse configuration\n\n  const auto config_sort_by_number = config_[\"sort-by-number\"];\n  if (config_sort_by_number.isBool()) {\n    spdlog::warn(\"[ext/workspaces]: Prefer sort-by-id instead of sort-by-number\");\n    sort_by_id_ = config_sort_by_number.asBool();\n  }\n\n  const auto config_sort_by_id = config_[\"sort-by-id\"];\n  if (config_sort_by_id.isBool()) {\n    sort_by_id_ = config_sort_by_id.asBool();\n  }\n\n  const auto config_sort_by_name = config_[\"sort-by-name\"];\n  if (config_sort_by_name.isBool()) {\n    sort_by_name_ = config_sort_by_name.asBool();\n  }\n\n  const auto config_sort_by_coordinates = config_[\"sort-by-coordinates\"];\n  if (config_sort_by_coordinates.isBool()) {\n    sort_by_coordinates_ = config_sort_by_coordinates.asBool();\n  }\n\n  const auto config_all_outputs = config_[\"all-outputs\"];\n  if (config_all_outputs.isBool()) {\n    all_outputs_ = config_all_outputs.asBool();\n  }\n\n  // setup UI\n\n  box_.set_name(\"workspaces\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  spdlog::debug(\"[ext/workspaces]: Workspace manager created\");\n}\n\nWorkspaceManager::~WorkspaceManager() {\n  workspaces_.clear();\n  groups_.clear();\n\n  if (ext_manager_ != nullptr) {\n    auto* display = Client::inst()->wl_display;\n    // Send `stop` request and wait for one roundtrip.\n    ext_workspace_manager_v1_stop(ext_manager_);\n    wl_display_roundtrip(display);\n  }\n\n  if (ext_manager_ != nullptr) {\n    spdlog::warn(\"[ext/workspaces]: Destroying workspace manager before .finished event\");\n    ext_workspace_manager_v1_destroy(ext_manager_);\n  }\n\n  spdlog::debug(\"[ext/workspaces]: Workspace manager destroyed\");\n}\n\nvoid WorkspaceManager::register_manager(wl_registry* registry, uint32_t name, uint32_t version) {\n  if (ext_manager_ != nullptr) {\n    spdlog::warn(\"[ext/workspaces]: Register workspace manager again although already registered!\");\n    return;\n  }\n  if (version != 1) {\n    spdlog::warn(\"[ext/workspaces]: Using different workspace manager protocol version: {}\",\n                 version);\n  }\n\n  ext_manager_ = workspace_manager_bind(registry, name, version, this);\n}\n\nvoid WorkspaceManager::remove_workspace_group(uint32_t id) {\n  const auto it =\n      std::find_if(groups_.begin(), groups_.end(), [id](const auto& g) { return g->id() == id; });\n\n  if (it == groups_.end()) {\n    spdlog::warn(\"[ext/workspaces]: Can't find workspace group with id {}\", id);\n    return;\n  }\n\n  groups_.erase(it);\n}\n\nvoid WorkspaceManager::remove_workspace(uint32_t id) {\n  const auto it = std::find_if(workspaces_.begin(), workspaces_.end(),\n                               [id](const auto& w) { return w->id() == id; });\n\n  if (it == workspaces_.end()) {\n    spdlog::warn(\"[ext/workspaces]: Can't find workspace with id {}\", id);\n    return;\n  }\n\n  workspaces_.erase(it);\n}\n\nvoid WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1* handle) {\n  const auto new_id = ++group_global_id;\n  groups_.push_back(std::make_unique<WorkspaceGroup>(*this, handle, new_id));\n  spdlog::debug(\"[ext/workspaces]: Workspace group {} created\", new_id);\n}\n\nvoid WorkspaceManager::handle_workspace(ext_workspace_handle_v1* handle) {\n  const auto new_id = ++workspace_global_id;\n  const auto new_name = std::to_string(++workspace_name);\n  workspaces_.push_back(std::make_unique<Workspace>(config_, *this, handle, new_id, new_name));\n  set_needs_sorting();\n  spdlog::debug(\"[ext/workspaces]: Workspace {} created\", new_id);\n}\n\nvoid WorkspaceManager::handle_done() { dp.emit(); }\n\nvoid WorkspaceManager::handle_finished() {\n  spdlog::debug(\"[ext/workspaces]: Finishing workspace manager\");\n  ext_workspace_manager_v1_destroy(ext_manager_);\n  ext_manager_ = nullptr;\n}\n\nvoid WorkspaceManager::commit() const {\n  if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_);\n}\n\nvoid WorkspaceManager::update() {\n  spdlog::debug(\"[ext/workspaces]: Updating state\");\n\n  if (needs_sorting_) {\n    clear_buttons();\n    sort_workspaces();\n    needs_sorting_ = false;\n  }\n\n  update_buttons();\n  AModule::update();\n}\n\nbool WorkspaceManager::has_button(const Gtk::Button* button) {\n  const auto buttons = box_.get_children();\n  return std::find(buttons.begin(), buttons.end(), button) != buttons.end();\n}\n\nvoid WorkspaceManager::sort_workspaces() {\n  // determine if workspace ID's and names can be sort numerically or literally\n\n  auto is_numeric = [](const std::string& s) {\n    return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);\n  };\n\n  auto sort_by_workspace_id_numerically =\n      std::all_of(workspaces_.begin(), workspaces_.end(),\n                  [&](const auto& w) { return is_numeric(w->workspace_id()); });\n\n  auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(),\n                                              [&](const auto& w) { return is_numeric(w->name()); });\n\n  // sort based on configuration setting with sort-by-id as fallback\n\n  std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto& w1, const auto& w2) {\n    if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) {\n      if (w1->workspace_id() == w2->workspace_id()) {\n        return w1->id() < w2->id();\n      }\n      if (sort_by_workspace_id_numerically) {\n        // the idea is that phonetic compare can be applied just to numbers\n        // with same number of digits\n        return w1->workspace_id().size() < w2->workspace_id().size() ||\n               (w1->workspace_id().size() == w2->workspace_id().size() &&\n                w1->workspace_id() < w2->workspace_id());\n      }\n      return w1->workspace_id() < w2->workspace_id();\n    }\n\n    if (sort_by_name_) {\n      if (w1->name() == w2->name()) {\n        return w1->id() < w2->id();\n      }\n      if (sort_by_name_numerically) {\n        // see above about numeric sorting\n        return w1->name().size() < w2->name().size() ||\n               (w1->name().size() == w2->name().size() && w1->name() < w2->name());\n      }\n      return w1->name() < w2->name();\n    }\n\n    if (sort_by_coordinates_) {\n      if (w1->coordinates() == w2->coordinates()) {\n        return w1->id() < w2->id();\n      }\n      return w1->coordinates() < w2->coordinates();\n    }\n\n    return w1->id() < w2->id();\n  });\n}\n\nvoid WorkspaceManager::clear_buttons() {\n  for (const auto& workspace : workspaces_) {\n    if (has_button(&workspace->button())) {\n      box_.remove(workspace->button());\n    }\n  }\n}\n\nvoid WorkspaceManager::update_buttons() {\n  const auto* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n\n  // go through all workspace\n\n  for (const auto& workspace : workspaces_) {\n    const bool workspace_on_any_group_for_output =\n        std::any_of(groups_.begin(), groups_.end(), [&](const auto& group) {\n          const bool group_on_output = group->has_output(output) || all_outputs_;\n          const bool workspace_on_group = group->has_workspace(workspace->handle());\n          return group_on_output && workspace_on_group;\n        });\n    const bool bar_contains_button = has_button(&workspace->button());\n\n    // add or remove buttons if needed, update button state\n\n    if (workspace_on_any_group_for_output) {\n      if (!bar_contains_button) {\n        // add button to bar\n        box_.pack_start(workspace->button(), false, false);\n        workspace->button().show_all();\n      }\n      workspace->update();\n    } else {\n      if (bar_contains_button) {\n        // remove button from bar\n        box_.remove(workspace->button());\n      }\n    }\n  }\n}\n\n// WorkspaceGroup\n\nWorkspaceGroup::WorkspaceGroup(WorkspaceManager& manager, ext_workspace_group_handle_v1* handle,\n                               uint32_t id)\n    : workspaces_manager_(manager), ext_handle_(handle), id_(id) {\n  add_workspace_group_listener(ext_handle_, this);\n}\n\nWorkspaceGroup::~WorkspaceGroup() {\n  if (ext_handle_ != nullptr) {\n    ext_workspace_group_handle_v1_destroy(ext_handle_);\n  }\n  spdlog::debug(\"[ext/workspaces]: Workspace group {} destroyed\", id_);\n}\n\nbool WorkspaceGroup::has_output(const wl_output* output) {\n  return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end();\n}\n\nbool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1* workspace) {\n  return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end();\n}\n\nvoid WorkspaceGroup::handle_capabilities(uint32_t capabilities) {\n  spdlog::debug(\"[ext/workspaces]:     Capabilities for workspace group {}:\", id_);\n  if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) ==\n      capabilities) {\n    spdlog::debug(\"[ext/workspaces]:     - create-workspace\");\n  }\n}\n\nvoid WorkspaceGroup::handle_output_enter(wl_output* output) { outputs_.push_back(output); }\n\nvoid WorkspaceGroup::handle_output_leave(wl_output* output) {\n  const auto it = std::find(outputs_.begin(), outputs_.end(), output);\n  if (it != outputs_.end()) {\n    outputs_.erase(it);\n  }\n}\n\nvoid WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1* handle) {\n  workspaces_.push_back(handle);\n}\n\nvoid WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1* handle) {\n  const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle);\n  if (it != workspaces_.end()) {\n    workspaces_.erase(it);\n  }\n}\n\nvoid WorkspaceGroup::handle_removed() {\n  spdlog::debug(\"[ext/workspaces]: Removing workspace group {}\", id_);\n  workspaces_manager_.remove_workspace_group(id_);\n}\n\n// Workspace\n\nWorkspace::Workspace(const Json::Value& config, WorkspaceManager& manager,\n                     ext_workspace_handle_v1* handle, uint32_t id, const std::string& name)\n    : workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) {\n  add_workspace_listener(ext_handle_, this);\n\n  // parse configuration\n\n  const auto& config_active_only = config[\"active-only\"];\n  if (config_active_only.isBool()) {\n    active_only_ = config_active_only.asBool();\n  }\n\n  const auto& config_ignore_hidden = config[\"ignore-hidden\"];\n  if (config_ignore_hidden.isBool()) {\n    ignore_hidden_ = config_ignore_hidden.asBool();\n  }\n\n  const auto& config_format = config[\"format\"];\n  format_ = config_format.isString() ? config_format.asString() : \"{name}\";\n  with_icon_ = format_.find(\"{icon}\") != std::string::npos;\n\n  if (with_icon_ && icon_map_.empty()) {\n    const auto& format_icons = config[\"format-icons\"];\n    for (auto& n : format_icons.getMemberNames()) {\n      icon_map_.emplace(n, format_icons[n].asString());\n    }\n  }\n\n  const bool config_on_click = config[\"on-click\"].isString();\n  if (config_on_click) {\n    on_click_action_ = config[\"on-click\"].asString();\n  }\n  const bool config_on_click_middle = config[\"on-click-middle\"].isString();\n  if (config_on_click_middle) {\n    on_click_middle_action_ = config[\"on-click-middle\"].asString();\n  }\n  const bool config_on_click_right = config[\"on-click-right\"].isString();\n  if (config_on_click_right) {\n    on_click_right_action_ = config[\"on-click-right\"].asString();\n  }\n\n  // setup UI\n\n  if (config_on_click || config_on_click_middle || config_on_click_right) {\n    button_.add_events(Gdk::BUTTON_PRESS_MASK);\n    button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),\n                                                false);\n  }\n\n  button_.set_relief(Gtk::RELIEF_NONE);\n  content_.set_center_widget(label_);\n  button_.add(content_);\n}\n\nWorkspace::~Workspace() {\n  if (ext_handle_ != nullptr) {\n    ext_workspace_handle_v1_destroy(ext_handle_);\n  }\n  spdlog::debug(\"[ext/workspaces]: Workspace {} destroyed\", id_);\n}\n\nvoid Workspace::update() {\n  const auto style_context = button_.get_style_context();\n\n  // update style and visibility\n\n  if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {\n    style_context->remove_class(\"active\");\n  }\n  if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {\n    style_context->remove_class(\"urgent\");\n  }\n  if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {\n    style_context->remove_class(\"hidden\");\n  }\n\n  if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {\n    button_.set_visible(true);\n    style_context->add_class(\"active\");\n  }\n  if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) {\n    button_.set_visible(true);\n    style_context->add_class(\"urgent\");\n  }\n  if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) {\n    button_.set_visible(!active_only_ && !ignore_hidden_);\n    style_context->add_class(\"hidden\");\n  }\n  if (state_ == 0) {\n    button_.set_visible(!active_only_);\n  }\n\n  // update label\n  label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg(\"name\", name_),\n                                fmt::arg(\"id\", workspace_id_),\n                                fmt::arg(\"icon\", with_icon_ ? icon() : \"\")));\n}\n\nvoid Workspace::handle_id(const std::string& id) {\n  spdlog::debug(\"[ext/workspaces]:     ID for workspace {}: {}\", id_, id);\n  workspace_id_ = id;\n  workspace_manager_.set_needs_sorting();\n}\n\nvoid Workspace::handle_name(const std::string& name) {\n  spdlog::debug(\"[ext/workspaces]:     Name for workspace {}: {}\", id_, name);\n  name_ = name;\n  workspace_manager_.set_needs_sorting();\n}\n\nvoid Workspace::handle_coordinates(const std::vector<uint32_t>& coordinates) {\n  coordinates_ = coordinates;\n  workspace_manager_.set_needs_sorting();\n}\n\nvoid Workspace::handle_state(uint32_t state) { state_ = state; }\n\nvoid Workspace::handle_capabilities(uint32_t capabilities) {\n  spdlog::debug(\"[ext/workspaces]:     Capabilities for workspace {}:\", id_);\n  if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) {\n    spdlog::debug(\"[ext/workspaces]:     - activate\");\n  }\n  if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) {\n    spdlog::debug(\"[ext/workspaces]:     - deactivate\");\n  }\n  if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) {\n    spdlog::debug(\"[ext/workspaces]:     - remove\");\n  }\n  if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) {\n    spdlog::debug(\"[ext/workspaces]:     - assign\");\n  }\n}\n\nvoid Workspace::handle_removed() {\n  spdlog::debug(\"[ext/workspaces]: Removing workspace {}\", id_);\n  workspace_manager_.remove_workspace(id_);\n}\n\nbool Workspace::handle_clicked(const GdkEventButton* button) const {\n  std::string action;\n  if (button->button == GDK_BUTTON_PRIMARY) {\n    action = on_click_action_;\n  } else if (button->button == GDK_BUTTON_MIDDLE) {\n    action = on_click_middle_action_;\n  } else if (button->button == GDK_BUTTON_SECONDARY) {\n    action = on_click_right_action_;\n  }\n\n  if (action.empty()) {\n    return true;\n  }\n\n  if (action == \"activate\") {\n    ext_workspace_handle_v1_activate(ext_handle_);\n  } else if (action == \"deactivate\") {\n    ext_workspace_handle_v1_deactivate(ext_handle_);\n  } else if (action == \"close\") {\n    ext_workspace_handle_v1_remove(ext_handle_);\n  } else {\n    spdlog::warn(\"[ext/workspaces]: Unknown action {}\", action);\n  }\n  workspace_manager_.commit();\n  return true;\n}\n\nstd::string Workspace::icon() {\n  if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) {\n    const auto active_icon_it = icon_map_.find(\"active\");\n    if (active_icon_it != icon_map_.end()) {\n      return active_icon_it->second;\n    }\n  }\n\n  const auto named_icon_it = icon_map_.find(name_);\n  if (named_icon_it != icon_map_.end()) {\n    return named_icon_it->second;\n  }\n\n  const auto default_icon_it = icon_map_.find(\"default\");\n  if (default_icon_it != icon_map_.end()) {\n    return default_icon_it->second;\n  }\n\n  return name_;\n}\n\n}  // namespace waybar::modules::ext\n"
  },
  {
    "path": "src/modules/ext/workspace_manager_binding.cpp",
    "content": "#include \"modules/ext/workspace_manager_binding.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <cstdint>\n\n#include \"client.hpp\"\n#include \"modules/ext/workspace_manager.hpp\"\n\nnamespace waybar::modules::ext {\n\nstatic void handle_global(void* data, wl_registry* registry, uint32_t name, const char* interface,\n                          uint32_t version) {\n  if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) {\n    static_cast<WorkspaceManager*>(data)->register_manager(registry, name, version);\n  }\n}\n\nstatic void handle_global_remove(void* data, wl_registry* registry, uint32_t name) {\n  /* Nothing to do here */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nvoid add_registry_listener(void* data) {\n  wl_display* display = Client::inst()->wl_display;\n  wl_registry* registry = wl_display_get_registry(display);\n\n  wl_registry_add_listener(registry, &registry_listener_impl, data);\n  wl_display_roundtrip(display);\n}\n\nstatic void workspace_manager_handle_workspace_group(\n    void* data, ext_workspace_manager_v1* _, ext_workspace_group_handle_v1* workspace_group) {\n  static_cast<WorkspaceManager*>(data)->handle_workspace_group(workspace_group);\n}\n\nstatic void workspace_manager_handle_workspace(void* data, ext_workspace_manager_v1* _,\n                                               ext_workspace_handle_v1* workspace) {\n  static_cast<WorkspaceManager*>(data)->handle_workspace(workspace);\n}\n\nstatic void workspace_manager_handle_done(void* data, ext_workspace_manager_v1* _) {\n  static_cast<WorkspaceManager*>(data)->handle_done();\n}\n\nstatic void workspace_manager_handle_finished(void* data, ext_workspace_manager_v1* _) {\n  static_cast<WorkspaceManager*>(data)->handle_finished();\n}\n\nstatic const ext_workspace_manager_v1_listener workspace_manager_impl = {\n    .workspace_group = workspace_manager_handle_workspace_group,\n    .workspace = workspace_manager_handle_workspace,\n    .done = workspace_manager_handle_done,\n    .finished = workspace_manager_handle_finished,\n};\n\next_workspace_manager_v1* workspace_manager_bind(wl_registry* registry, uint32_t name,\n                                                 uint32_t version, void* data) {\n  auto* workspace_manager = static_cast<ext_workspace_manager_v1*>(\n      wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version));\n\n  if (workspace_manager)\n    ext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);\n  else\n    spdlog::error(\"Failed to register manager\");\n\n  return workspace_manager;\n}\n\nstatic void workspace_group_handle_capabilities(void* data, ext_workspace_group_handle_v1* _,\n                                                uint32_t capabilities) {\n  static_cast<WorkspaceGroup*>(data)->handle_capabilities(capabilities);\n}\n\nstatic void workspace_group_handle_output_enter(void* data, ext_workspace_group_handle_v1* _,\n                                                wl_output* output) {\n  static_cast<WorkspaceGroup*>(data)->handle_output_enter(output);\n}\n\nstatic void workspace_group_handle_output_leave(void* data, ext_workspace_group_handle_v1* _,\n                                                wl_output* output) {\n  static_cast<WorkspaceGroup*>(data)->handle_output_leave(output);\n}\n\nstatic void workspace_group_handle_workspace_enter(void* data, ext_workspace_group_handle_v1* _,\n                                                   ext_workspace_handle_v1* workspace) {\n  static_cast<WorkspaceGroup*>(data)->handle_workspace_enter(workspace);\n}\n\nstatic void workspace_group_handle_workspace_leave(void* data, ext_workspace_group_handle_v1* _,\n                                                   ext_workspace_handle_v1* workspace) {\n  static_cast<WorkspaceGroup*>(data)->handle_workspace_leave(workspace);\n}\n\nstatic void workspace_group_handle_removed(void* data, ext_workspace_group_handle_v1* _) {\n  static_cast<WorkspaceGroup*>(data)->handle_removed();\n}\n\nstatic const ext_workspace_group_handle_v1_listener workspace_group_impl = {\n    .capabilities = workspace_group_handle_capabilities,\n    .output_enter = workspace_group_handle_output_enter,\n    .output_leave = workspace_group_handle_output_leave,\n    .workspace_enter = workspace_group_handle_workspace_enter,\n    .workspace_leave = workspace_group_handle_workspace_leave,\n    .removed = workspace_group_handle_removed};\n\nvoid add_workspace_group_listener(ext_workspace_group_handle_v1* workspace_group_handle,\n                                  void* data) {\n  ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);\n}\n\nvoid workspace_handle_name(void* data, struct ext_workspace_handle_v1* _, const char* name) {\n  static_cast<Workspace*>(data)->handle_name(name);\n}\n\nvoid workspace_handle_id(void* data, struct ext_workspace_handle_v1* _, const char* id) {\n  static_cast<Workspace*>(data)->handle_id(id);\n}\n\nvoid workspace_handle_coordinates(void* data, struct ext_workspace_handle_v1* _,\n                                  struct wl_array* coordinates) {\n  std::vector<uint32_t> coords_vec;\n  auto coords = static_cast<uint32_t*>(coordinates->data);\n  for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {\n    coords_vec.push_back(coords[i]);\n  }\n\n  static_cast<Workspace*>(data)->handle_coordinates(coords_vec);\n}\n\nvoid workspace_handle_state(void* data, struct ext_workspace_handle_v1* workspace_handle,\n                            uint32_t state) {\n  static_cast<Workspace*>(data)->handle_state(state);\n}\n\nstatic void workspace_handle_capabilities(void* data,\n                                          struct ext_workspace_handle_v1* workspace_handle,\n                                          uint32_t capabilities) {\n  static_cast<Workspace*>(data)->handle_capabilities(capabilities);\n}\n\nvoid workspace_handle_removed(void* data, struct ext_workspace_handle_v1* workspace_handle) {\n  static_cast<Workspace*>(data)->handle_removed();\n}\n\nstatic const ext_workspace_handle_v1_listener workspace_impl = {\n    .id = workspace_handle_id,\n    .name = workspace_handle_name,\n    .coordinates = workspace_handle_coordinates,\n    .state = workspace_handle_state,\n    .capabilities = workspace_handle_capabilities,\n    .removed = workspace_handle_removed};\n\nvoid add_workspace_listener(ext_workspace_handle_v1* workspace_handle, void* data) {\n  ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);\n}\n}  // namespace waybar::modules::ext\n"
  },
  {
    "path": "src/modules/gamemode.cpp",
    "content": "#include \"modules/gamemode.hpp\"\n\n#include <fmt/core.h>\n#include <spdlog/spdlog.h>\n\n#include <cstdio>\n#include <cstring>\n#include <string>\n\n#include \"AModule.hpp\"\n#include \"giomm/dbusconnection.h\"\n#include \"giomm/dbusinterface.h\"\n#include \"giomm/dbusproxy.h\"\n#include \"giomm/dbuswatchname.h\"\n#include \"glibmm/error.h\"\n#include \"glibmm/ustring.h\"\n#include \"glibmm/variant.h\"\n#include \"glibmm/varianttype.h\"\n#include \"gtkmm/label.h\"\n#include \"gtkmm/tooltip.h\"\n#include \"util/gtk_icon.hpp\"\n\nnamespace waybar::modules {\nGamemode::Gamemode(const std::string& id, const Json::Value& config)\n    : AModule(config, \"gamemode\", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0), icon_(), label_() {\n  box_.pack_start(icon_);\n  box_.pack_start(label_);\n  box_.set_name(name_);\n  event_box_.add(box_);\n\n  // Tooltip\n  if (config_[\"tooltip\"].isBool()) {\n    tooltip = config_[\"tooltip\"].asBool();\n  }\n  box_.set_has_tooltip(tooltip);\n\n  // Tooltip Format\n  if (config_[\"tooltip-format\"].isString()) {\n    tooltip_format = config_[\"tooltip-format\"].asString();\n  }\n\n  // Hide when game count is 0\n  if (config_[\"hide-not-running\"].isBool()) {\n    hideNotRunning = config_[\"hide-not-running\"].asBool();\n  }\n\n  // Icon Name\n  if (config_[\"icon-name\"].isString()) {\n    iconName = config_[\"icon-name\"].asString();\n  }\n\n  // Icon Spacing\n  if (config_[\"icon-spacing\"].isUInt()) {\n    iconSpacing = config_[\"icon-spacing\"].asUInt();\n  }\n\n  // Whether to use icon or not\n  if (config_[\"use-icon\"].isBool()) {\n    useIcon = config_[\"use-icon\"].asBool();\n  }\n\n  // Icon Size\n  if (config_[\"icon-size\"].isUInt()) {\n    iconSize = config_[\"icon-size\"].asUInt();\n  }\n\n  // Format\n  if (config_[\"format\"].isString()) {\n    format = config_[\"format\"].asString();\n  }\n\n  // Format Alt\n  if (config_[\"format-alt\"].isString()) {\n    format_alt = config_[\"format-alt\"].asString();\n  }\n\n  // Glyph\n  if (config_[\"glyph\"].isString()) {\n    glyph = config_[\"glyph\"].asString();\n  }\n\n  gamemodeWatcher_id = Gio::DBus::watch_name(\n      Gio::DBus::BUS_TYPE_SESSION, dbus_name, sigc::mem_fun(*this, &Gamemode::appear),\n      sigc::mem_fun(*this, &Gamemode::disappear),\n      Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START);\n\n  // Connect to gamemode\n  gamemode_proxy = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BusType::BUS_TYPE_SESSION,\n                                                         dbus_name, dbus_obj_path, dbus_interface);\n  if (!gamemode_proxy) {\n    throw std::runtime_error(\"Unable to connect to gamemode DBus!...\");\n  } else {\n    gamemode_proxy->signal_signal().connect(sigc::mem_fun(*this, &Gamemode::notify_cb));\n  }\n\n  // Connect to Login1 PrepareForSleep signal\n  system_connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SYSTEM);\n  if (!system_connection) {\n    throw std::runtime_error(\"Unable to connect to the SYSTEM Bus!...\");\n  } else {\n    login1_id = system_connection->signal_subscribe(\n        sigc::mem_fun(*this, &Gamemode::prepareForSleep_cb), \"org.freedesktop.login1\",\n        \"org.freedesktop.login1.Manager\", \"PrepareForSleep\", \"/org/freedesktop/login1\");\n  }\n\n  event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Gamemode::handleToggle));\n}\n\nGamemode::~Gamemode() {\n  if (gamemode_proxy) gamemode_proxy.reset();\n  if (gamemodeWatcher_id > 0) {\n    Gio::DBus::unwatch_name(gamemodeWatcher_id);\n    gamemodeWatcher_id = 0;\n  }\n  if (login1_id > 0) {\n    system_connection->signal_unsubscribe(login1_id);\n    login1_id = 0;\n  }\n}\n\n// Gets the DBus ClientCount\nvoid Gamemode::getData() {\n  if (gamemodeRunning && gamemode_proxy) {\n    try {\n      // Get game count\n      auto parameters = Glib::VariantContainerBase(\n          g_variant_new(\"(ss)\", dbus_get_interface.c_str(), \"ClientCount\"));\n      Glib::VariantContainerBase data = gamemode_proxy->call_sync(\"Get\", parameters);\n      if (data && data.is_of_type(Glib::VariantType(\"(v)\"))) {\n        Glib::VariantBase variant;\n        g_variant_get(const_cast<GVariant*>(data.gobj()), \"(v)\", &variant);\n        if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) {\n          g_variant_get(const_cast<GVariant*>(variant.gobj()), \"i\", &gameCount);\n          return;\n        }\n      }\n    } catch (Glib::Error& e) {\n      spdlog::error(\"Gamemode Error {}\", e.what().c_str());\n    }\n  }\n  gameCount = 0;\n}\n\n// Whenever the DBus ClientCount changes\nvoid Gamemode::notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                         const Glib::VariantContainerBase& arguments) {\n  if (signal_name == \"PropertiesChanged\") {\n    getData();\n    dp.emit();\n  }\n}\n\nvoid Gamemode::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                                  const Glib::ustring& sender_name,\n                                  const Glib::ustring& object_path,\n                                  const Glib::ustring& interface_name,\n                                  const Glib::ustring& signal_name,\n                                  const Glib::VariantContainerBase& parameters) {\n  if (parameters.is_of_type(Glib::VariantType(\"(b)\"))) {\n    gboolean sleeping;\n    g_variant_get(const_cast<GVariant*>(parameters.gobj()), \"(b)\", &sleeping);\n    if (!sleeping) {\n      getData();\n      dp.emit();\n    }\n  }\n}\n\n// When the gamemode name appears\nvoid Gamemode::appear(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                      const Glib::ustring& name, const Glib::ustring& name_owner) {\n  gamemodeRunning = true;\n  event_box_.set_visible(true);\n  getData();\n  dp.emit();\n}\n// When the gamemode name disappears\nvoid Gamemode::disappear(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                         const Glib::ustring& name) {\n  gamemodeRunning = false;\n  event_box_.set_visible(false);\n}\n\nbool Gamemode::handleToggle(GdkEventButton* const& event) {\n  showAltText = !showAltText;\n  dp.emit();\n  return true;\n}\n\nauto Gamemode::update() -> void {\n  // Don't update widget if the Gamemode service isn't running\n  if (!gamemodeRunning || (gameCount <= 0 && hideNotRunning)) {\n    event_box_.set_visible(false);\n    return;\n  }\n\n  // Show the module\n  if (!event_box_.get_visible()) event_box_.set_visible(true);\n\n  // CSS status class\n  const std::string status = gamemodeRunning && gameCount > 0 ? \"running\" : \"\";\n  // Remove last status if it exists\n  if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {\n    box_.get_style_context()->remove_class(lastStatus);\n  }\n  // Add the new status class to the Box\n  if (!status.empty() && !box_.get_style_context()->has_class(status)) {\n    box_.get_style_context()->add_class(status);\n  }\n  lastStatus = status;\n\n  // Tooltip\n  if (tooltip) {\n    std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg(\"count\", gameCount));\n    box_.set_tooltip_markup(text);\n  }\n\n  // Label format\n  std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),\n                                fmt::arg(\"glyph\", useIcon ? \"\" : glyph),\n                                fmt::arg(\"count\", gameCount > 0 ? std::to_string(gameCount) : \"\"));\n  label_.set_markup(str);\n\n  if (useIcon) {\n    if (!DefaultGtkIconThemeWrapper::has_icon(iconName)) {\n      iconName = DEFAULT_ICON_NAME;\n    }\n    icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);\n    box_.set_spacing(iconSpacing);\n    icon_.set_pixel_size(iconSize);\n  } else {\n    box_.set_spacing(0);\n    icon_.set_pixel_size(0);\n  }\n\n  // Call parent update\n  AModule::update();\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/gps.cpp",
    "content": "#include \"modules/gps.hpp\"\n\n#include <gps.h>\n#include <spdlog/spdlog.h>\n\n#include <cmath>\n#include <cstdio>\n\n// In the 80000 version of fmt library authors decided to optimize imports\n// and moved declarations required for fmt::dynamic_format_arg_store in new\n// header fmt/args.h\n#if (FMT_VERSION >= 80000)\n#include <fmt/args.h>\n#else\n#include <fmt/core.h>\n#endif\n\nnamespace {\nusing namespace waybar::util;\nconstexpr const char* DEFAULT_FORMAT = \"{mode}\";\n}  // namespace\n\nwaybar::modules::Gps::Gps(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"gps\", id, \"{}\", 5)\n#ifdef WANT_RFKILL\n      ,\n      rfkill_{RFKILL_TYPE_GPS}\n#endif\n{\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n\n  if (0 != gps_open(\"localhost\", \"2947\", &gps_data_)) {\n    throw std::runtime_error(\"Can't open gpsd socket\");\n  }\n\n  if (config_[\"hide-disconnected\"].isBool()) {\n    hideDisconnected = config_[\"hide-disconnected\"].asBool();\n  }\n\n  if (config_[\"hide-no-fix\"].isBool()) {\n    hideNoFix = config_[\"hide-no-fix\"].asBool();\n  }\n\n  gps_thread_ = [this] {\n    dp.emit();\n    gps_stream(&gps_data_, WATCH_ENABLE, NULL);\n    int last_gps_mode = 0;\n\n    while (gps_waiting(&gps_data_, 5000000)) {\n      if (gps_read(&gps_data_, NULL, 0) == -1) {\n        throw std::runtime_error(\"Can't read data from gpsd.\");\n      }\n\n      if (MODE_SET != (MODE_SET & gps_data_.set)) {\n        // did not even get mode, nothing to see here\n        continue;\n      }\n\n      if (gps_data_.fix.mode != last_gps_mode) {\n        // significant update\n        dp.emit();\n      }\n      last_gps_mode = gps_data_.fix.mode;\n    }\n  };\n\n#ifdef WANT_RFKILL\n  rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update)));\n#endif\n}\n\nconst std::string waybar::modules::Gps::getFixModeName() const {\n  switch (gps_data_.fix.mode) {\n    case MODE_NO_FIX:\n      return \"fix-none\";\n    case MODE_2D:\n      return \"fix-2d\";\n    case MODE_3D:\n      return \"fix-3d\";\n    default:\n#ifdef WANT_RFKILL\n      if (rfkill_.getState()) return \"disabled\";\n#endif\n      return \"disconnected\";\n  }\n}\n\nconst std::string waybar::modules::Gps::getFixModeString() const {\n  switch (gps_data_.fix.mode) {\n    case MODE_NO_FIX:\n      return \"No fix\";\n    case MODE_2D:\n      return \"2D Fix\";\n    case MODE_3D:\n      return \"3D Fix\";\n    default:\n      return \"Disconnected\";\n  }\n}\n\nconst std::string waybar::modules::Gps::getFixStatusString() const {\n  switch (gps_data_.fix.status) {\n    case STATUS_GPS:\n      return \"GPS\";\n    case STATUS_DGPS:\n      return \"DGPS\";\n    case STATUS_RTK_FIX:\n      return \"RTK Fixed\";\n    case STATUS_RTK_FLT:\n      return \"RTK Float\";\n    case STATUS_DR:\n      return \"Dead Reckoning\";\n    case STATUS_GNSSDR:\n      return \"GNSS + Dead Reckoning\";\n    case STATUS_TIME:\n      return \"Time Only\";\n    case STATUS_PPS_FIX:\n      return \"PPS Fix\";\n    default:\n\n#ifdef WANT_RFKILL\n      if (rfkill_.getState()) return \"Disabled\";\n#endif\n\n      return \"Unknown\";\n  }\n}\n\nauto waybar::modules::Gps::update() -> void {\n  sleep(0);  // Wait for gps status change\n\n  if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) ||\n      (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) {\n    event_box_.set_visible(false);\n    return;\n  }\n\n  // Show the module\n  if (!event_box_.get_visible()) event_box_.set_visible(true);\n\n  std::string tooltip_format;\n\n  if (!alt_) {\n    auto state = getFixModeName();\n    if (!state_.empty() && label_.get_style_context()->has_class(state_)) {\n      label_.get_style_context()->remove_class(state_);\n    }\n    if (config_[\"format-\" + state].isString()) {\n      default_format_ = config_[\"format-\" + state].asString();\n    } else if (config_[\"format\"].isString()) {\n      default_format_ = config_[\"format\"].asString();\n    } else {\n      default_format_ = DEFAULT_FORMAT;\n    }\n    if (config_[\"tooltip-format-\" + state].isString()) {\n      tooltip_format = config_[\"tooltip-format-\" + state].asString();\n    }\n    if (!label_.get_style_context()->has_class(state)) {\n      label_.get_style_context()->add_class(state);\n    }\n    format_ = default_format_;\n    state_ = state;\n  }\n\n  auto format = format_;\n\n  fmt::dynamic_format_arg_store<fmt::format_context> store;\n  store.push_back(fmt::arg(\"mode\", getFixModeString()));\n  store.push_back(fmt::arg(\"status\", getFixStatusString()));\n\n  store.push_back(fmt::arg(\"latitude\", gps_data_.fix.latitude));\n  store.push_back(fmt::arg(\"latitude_error\", gps_data_.fix.epy));\n\n  store.push_back(fmt::arg(\"longitude\", gps_data_.fix.longitude));\n  store.push_back(fmt::arg(\"longitude_error\", gps_data_.fix.epx));\n\n  store.push_back(fmt::arg(\"altitude_hae\", gps_data_.fix.altHAE));\n  store.push_back(fmt::arg(\"altitude_msl\", gps_data_.fix.altMSL));\n  store.push_back(fmt::arg(\"altitude_error\", gps_data_.fix.epv));\n\n  store.push_back(fmt::arg(\"speed\", gps_data_.fix.speed));\n  store.push_back(fmt::arg(\"speed_error\", gps_data_.fix.eps));\n\n  store.push_back(fmt::arg(\"climb\", gps_data_.fix.climb));\n  store.push_back(fmt::arg(\"climb_error\", gps_data_.fix.epc));\n\n  store.push_back(fmt::arg(\"satellites_used\", gps_data_.satellites_used));\n  store.push_back(fmt::arg(\"satellites_visible\", gps_data_.satellites_visible));\n\n  auto text = fmt::vformat(format, store);\n\n  if (tooltipEnabled()) {\n    if (tooltip_format.empty() && config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    if (!tooltip_format.empty()) {\n      auto tooltip_text = fmt::vformat(tooltip_format, store);\n      if (label_.get_tooltip_text() != tooltip_text) {\n        label_.set_tooltip_markup(tooltip_text);\n      }\n    } else if (label_.get_tooltip_text() != text) {\n      label_.set_tooltip_markup(text);\n    }\n  }\n  label_.set_markup(text);\n  // Call parent update\n  ALabel::update();\n}\n\nwaybar::modules::Gps::~Gps() {\n  gps_stream(&gps_data_, WATCH_DISABLE, NULL);\n  gps_close(&gps_data_);\n}\n"
  },
  {
    "path": "src/modules/hyprland/backend.cpp",
    "content": "#include \"modules/hyprland/backend.hpp\"\n\n#include <netdb.h>\n#include <netinet/in.h>\n#include <spdlog/spdlog.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <unistd.h>\n\n#include <array>\n#include <cerrno>\n#include <cstring>\n#include <filesystem>\n#include <string>\n\n#include \"util/scoped_fd.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nstd::filesystem::path IPC::socketFolder_;\n\nstd::filesystem::path IPC::getSocketFolder(const char* instanceSig) {\n  static std::mutex folderMutex;\n  std::unique_lock lock(folderMutex);\n\n  if (socketFolder_.empty()) {\n    const char* xdgRuntimeDirEnv = std::getenv(\"XDG_RUNTIME_DIR\");\n    std::filesystem::path xdgRuntimeDir;\n    // Only set path if env variable is set\n    if (xdgRuntimeDirEnv != nullptr) {\n      xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv);\n    }\n\n    if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / \"hypr\")) {\n      socketFolder_ = xdgRuntimeDir / \"hypr\";\n    } else {\n      spdlog::warn(\"$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr\");\n      socketFolder_ = std::filesystem::path(\"/tmp\") / \"hypr\";\n    }\n  }\n\n  return socketFolder_ / instanceSig;\n}\n\nIPC::IPC() {\n  // will start IPC and relay events to parseIPC\n  socketOwnerPid_ = getpid();\n  ipcThread_ = std::thread([this]() { socketListener(); });\n}\n\nIPC::~IPC() {\n  // Do no stop Hyprland IPC if a child process (with successful fork() but\n  // failed exec()) exits.\n  if (getpid() != socketOwnerPid_) return;\n\n  running_.store(false, std::memory_order_relaxed);\n  spdlog::info(\"Hyprland IPC stopping...\");\n  {\n    std::lock_guard<std::mutex> lock(socketMutex_);\n    if (socketfd_ != -1) {\n      spdlog::trace(\"Shutting down socket\");\n      if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {\n        spdlog::error(\"Hyprland IPC: Couldn't shutdown socket\");\n      }\n    }\n  }\n  if (ipcThread_.joinable()) {\n    ipcThread_.join();\n  }\n}\n\nIPC& IPC::inst() {\n  static IPC ipc;\n  return ipc;\n}\n\nvoid IPC::socketListener() {\n  // check for hyprland\n  const char* his = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n  if (his == nullptr) {\n    spdlog::warn(\"Hyprland is not running, Hyprland IPC will not be available.\");\n    return;\n  }\n\n  spdlog::info(\"Hyprland IPC starting\");\n\n  struct sockaddr_un addr = {};\n  const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);\n\n  if (socketfd == -1) {\n    spdlog::error(\"Hyprland IPC: socketfd failed\");\n    return;\n  }\n\n  addr.sun_family = AF_UNIX;\n\n  auto socketPath = IPC::getSocketFolder(his) / \".socket2.sock\";\n  if (socketPath.native().size() >= sizeof(addr.sun_path)) {\n    spdlog::error(\"Hyprland IPC: Socket path is too long: {}\", socketPath.string());\n    close(socketfd);\n    return;\n  }\n  strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);\n\n  int l = sizeof(struct sockaddr_un);\n\n  if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {\n    spdlog::error(\"Hyprland IPC: Unable to connect? {}\", std::strerror(errno));\n    close(socketfd);\n    return;\n  }\n\n  {\n    std::lock_guard<std::mutex> lock(socketMutex_);\n    socketfd_ = socketfd;\n  }\n\n  std::string pending;\n  while (running_.load(std::memory_order_relaxed)) {\n    std::array<char, 1024> buffer;  // Hyprland socket2 events are max 1024 bytes\n    const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());\n\n    if (bytes_read == 0) {\n      if (running_.load(std::memory_order_relaxed)) {\n        spdlog::warn(\"Hyprland IPC: Socket closed by peer\");\n      }\n      break;\n    }\n\n    if (bytes_read < 0) {\n      if (errno == EINTR) {\n        continue;\n      }\n      if (!running_.load(std::memory_order_relaxed)) {\n        break;\n      }\n      spdlog::error(\"Hyprland IPC: read failed: {}\", std::strerror(errno));\n      break;\n    }\n\n    pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));\n    for (auto newline_pos = pending.find('\\n'); newline_pos != std::string::npos;\n         newline_pos = pending.find('\\n')) {\n      std::string messageReceived = pending.substr(0, newline_pos);\n      pending.erase(0, newline_pos + 1);\n      if (messageReceived.empty()) {\n        continue;\n      }\n      spdlog::debug(\"hyprland IPC received {}\", messageReceived);\n\n      try {\n        parseIPC(messageReceived);\n      } catch (std::exception& e) {\n        spdlog::warn(\"Failed to parse IPC message: {}, reason: {}\", messageReceived, e.what());\n      } catch (...) {\n        throw;\n      }\n    }\n  }\n  {\n    std::lock_guard<std::mutex> lock(socketMutex_);\n    if (socketfd_ != -1) {\n      if (close(socketfd_) == -1) {\n        spdlog::error(\"Hyprland IPC: Couldn't close socket\");\n      }\n      socketfd_ = -1;\n    }\n  }\n  spdlog::debug(\"Hyprland IPC stopped\");\n}\n\nvoid IPC::parseIPC(const std::string& ev) {\n  std::string request = ev.substr(0, ev.find_first_of('>'));\n  std::unique_lock lock(callbackMutex_);\n\n  for (auto& [eventname, handler] : callbacks_) {\n    if (eventname == request) {\n      handler->onEvent(ev);\n    }\n  }\n}\n\nvoid IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {\n  if (ev_handler == nullptr) {\n    return;\n  }\n\n  std::unique_lock lock(callbackMutex_);\n  callbacks_.emplace_back(ev, ev_handler);\n}\n\nvoid IPC::unregisterForIPC(EventHandler* ev_handler) {\n  if (ev_handler == nullptr) {\n    return;\n  }\n\n  std::unique_lock lock(callbackMutex_);\n\n  for (auto it = callbacks_.begin(); it != callbacks_.end();) {\n    auto& [eventname, handler] = *it;\n    if (handler == ev_handler) {\n      callbacks_.erase(it++);\n    } else {\n      ++it;\n    }\n  }\n}\n\nstd::string IPC::getSocket1Reply(const std::string& rq) {\n  // basically hyprctl\n\n  util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));\n\n  if (serverSocket < 0) {\n    throw std::runtime_error(\"Hyprland IPC: Couldn't open a socket (1)\");\n  }\n\n  // get the instance signature\n  auto* instanceSig = getenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n\n  if (instanceSig == nullptr) {\n    throw std::runtime_error(\n        \"Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)\");\n  }\n\n  sockaddr_un serverAddress = {0};\n  serverAddress.sun_family = AF_UNIX;\n\n  std::string socketPath = IPC::getSocketFolder(instanceSig) / \".socket.sock\";\n\n  // Use snprintf to copy the socketPath string into serverAddress.sun_path\n  const auto socketPathLength =\n      snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), \"%s\", socketPath.c_str());\n  if (socketPathLength < 0 ||\n      socketPathLength >= static_cast<int>(sizeof(serverAddress.sun_path))) {\n    throw std::runtime_error(\"Hyprland IPC: Couldn't copy socket path (6)\");\n  }\n\n  if (connect(serverSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) <\n      0) {\n    throw std::runtime_error(\"Hyprland IPC: Couldn't connect to \" + socketPath + \". (3)\");\n  }\n\n  std::size_t totalWritten = 0;\n  while (totalWritten < rq.length()) {\n    const auto sizeWritten =\n        write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten);\n\n    if (sizeWritten < 0) {\n      if (errno == EINTR) {\n        continue;\n      }\n      spdlog::error(\"Hyprland IPC: Couldn't write (4)\");\n      return \"\";\n    }\n    if (sizeWritten == 0) {\n      spdlog::error(\"Hyprland IPC: Socket write made no progress\");\n      return \"\";\n    }\n    totalWritten += static_cast<std::size_t>(sizeWritten);\n  }\n\n  std::array<char, 8192> buffer = {0};\n  std::string response;\n  ssize_t sizeWritten = 0;\n\n  do {\n    sizeWritten = read(serverSocket, buffer.data(), 8192);\n\n    if (sizeWritten < 0) {\n      spdlog::error(\"Hyprland IPC: Couldn't read (5)\");\n      return \"\";\n    }\n    response.append(buffer.data(), sizeWritten);\n  } while (sizeWritten > 0);\n\n  return response;\n}\n\nJson::Value IPC::getSocket1JsonReply(const std::string& rq) {\n  std::string reply = getSocket1Reply(\"j/\" + rq);\n\n  if (reply.empty()) {\n    return {};\n  }\n\n  return parser_.parse(reply);\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/language.cpp",
    "content": "#include \"modules/hyprland/language.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <xkbcommon/xkbcommon.h>\n#include <xkbcommon/xkbregistry.h>\n\n#include \"util/sanitize_str.hpp\"\n#include \"util/string.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nLanguage::Language(const std::string& id, const Bar& bar, const Json::Value& config)\n    : ALabel(config, \"language\", id, \"{}\", 0, true), bar_(bar), m_ipc(IPC::inst()) {\n  // get the active layout when open\n  initLanguage();\n\n  label_.hide();\n  update();\n\n  // register for hyprland ipc\n  m_ipc.registerForIPC(\"activelayout\", this);\n}\n\nLanguage::~Language() {\n  m_ipc.unregisterForIPC(this);\n  // wait for possible event handler to finish\n  std::lock_guard<std::mutex> lg(mutex_);\n}\n\nauto Language::update() -> void {\n  std::lock_guard<std::mutex> lg(mutex_);\n\n  spdlog::debug(\"hyprland language update with full name {}\", layout_.full_name);\n  spdlog::debug(\"hyprland language update with short name {}\", layout_.short_name);\n  spdlog::debug(\"hyprland language update with short description {}\", layout_.short_description);\n  spdlog::debug(\"hyprland language update with variant {}\", layout_.variant);\n\n  std::string layoutName = std::string{};\n  if (config_.isMember(\"format-\" + layout_.short_description + \"-\" + layout_.variant)) {\n    const auto propName = \"format-\" + layout_.short_description + \"-\" + layout_.variant;\n    layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());\n  } else if (config_.isMember(\"format-\" + layout_.short_description)) {\n    const auto propName = \"format-\" + layout_.short_description;\n    layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());\n  } else {\n    layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg(\"long\", layout_.full_name),\n                                  fmt::arg(\"short\", layout_.short_name),\n                                  fmt::arg(\"shortDescription\", layout_.short_description),\n                                  fmt::arg(\"variant\", layout_.variant)));\n  }\n\n  spdlog::debug(\"hyprland language formatted layout name {}\", layoutName);\n\n  if (!format_.empty()) {\n    label_.show();\n    label_.set_markup(layoutName);\n  } else {\n    label_.hide();\n  }\n\n  ALabel::update();\n}\n\nvoid Language::onEvent(const std::string& ev) {\n  std::lock_guard<std::mutex> lg(mutex_);\n  const auto payloadStart = ev.find(\">>\");\n  if (payloadStart == std::string::npos) {\n    spdlog::warn(\"hyprland language received malformed event: {}\", ev);\n    return;\n  }\n  const auto payload = ev.substr(payloadStart + 2);\n  const auto kbSeparator = payload.find(',');\n  if (kbSeparator == std::string::npos) {\n    spdlog::warn(\"hyprland language received malformed event payload: {}\", ev);\n    return;\n  }\n  std::string kbName = payload.substr(0, kbSeparator);\n\n  // Last comma before variants parenthesis, eg:\n  // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,\n  // with dead keys)\n  std::string beforeParenthesis;\n  auto parenthesisPos = payload.find_last_of('(');\n  if (parenthesisPos == std::string::npos) {\n    beforeParenthesis = payload;\n  } else {\n    beforeParenthesis = payload.substr(0, parenthesisPos);\n  }\n  const auto layoutSeparator = beforeParenthesis.find_last_of(',');\n  if (layoutSeparator == std::string::npos) {\n    spdlog::warn(\"hyprland language received malformed layout payload: {}\", ev);\n    return;\n  }\n  auto layoutName = payload.substr(layoutSeparator + 1);\n\n  if (config_.isMember(\"keyboard-name\") && kbName != config_[\"keyboard-name\"].asString())\n    return;  // ignore\n\n  layoutName = waybar::util::sanitize_string(layoutName);\n\n  layout_ = getLayout(layoutName);\n\n  spdlog::debug(\"hyprland language onevent with {}\", layoutName);\n\n  dp.emit();\n}\n\nvoid Language::initLanguage() {\n  const auto inputDevices = m_ipc.getSocket1Reply(\"devices\");\n\n  const auto kbName = config_[\"keyboard-name\"].asString();\n\n  try {\n    auto searcher = kbName.empty()\n                        ? inputDevices\n                        : inputDevices.substr(inputDevices.find(kbName) + kbName.length());\n    searcher = searcher.substr(searcher.find(\"keymap:\") + 8);\n    searcher = searcher.substr(0, searcher.find_first_of(\"\\n\\t\"));\n\n    searcher = waybar::util::sanitize_string(searcher);\n\n    layout_ = getLayout(searcher);\n\n    spdlog::debug(\"hyprland language initLanguage found {}\", layout_.full_name);\n\n    dp.emit();\n  } catch (std::exception& e) {\n    spdlog::error(\"hyprland language initLanguage failed with {}\", e.what());\n  }\n}\n\nauto Language::getLayout(const std::string& fullName) -> Layout {\n  auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);\n  rxkb_context_parse_default_ruleset(context);\n\n  rxkb_layout* layout = rxkb_layout_first(context);\n  while (layout != nullptr) {\n    std::string nameOfLayout = rxkb_layout_get_description(layout);\n\n    if (nameOfLayout != fullName) {\n      layout = rxkb_layout_next(layout);\n      continue;\n    }\n\n    auto name = std::string(rxkb_layout_get_name(layout));\n    const auto* variantPtr = rxkb_layout_get_variant(layout);\n    std::string variant = variantPtr == nullptr ? \"\" : std::string(variantPtr);\n\n    const auto* descriptionPtr = rxkb_layout_get_brief(layout);\n    std::string description = descriptionPtr == nullptr ? \"\" : std::string(descriptionPtr);\n\n    Layout info = Layout{nameOfLayout, name, variant, description};\n\n    rxkb_context_unref(context);\n\n    return info;\n  }\n\n  rxkb_context_unref(context);\n\n  spdlog::debug(\"hyprland language didn't find matching layout\");\n\n  return Layout{\"\", \"\", \"\", \"\"};\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/submap.cpp",
    "content": "#include \"modules/hyprland/submap.hpp\"\n\n#include <spdlog/spdlog.h>\n\nnamespace waybar::modules::hyprland {\n\nSubmap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)\n    : ALabel(config, \"submap\", id, \"{}\", 0, true), bar_(bar), m_ipc(IPC::inst()) {\n  parseConfig(config);\n\n  label_.hide();\n  ALabel::update();\n\n  // Displays widget immediately if always_on_ assuming default submap\n  // Needs an actual way to retrieve current submap on startup\n  if (always_on_) {\n    submap_ = default_submap_;\n    label_.get_style_context()->add_class(submap_);\n  }\n\n  // register for hyprland ipc\n  m_ipc.registerForIPC(\"submap\", this);\n  dp.emit();\n}\n\nSubmap::~Submap() {\n  m_ipc.unregisterForIPC(this);\n  // wait for possible event handler to finish\n  std::lock_guard<std::mutex> lg(mutex_);\n}\n\nauto Submap::parseConfig(const Json::Value& config) -> void {\n  auto const& alwaysOn = config[\"always-on\"];\n  if (alwaysOn.isBool()) {\n    always_on_ = alwaysOn.asBool();\n  }\n\n  auto const& defaultSubmap = config[\"default-submap\"];\n  if (defaultSubmap.isString()) {\n    default_submap_ = defaultSubmap.asString();\n  }\n}\n\nauto Submap::update() -> void {\n  std::lock_guard<std::mutex> lg(mutex_);\n\n  // Handle style class changes\n  if (!prev_submap_.empty()) {\n    label_.get_style_context()->remove_class(prev_submap_);\n  }\n\n  if (!submap_.empty()) {\n    label_.get_style_context()->add_class(submap_);\n  }\n\n  prev_submap_ = submap_;\n\n  if (submap_.empty()) {\n    event_box_.hide();\n  } else {\n    label_.set_markup(fmt::format(fmt::runtime(format_), submap_));\n    if (tooltipEnabled()) {\n      label_.set_tooltip_markup(submap_);\n    }\n    event_box_.show();\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nvoid Submap::onEvent(const std::string& ev) {\n  std::lock_guard<std::mutex> lg(mutex_);\n\n  if (ev.find(\"submap\") == std::string::npos) {\n    return;\n  }\n\n  const auto separator = ev.find(\">>\");\n  if (separator == std::string::npos) {\n    spdlog::warn(\"hyprland submap received malformed event: {}\", ev);\n    return;\n  }\n  auto submapName = ev.substr(separator + 2);\n\n  submap_ = submapName;\n\n  if (submap_.empty() && always_on_) {\n    submap_ = default_submap_;\n  }\n\n  spdlog::debug(\"hyprland submap onevent with {}\", submap_);\n\n  dp.emit();\n}\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/window.cpp",
    "content": "#include \"modules/hyprland/window.hpp\"\n\n#include <glibmm/fileutils.h>\n#include <glibmm/keyfile.h>\n#include <glibmm/miscutils.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <shared_mutex>\n#include <vector>\n\n#include \"modules/hyprland/backend.hpp\"\n#include \"util/rewrite_string.hpp\"\n#include \"util/sanitize_str.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nstd::shared_mutex windowIpcSmtx;\n\nWindow::Window(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"window\", id, \"{title}\", 0, true), bar_(bar), m_ipc(IPC::inst()) {\n  separateOutputs_ = config[\"separate-outputs\"].asBool();\n\n  update();\n\n  // register for hyprland ipc\n  std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);\n  m_ipc.registerForIPC(\"activewindow\", this);\n  m_ipc.registerForIPC(\"closewindow\", this);\n  m_ipc.registerForIPC(\"movewindow\", this);\n  m_ipc.registerForIPC(\"changefloatingmode\", this);\n  m_ipc.registerForIPC(\"fullscreen\", this);\n  windowIpcUniqueLock.unlock();\n\n  dp.emit();\n}\n\nWindow::~Window() {\n  std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);\n  m_ipc.unregisterForIPC(this);\n}\n\nauto Window::update() -> void {\n  std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);\n\n  queryActiveWorkspace();\n\n  std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);\n  std::string windowAddress = workspace_.last_window;\n\n  windowData_.title = windowName;\n\n  std::string label_text;\n  if (!format_.empty()) {\n    label_.show();\n    label_text = waybar::util::rewriteString(\n        fmt::format(fmt::runtime(format_), fmt::arg(\"title\", windowName),\n                    fmt::arg(\"initialTitle\", windowData_.initial_title),\n                    fmt::arg(\"class\", windowData_.class_name),\n                    fmt::arg(\"initialClass\", windowData_.initial_class_name)),\n        config_[\"rewrite\"]);\n    label_.set_markup(label_text);\n  } else {\n    label_.hide();\n  }\n\n  if (tooltipEnabled()) {\n    std::string tooltip_format;\n    if (config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    if (!tooltip_format.empty()) {\n      label_.set_tooltip_markup(\n          fmt::format(fmt::runtime(tooltip_format), fmt::arg(\"title\", windowName),\n                      fmt::arg(\"initialTitle\", windowData_.initial_title),\n                      fmt::arg(\"class\", windowData_.class_name),\n                      fmt::arg(\"initialClass\", windowData_.initial_class_name)));\n    } else if (!label_text.empty()) {\n      label_.set_tooltip_markup(label_text);\n    }\n  }\n\n  if (focused_) {\n    image_.show();\n  } else {\n    image_.hide();\n  }\n\n  setClass(\"empty\", workspace_.windows == 0);\n  setClass(\"solo\", solo_);\n  setClass(\"floating\", allFloating_);\n  setClass(\"swallowing\", swallowing_);\n  setClass(\"fullscreen\", fullscreen_);\n\n  if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) {\n    if (bar_.window.get_style_context()->has_class(lastSoloClass_)) {\n      bar_.window.get_style_context()->remove_class(lastSoloClass_);\n      spdlog::trace(\"Removing solo class: {}\", lastSoloClass_);\n    }\n  }\n\n  if (!soloClass_.empty() && soloClass_ != lastSoloClass_) {\n    bar_.window.get_style_context()->add_class(soloClass_);\n    spdlog::trace(\"Adding solo class: {}\", soloClass_);\n  }\n  lastSoloClass_ = soloClass_;\n\n  AAppIconLabel::update();\n}\n\nauto Window::getActiveWorkspace() -> Workspace {\n  const auto workspace = IPC::inst().getSocket1JsonReply(\"activeworkspace\");\n\n  if (workspace.isObject()) {\n    return Workspace::parse(workspace);\n  }\n\n  return {};\n}\n\nauto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {\n  const auto monitors = IPC::inst().getSocket1JsonReply(\"monitors\");\n  if (monitors.isArray()) {\n    auto monitor = std::ranges::find_if(\n        monitors, [&](const Json::Value& monitor) { return monitor[\"name\"] == monitorName; });\n    if (monitor == std::end(monitors)) {\n      spdlog::warn(\"Monitor not found: {}\", monitorName);\n      return Workspace{\n          .id = -1,\n          .windows = 0,\n          .last_window = \"\",\n          .last_window_title = \"\",\n      };\n    }\n    const int id = (*monitor)[\"activeWorkspace\"][\"id\"].asInt();\n\n    const auto workspaces = IPC::inst().getSocket1JsonReply(\"workspaces\");\n    if (workspaces.isArray()) {\n      auto workspace = std::ranges::find_if(\n          workspaces, [&](const Json::Value& workspace) { return workspace[\"id\"] == id; });\n      if (workspace == std::end(workspaces)) {\n        spdlog::warn(\"No workspace with id {}\", id);\n        return Workspace{\n            .id = -1,\n            .windows = 0,\n            .last_window = \"\",\n            .last_window_title = \"\",\n        };\n      }\n      return Workspace::parse(*workspace);\n    };\n  };\n\n  return {};\n}\n\nauto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {\n  return Workspace{\n      .id = value[\"id\"].asInt(),\n      .windows = value[\"windows\"].asInt(),\n      .last_window = value[\"lastwindow\"].asString(),\n      .last_window_title = value[\"lastwindowtitle\"].asString(),\n  };\n}\n\nauto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {\n  return WindowData{.floating = value[\"floating\"].asBool(),\n                    .monitor = value[\"monitor\"].asInt(),\n                    .class_name = value[\"class\"].asString(),\n                    .initial_class_name = value[\"initialClass\"].asString(),\n                    .title = value[\"title\"].asString(),\n                    .initial_title = value[\"initialTitle\"].asString(),\n                    .fullscreen = value[\"fullscreen\"].asBool(),\n                    .grouped = !value[\"grouped\"].empty()};\n}\n\nvoid Window::queryActiveWorkspace() {\n  if (separateOutputs_) {\n    workspace_ = getActiveWorkspace(this->bar_.output->name);\n  } else {\n    workspace_ = getActiveWorkspace();\n  }\n\n  focused_ = false;\n  windowData_ = WindowData{};\n  allFloating_ = false;\n  swallowing_ = false;\n  fullscreen_ = false;\n  solo_ = false;\n  soloClass_.clear();\n\n  if (workspace_.windows <= 0) {\n    return;\n  }\n\n  const auto clients = m_ipc.getSocket1JsonReply(\"clients\");\n  if (!clients.isArray()) {\n    return;\n  }\n\n  auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) {\n    return window[\"address\"] == workspace_.last_window;\n  });\n\n  if (activeWindow == std::end(clients)) {\n    return;\n  }\n\n  focused_ = true;\n  windowData_ = WindowData::parse(*activeWindow);\n  updateAppIconName(windowData_.class_name, windowData_.initial_class_name);\n  std::vector<Json::Value> workspaceWindows;\n  std::ranges::copy_if(\n      clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) {\n        return window[\"workspace\"][\"id\"] == workspace_.id && window[\"mapped\"].asBool();\n      });\n  swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {\n    return !window[\"swallowing\"].isNull() && window[\"swallowing\"].asString() != \"0x0\";\n  });\n  std::vector<Json::Value> visibleWindows;\n  std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),\n                       [&](const Json::Value& window) { return !window[\"hidden\"].asBool(); });\n  solo_ = 1 == std::count_if(\n                   visibleWindows.begin(), visibleWindows.end(),\n                   [&](const Json::Value& window) { return !window[\"floating\"].asBool(); });\n  allFloating_ = std::ranges::all_of(\n      visibleWindows, [&](const Json::Value& window) { return window[\"floating\"].asBool(); });\n  fullscreen_ = windowData_.fullscreen;\n\n  // Fullscreen windows look like they are solo\n  if (fullscreen_) {\n    solo_ = true;\n  }\n\n  if (solo_) {\n    soloClass_ = windowData_.class_name;\n  }\n}\n\nvoid Window::onEvent(const std::string& ev) { dp.emit(); }\n\nvoid Window::setClass(const std::string& classname, bool enable) {\n  if (enable) {\n    if (!bar_.window.get_style_context()->has_class(classname)) {\n      bar_.window.get_style_context()->add_class(classname);\n    }\n  } else {\n    bar_.window.get_style_context()->remove_class(classname);\n  }\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/windowcount.cpp",
    "content": "#include \"modules/hyprland/windowcount.hpp\"\n\n#include <glibmm/fileutils.h>\n#include <glibmm/keyfile.h>\n#include <glibmm/miscutils.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <vector>\n\n#include \"modules/hyprland/backend.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nWindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"windowcount\", id, \"{count}\", 0, true), bar_(bar), m_ipc(IPC::inst()) {\n  separateOutputs_ =\n      config.isMember(\"separate-outputs\") ? config[\"separate-outputs\"].asBool() : true;\n\n  queryActiveWorkspace();\n  update();\n  dp.emit();\n\n  // register for hyprland ipc\n  m_ipc.registerForIPC(\"fullscreen\", this);\n  m_ipc.registerForIPC(\"workspace\", this);\n  m_ipc.registerForIPC(\"focusedmon\", this);\n  m_ipc.registerForIPC(\"openwindow\", this);\n  m_ipc.registerForIPC(\"closewindow\", this);\n  m_ipc.registerForIPC(\"movewindow\", this);\n}\n\nWindowCount::~WindowCount() {\n  m_ipc.unregisterForIPC(this);\n  // wait for possible event handler to finish\n  std::lock_guard<std::mutex> lg(mutex_);\n}\n\nauto WindowCount::update() -> void {\n  std::lock_guard<std::mutex> lg(mutex_);\n\n  queryActiveWorkspace();\n\n  std::string format = config_[\"format\"].asString();\n  std::string formatEmpty = config_[\"format-empty\"].asString();\n  std::string formatWindowed = config_[\"format-windowed\"].asString();\n  std::string formatFullscreen = config_[\"format-fullscreen\"].asString();\n\n  setClass(\"empty\", workspace_.windows == 0);\n  setClass(\"fullscreen\", workspace_.hasfullscreen);\n\n  if (workspace_.windows == 0 && !formatEmpty.empty()) {\n    label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));\n  } else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {\n    label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));\n  } else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {\n    label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));\n  } else if (!format.empty()) {\n    label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));\n  } else {\n    label_.set_markup(fmt::format(\"{}\", workspace_.windows));\n  }\n\n  label_.show();\n  AAppIconLabel::update();\n}\n\nauto WindowCount::getActiveWorkspace() -> Workspace {\n  const auto workspace = m_ipc.getSocket1JsonReply(\"activeworkspace\");\n\n  if (workspace.isObject()) {\n    return Workspace::parse(workspace);\n  }\n\n  return {};\n}\n\nauto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {\n  const auto monitors = m_ipc.getSocket1JsonReply(\"monitors\");\n  if (monitors.isArray()) {\n    auto monitor = std::ranges::find_if(\n        monitors, [&](const Json::Value& monitor) { return monitor[\"name\"] == monitorName; });\n    if (monitor == std::end(monitors)) {\n      spdlog::warn(\"Monitor not found: {}\", monitorName);\n      return Workspace{\n          .id = -1,\n          .windows = 0,\n          .hasfullscreen = false,\n      };\n    }\n    const int id = (*monitor)[\"activeWorkspace\"][\"id\"].asInt();\n\n    const auto workspaces = m_ipc.getSocket1JsonReply(\"workspaces\");\n    if (workspaces.isArray()) {\n      auto workspace = std::ranges::find_if(\n          workspaces, [&](const Json::Value& workspace) { return workspace[\"id\"] == id; });\n      if (workspace == std::end(workspaces)) {\n        spdlog::warn(\"No workspace with id {}\", id);\n        return Workspace{\n            .id = -1,\n            .windows = 0,\n            .hasfullscreen = false,\n        };\n      }\n      return Workspace::parse(*workspace);\n    };\n  };\n\n  return {};\n}\n\nauto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {\n  return Workspace{\n      .id = value[\"id\"].asInt(),\n      .windows = value[\"windows\"].asInt(),\n      .hasfullscreen = value[\"hasfullscreen\"].asBool(),\n  };\n}\n\nvoid WindowCount::queryActiveWorkspace() {\n  if (separateOutputs_) {\n    workspace_ = getActiveWorkspace(this->bar_.output->name);\n  } else {\n    workspace_ = getActiveWorkspace();\n  }\n}\n\nvoid WindowCount::onEvent(const std::string& ev) { dp.emit(); }\n\nvoid WindowCount::setClass(const std::string& classname, bool enable) {\n  if (enable) {\n    if (!bar_.window.get_style_context()->has_class(classname)) {\n      bar_.window.get_style_context()->add_class(classname);\n    }\n  } else {\n    bar_.window.get_style_context()->remove_class(classname);\n  }\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/windowcreationpayload.cpp",
    "content": "#include \"modules/hyprland/windowcreationpayload.hpp\"\n\n#include <json/value.h>\n#include <spdlog/spdlog.h>\n\n#include <string>\n#include <utility>\n#include <variant>\n\n#include \"modules/hyprland/workspaces.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nWindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)\n    : m_window(std::make_pair(client_data[\"class\"].asString(), client_data[\"title\"].asString())),\n      m_windowAddress(client_data[\"address\"].asString()),\n      m_workspaceName(client_data[\"workspace\"][\"name\"].asString()) {\n  clearAddr();\n  clearWorkspaceName();\n}\n\nWindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,\n                                             WindowAddress window_address, WindowRepr window_repr)\n    : m_window(std::move(window_repr)),\n      m_windowAddress(std::move(window_address)),\n      m_workspaceName(std::move(workspace_name)) {\n  clearAddr();\n  clearWorkspaceName();\n}\n\nWindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,\n                                             WindowAddress window_address,\n                                             const std::string& window_class,\n                                             const std::string& window_title, bool is_active)\n    : m_window(std::make_pair(std::move(window_class), std::move(window_title))),\n      m_windowAddress(std::move(window_address)),\n      m_workspaceName(std::move(workspace_name)),\n      m_isActive(is_active) {\n  clearAddr();\n  clearWorkspaceName();\n}\n\nvoid WindowCreationPayload::clearAddr() {\n  // substr(2, ...) is necessary because Hyprland's JSON follows this format:\n  // 0x{ADDR}\n  // While Hyprland's IPC follows this format:\n  // {ADDR}\n  static const std::string ADDR_PREFIX = \"0x\";\n  static const int ADDR_PREFIX_LEN = ADDR_PREFIX.length();\n\n  if (m_windowAddress.starts_with(ADDR_PREFIX)) {\n    m_windowAddress =\n        m_windowAddress.substr(ADDR_PREFIX_LEN, m_windowAddress.length() - ADDR_PREFIX_LEN);\n  }\n}\n\nvoid WindowCreationPayload::clearWorkspaceName() {\n  // The workspace name may optionally feature \"special:\" at the beginning.\n  // If so, we need to remove it because the workspace is saved WITHOUT the\n  // special qualifier. The reasoning is that not all of Hyprland's IPC events\n  // use this qualifier, so it's better to be consistent about our uses.\n\n  static const std::string SPECIAL_QUALIFIER_PREFIX = \"special:\";\n  static const int SPECIAL_QUALIFIER_PREFIX_LEN = SPECIAL_QUALIFIER_PREFIX.length();\n\n  if (m_workspaceName.starts_with(SPECIAL_QUALIFIER_PREFIX)) {\n    m_workspaceName = m_workspaceName.substr(\n        SPECIAL_QUALIFIER_PREFIX_LEN, m_workspaceName.length() - SPECIAL_QUALIFIER_PREFIX_LEN);\n  }\n\n  std::size_t spaceFound = m_workspaceName.find(' ');\n  if (spaceFound != std::string::npos) {\n    m_workspaceName.erase(m_workspaceName.begin() + spaceFound, m_workspaceName.end());\n  }\n}\n\nbool WindowCreationPayload::isEmpty(Workspaces& workspace_manager) {\n  if (std::holds_alternative<Repr>(m_window)) {\n    return std::get<Repr>(m_window).empty();\n  }\n  if (std::holds_alternative<ClassAndTitle>(m_window)) {\n    auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);\n    return (window_class.empty() &&\n            (!workspace_manager.windowRewriteConfigUsesTitle() || window_title.empty()));\n  }\n  // Unreachable\n  spdlog::error(\"WorkspaceWindow::isEmpty: Unreachable\");\n  throw std::runtime_error(\"WorkspaceWindow::isEmpty: Unreachable\");\n}\n\nint WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }\n\nvoid WindowCreationPayload::moveToWorkspace(std::string& new_workspace_name) {\n  m_workspaceName = new_workspace_name;\n}\n\nWindowRepr WindowCreationPayload::repr(Workspaces& workspace_manager) {\n  if (std::holds_alternative<Repr>(m_window)) {\n    return std::get<Repr>(m_window);\n  }\n  if (std::holds_alternative<ClassAndTitle>(m_window)) {\n    auto const& [window_class, window_title] = std::get<ClassAndTitle>(m_window);\n    return {m_windowAddress, window_class, window_title,\n            workspace_manager.getRewrite(window_class, window_title), m_isActive};\n  }\n  // Unreachable\n  spdlog::error(\"WorkspaceWindow::repr: Unreachable\");\n  throw std::runtime_error(\"WorkspaceWindow::repr: Unreachable\");\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/workspace.cpp",
    "content": "#include <json/value.h>\n#include <spdlog/spdlog.h>\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"modules/hyprland/workspaces.hpp\"\n#include \"util/command.hpp\"\n#include \"util/icon_loader.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nWorkspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,\n                     const Json::Value& clients_data)\n    : m_workspaceManager(workspace_manager),\n      m_id(workspace_data[\"id\"].asInt()),\n      m_name(workspace_data[\"name\"].asString()),\n      m_output(workspace_data[\"monitor\"].asString()),  // TODO:allow using monitor desc\n      m_windows(workspace_data[\"windows\"].asInt()),\n      m_isActive(true),\n      m_isPersistentRule(workspace_data[\"persistent-rule\"].asBool()),\n      m_isPersistentConfig(workspace_data[\"persistent-config\"].asBool()),\n      m_ipc(IPC::inst()) {\n  if (m_name.starts_with(\"name:\")) {\n    m_name = m_name.substr(5);\n  } else if (m_name.starts_with(\"special\")) {\n    m_name = m_id == -99 ? m_name : m_name.substr(8);\n    m_isSpecial = true;\n  }\n\n  m_button.add_events(Gdk::BUTTON_PRESS_MASK);\n  m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked),\n                                               false);\n\n  m_button.set_relief(Gtk::RELIEF_NONE);\n  if (m_workspaceManager.enableTaskbar()) {\n    m_content.set_orientation(m_workspaceManager.taskbarOrientation());\n    m_content.pack_start(m_labelBefore, false, false);\n  } else {\n    m_content.set_center_widget(m_labelBefore);\n  }\n  m_button.add(m_content);\n\n  initializeWindowMap(clients_data);\n}\n\nvoid addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext>& context, bool condition,\n                      const std::string& class_name) {\n  if (condition) {\n    context->add_class(class_name);\n  } else {\n    context->remove_class(class_name);\n  }\n}\n\nstd::optional<WindowRepr> Workspace::closeWindow(WindowAddress const& addr) {\n  auto it = std::ranges::find_if(m_windowMap,\n                                 [&addr](const auto& window) { return window.address == addr; });\n  // If the vector contains the address, remove it and return the window representation\n  if (it != m_windowMap.end()) {\n    WindowRepr windowRepr = *it;\n    m_windowMap.erase(it);\n    return windowRepr;\n  }\n  return std::nullopt;\n}\n\nbool Workspace::handleClicked(GdkEventButton* bt) const {\n  if (bt->type == GDK_BUTTON_PRESS) {\n    try {\n      if (id() > 0) {  // normal\n        if (m_workspaceManager.moveToMonitor()) {\n          m_ipc.getSocket1Reply(\"dispatch focusworkspaceoncurrentmonitor \" + std::to_string(id()));\n        } else {\n          m_ipc.getSocket1Reply(\"dispatch workspace \" + std::to_string(id()));\n        }\n      } else if (!isSpecial()) {  // named (this includes persistent)\n        if (m_workspaceManager.moveToMonitor()) {\n          m_ipc.getSocket1Reply(\"dispatch focusworkspaceoncurrentmonitor name:\" + name());\n        } else {\n          m_ipc.getSocket1Reply(\"dispatch workspace name:\" + name());\n        }\n      } else if (id() != -99) {  // named special\n        m_ipc.getSocket1Reply(\"dispatch togglespecialworkspace \" + name());\n      } else {  // special\n        m_ipc.getSocket1Reply(\"dispatch togglespecialworkspace\");\n      }\n      return true;\n    } catch (const std::exception& e) {\n      spdlog::error(\"Failed to dispatch workspace: {}\", e.what());\n    }\n  }\n  return false;\n}\n\nvoid Workspace::initializeWindowMap(const Json::Value& clients_data) {\n  m_windowMap.clear();\n  for (const auto& client : clients_data) {\n    if (client[\"workspace\"][\"id\"].asInt() == id()) {\n      insertWindow({client});\n    }\n  }\n}\n\nvoid Workspace::setActiveWindow(WindowAddress const& addr) {\n  std::optional<long> activeIdx;\n  for (size_t i = 0; i < m_windowMap.size(); ++i) {\n    auto& window = m_windowMap[i];\n    bool isActive = (window.address == addr);\n    window.setActive(isActive);\n    if (isActive) {\n      activeIdx = i;\n    }\n  }\n\n  auto activeWindowPos = m_workspaceManager.activeWindowPosition();\n  if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) {\n    auto window = std::move(m_windowMap[*activeIdx]);\n    m_windowMap.erase(m_windowMap.begin() + *activeIdx);\n    if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {\n      m_windowMap.insert(m_windowMap.begin(), std::move(window));\n    } else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) {\n      m_windowMap.emplace_back(std::move(window));\n    }\n  }\n}\n\nvoid Workspace::insertWindow(WindowCreationPayload create_window_payload) {\n  if (!create_window_payload.isEmpty(m_workspaceManager)) {\n    auto repr = create_window_payload.repr(m_workspaceManager);\n\n    if (!repr.empty() || m_workspaceManager.enableTaskbar()) {\n      auto addr = create_window_payload.getAddress();\n      auto it = std::ranges::find_if(\n          m_windowMap, [&addr](const auto& window) { return window.address == addr; });\n      // If the vector contains the address, update the window representation, otherwise insert it\n      if (it != m_windowMap.end()) {\n        *it = repr;\n      } else {\n        m_windowMap.emplace_back(repr);\n      }\n    }\n  }\n};\n\nbool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) {\n  if (create_window_payload.getWorkspaceName() == name()) {\n    insertWindow(create_window_payload);\n    return true;\n  }\n  return false;\n}\n\nstd::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map) {\n  spdlog::trace(\"Selecting icon for workspace {}\", name());\n  if (isUrgent()) {\n    auto urgentIconIt = icons_map.find(\"urgent\");\n    if (urgentIconIt != icons_map.end()) {\n      return urgentIconIt->second;\n    }\n  }\n\n  if (isActive()) {\n    auto activeIconIt = icons_map.find(\"active\");\n    if (activeIconIt != icons_map.end()) {\n      return activeIconIt->second;\n    }\n  }\n\n  if (isSpecial()) {\n    auto specialIconIt = icons_map.find(\"special\");\n    if (specialIconIt != icons_map.end()) {\n      return specialIconIt->second;\n    }\n  }\n\n  auto namedIconIt = icons_map.find(name());\n  if (namedIconIt != icons_map.end()) {\n    return namedIconIt->second;\n  }\n\n  if (isVisible()) {\n    auto visibleIconIt = icons_map.find(\"visible\");\n    if (visibleIconIt != icons_map.end()) {\n      return visibleIconIt->second;\n    }\n  }\n\n  if (isEmpty()) {\n    auto emptyIconIt = icons_map.find(\"empty\");\n    if (emptyIconIt != icons_map.end()) {\n      return emptyIconIt->second;\n    }\n  }\n\n  if (isPersistent()) {\n    auto persistentIconIt = icons_map.find(\"persistent\");\n    if (persistentIconIt != icons_map.end()) {\n      return persistentIconIt->second;\n    }\n  }\n\n  auto defaultIconIt = icons_map.find(\"default\");\n  if (defaultIconIt != icons_map.end()) {\n    return defaultIconIt->second;\n  }\n\n  return m_name;\n}\n\nvoid Workspace::update(const std::string& workspace_icon) {\n  if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {\n    m_button.hide();\n    return;\n  }\n  // clang-format off\n  if (this->m_workspaceManager.activeOnly() && \\\n     !this->isActive() && \\\n     !this->isPersistent() && \\\n     !this->isVisible() && \\\n     !this->isSpecial()) {\n    // clang-format on\n    // if activeOnly is true, hide if not active, persistent, visible or special\n    m_button.hide();\n    return;\n  }\n  if (this->m_workspaceManager.specialVisibleOnly() && this->isSpecial() && !this->isVisible()) {\n    m_button.hide();\n    return;\n  }\n  m_button.show();\n\n  auto styleContext = m_button.get_style_context();\n  addOrRemoveClass(styleContext, isActive(), \"active\");\n  addOrRemoveClass(styleContext, isSpecial(), \"special\");\n  addOrRemoveClass(styleContext, isEmpty(), \"empty\");\n  addOrRemoveClass(styleContext, isPersistent(), \"persistent\");\n  addOrRemoveClass(styleContext, isUrgent(), \"urgent\");\n  addOrRemoveClass(styleContext, isVisible(), \"visible\");\n  addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), \"hosting-monitor\");\n\n  std::string windows;\n  // Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no\n  // need to compute this if enableTaskbar() is true\n  if (!m_workspaceManager.enableTaskbar()) {\n    auto windowSeparator = m_workspaceManager.getWindowSeparator();\n\n    bool isNotFirst = false;\n\n    for (const auto& window_repr : m_windowMap) {\n      if (isNotFirst) {\n        windows.append(windowSeparator);\n      }\n      isNotFirst = true;\n      windows.append(window_repr.repr_rewrite);\n    }\n  }\n\n  auto formatBefore = m_workspaceManager.formatBefore();\n  m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg(\"id\", id()),\n                                       fmt::arg(\"name\", name()), fmt::arg(\"icon\", workspace_icon),\n                                       fmt::arg(\"windows\", windows)));\n  m_labelBefore.get_style_context()->add_class(\"workspace-label\");\n\n  if (m_workspaceManager.enableTaskbar()) {\n    updateTaskbar(workspace_icon);\n  }\n}\n\nbool Workspace::isEmpty() const {\n  auto ignore_list = m_workspaceManager.getIgnoredWindows();\n  if (ignore_list.empty()) {\n    return m_windows == 0;\n  }\n  // If there are windows but they are all ignored, consider the workspace empty\n  return std::all_of(\n      m_windowMap.begin(), m_windowMap.end(),\n      [this, &ignore_list](const auto& window_repr) { return shouldSkipWindow(window_repr); });\n}\n\nvoid Workspace::updateTaskbar(const std::string& workspace_icon) {\n  for (auto child : m_content.get_children()) {\n    if (child != &m_labelBefore) {\n      m_content.remove(*child);\n    }\n  }\n\n  bool isFirst = true;\n  auto processWindow = [&](const WindowRepr& window_repr) {\n    if (shouldSkipWindow(window_repr)) {\n      return;  // skip\n    }\n    if (isFirst) {\n      isFirst = false;\n    } else if (m_workspaceManager.getWindowSeparator() != \"\") {\n      auto windowSeparator = Gtk::make_managed<Gtk::Label>(m_workspaceManager.getWindowSeparator());\n      m_content.pack_start(*windowSeparator, false, false);\n      windowSeparator->show();\n    }\n\n    auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);\n    window_box->set_tooltip_markup(window_repr.window_title);\n    window_box->get_style_context()->add_class(\"taskbar-window\");\n    if (window_repr.isActive) {\n      window_box->get_style_context()->add_class(\"active\");\n    }\n    auto event_box = Gtk::manage(new Gtk::EventBox());\n    event_box->add(*window_box);\n    if (m_workspaceManager.onClickWindow() != \"\") {\n      event_box->signal_button_press_event().connect(\n          sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address));\n    }\n\n    auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),\n                                   fmt::arg(\"title\", window_repr.window_title));\n    if (!text_before.empty()) {\n      auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);\n      window_box->pack_start(*window_label_before, true, true);\n    }\n\n    if (m_workspaceManager.taskbarWithIcon()) {\n      auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class);\n      int icon_size = m_workspaceManager.taskbarIconSize();\n      auto window_icon = Gtk::make_managed<Gtk::Image>();\n      m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size);\n      window_box->pack_start(*window_icon, false, false);\n    }\n\n    auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()),\n                                  fmt::arg(\"title\", window_repr.window_title));\n    if (!text_after.empty()) {\n      auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);\n      window_box->pack_start(*window_label_after, true, true);\n    }\n\n    m_content.pack_start(*event_box, true, false);\n    event_box->show_all();\n  };\n\n  if (m_workspaceManager.taskbarReverseDirection()) {\n    for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) {\n      processWindow(*it);\n    }\n  } else {\n    for (const auto& window_repr : m_windowMap) {\n      processWindow(window_repr);\n    }\n  }\n\n  auto formatAfter = m_workspaceManager.formatAfter();\n  if (!formatAfter.empty()) {\n    m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg(\"id\", id()),\n                                        fmt::arg(\"name\", name()),\n                                        fmt::arg(\"icon\", workspace_icon)));\n    m_content.pack_end(m_labelAfter, false, false);\n    m_labelAfter.show();\n  }\n}\n\nbool Workspace::handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const {\n  if (event_button->type == GDK_BUTTON_PRESS) {\n    std::string command = std::regex_replace(m_workspaceManager.onClickWindow(),\n                                             std::regex(\"\\\\{address\\\\}\"), \"0x\" + addr);\n    command = std::regex_replace(command, std::regex(\"\\\\{button\\\\}\"),\n                                 std::to_string(event_button->button));\n    auto res = util::command::execNoRead(command);\n    if (res.exit_code != 0) {\n      spdlog::error(\"Failed to execute {}: {}\", command, res.out);\n    }\n  }\n  return true;\n}\n\nbool Workspace::shouldSkipWindow(const WindowRepr& window_repr) const {\n  auto ignore_list = m_workspaceManager.getIgnoredWindows();\n  auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto& ignoreItem) {\n    return std::regex_match(window_repr.window_class, ignoreItem) ||\n           std::regex_match(window_repr.window_title, ignoreItem);\n  });\n  return it != ignore_list.end();\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/hyprland/workspaces.cpp",
    "content": "#include \"modules/hyprland/workspaces.hpp\"\n\n#include <json/value.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <utility>\n\n#include \"util/regex_collection.hpp\"\n#include \"util/string.hpp\"\n\nnamespace waybar::modules::hyprland {\n\nWorkspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AModule(config, \"workspaces\", id, false, false),\n      m_bar(bar),\n      m_box(bar.orientation, 0),\n      m_ipc(IPC::inst()) {\n  parseConfig(config);\n\n  m_box.set_name(\"workspaces\");\n  if (!id.empty()) {\n    m_box.get_style_context()->add_class(id);\n  }\n  m_box.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(m_box);\n\n  setCurrentMonitorId();\n  init();\n  registerIpc();\n}\n\nWorkspaces::~Workspaces() {\n  if (m_scrollEventConnection_.connected()) {\n    m_scrollEventConnection_.disconnect();\n  }\n  m_ipc.unregisterForIPC(this);\n  // wait for possible event handler to finish\n  std::lock_guard<std::mutex> lg(m_mutex);\n}\n\nvoid Workspaces::init() {\n  m_activeWorkspaceId = m_ipc.getSocket1JsonReply(\"activeworkspace\")[\"id\"].asInt();\n\n  initializeWorkspaces();\n\n  if (m_scrollEventConnection_.connected()) {\n    m_scrollEventConnection_.disconnect();\n  }\n  if (barScroll()) {\n    auto& window = const_cast<Bar&>(m_bar).window;\n    window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n    m_scrollEventConnection_ =\n        window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));\n  }\n\n  dp.emit();\n}\n\nJson::Value Workspaces::createMonitorWorkspaceData(std::string const& name,\n                                                   std::string const& monitor) {\n  spdlog::trace(\"Creating persistent workspace: {} on monitor {}\", name, monitor);\n  Json::Value workspaceData;\n\n  auto workspaceId = parseWorkspaceId(name);\n  if (!workspaceId.has_value()) {\n    workspaceId = 0;\n  }\n  workspaceData[\"id\"] = *workspaceId;\n  workspaceData[\"name\"] = name;\n  workspaceData[\"monitor\"] = monitor;\n  workspaceData[\"windows\"] = 0;\n  return workspaceData;\n}\n\nvoid Workspaces::createWorkspace(Json::Value const& workspace_data,\n                                 Json::Value const& clients_data) {\n  auto workspaceName = workspace_data[\"name\"].asString();\n  auto workspaceId = workspace_data[\"id\"].asInt();\n  spdlog::debug(\"Creating workspace {}\", workspaceName);\n\n  // avoid recreating existing workspaces\n  auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace> const& w) {\n    if (workspaceId > 0) {\n      return w->id() == workspaceId;\n    }\n    return (workspaceName.starts_with(\"special:\") && workspaceName.substr(8) == w->name()) ||\n           workspaceName == w->name();\n  });\n\n  if (workspace != m_workspaces.end()) {\n    // don't recreate workspace, but update persistency if necessary\n    const auto keys = workspace_data.getMemberNames();\n\n    const auto* k = \"persistent-rule\";\n    if (std::ranges::find(keys, k) != keys.end()) {\n      spdlog::debug(\"Set dynamic persistency of workspace {} to: {}\", workspaceName,\n                    workspace_data[k].asBool() ? \"true\" : \"false\");\n      (*workspace)->setPersistentRule(workspace_data[k].asBool());\n    }\n\n    k = \"persistent-config\";\n    if (std::ranges::find(keys, k) != keys.end()) {\n      spdlog::debug(\"Set config persistency of workspace {} to: {}\", workspaceName,\n                    workspace_data[k].asBool() ? \"true\" : \"false\");\n      (*workspace)->setPersistentConfig(workspace_data[k].asBool());\n    }\n\n    return;\n  }\n\n  // create new workspace\n  m_workspaces.emplace_back(std::make_unique<Workspace>(workspace_data, *this, clients_data));\n  Gtk::Button& newWorkspaceButton = m_workspaces.back()->button();\n  m_box.pack_start(newWorkspaceButton, false, false);\n  sortWorkspaces();\n  newWorkspaceButton.show_all();\n}\n\nvoid Workspaces::createWorkspacesToCreate() {\n  for (const auto& [workspaceData, clientsData] : m_workspacesToCreate) {\n    createWorkspace(workspaceData, clientsData);\n  }\n  if (!m_workspacesToCreate.empty()) {\n    updateWindowCount();\n    sortWorkspaces();\n  }\n  m_workspacesToCreate.clear();\n}\n\n/**\n *  Workspaces::doUpdate - update workspaces in UI thread.\n *\n * Note: some memberfields are modified by both UI thread and event listener thread, use m_mutex to\n *       protect these member fields, and lock should released before calling AModule::update().\n */\nvoid Workspaces::doUpdate() {\n  std::unique_lock lock(m_mutex);\n\n  removeWorkspacesToRemove();\n  createWorkspacesToCreate();\n  updateWorkspaceStates();\n  updateWindowCount();\n  sortWorkspaces();\n\n  bool anyWindowCreated = updateWindowsToCreate();\n\n  if (anyWindowCreated) {\n    dp.emit();\n  }\n}\n\nvoid Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson) {\n  spdlog::trace(\"Extending orphans with workspace {}\", workspaceId);\n  for (const auto& client : clientsJson) {\n    if (client[\"workspace\"][\"id\"].asInt() == workspaceId) {\n      registerOrphanWindow({client});\n    }\n  }\n}\n\nstd::string Workspaces::getRewrite(const std::string& window_class,\n                                   const std::string& window_title) {\n  std::string windowReprKey;\n  if (windowRewriteConfigUsesTitle()) {\n    windowReprKey = fmt::format(\"class<{}> title<{}>\", window_class, window_title);\n  } else {\n    windowReprKey = fmt::format(\"class<{}>\", window_class);\n  }\n  auto const rewriteRule = m_windowRewriteRules.get(windowReprKey);\n  return fmt::format(fmt::runtime(rewriteRule), fmt::arg(\"class\", window_class),\n                     fmt::arg(\"title\", window_title));\n}\n\nstd::vector<int> Workspaces::getVisibleWorkspaces() {\n  std::vector<int> visibleWorkspaces;\n  auto monitors = IPC::inst().getSocket1JsonReply(\"monitors\");\n  for (const auto& monitor : monitors) {\n    auto ws = monitor[\"activeWorkspace\"];\n    if (ws.isObject() && ws[\"id\"].isInt()) {\n      visibleWorkspaces.push_back(ws[\"id\"].asInt());\n    }\n    auto sws = monitor[\"specialWorkspace\"];\n    auto name = sws[\"name\"].asString();\n    if (sws.isObject() && sws[\"id\"].isInt() && !name.empty()) {\n      visibleWorkspaces.push_back(sws[\"id\"].asInt());\n    }\n  }\n  return visibleWorkspaces;\n}\n\nvoid Workspaces::initializeWorkspaces() {\n  spdlog::debug(\"Initializing workspaces\");\n\n  // if the workspace rules changed since last initialization, make sure we reset everything:\n  for (auto& workspace : m_workspaces) {\n    m_workspacesToRemove.push_back(std::to_string(workspace->id()));\n  }\n\n  // get all current workspaces\n  auto const workspacesJson = m_ipc.getSocket1JsonReply(\"workspaces\");\n  auto const clientsJson = m_ipc.getSocket1JsonReply(\"clients\");\n\n  for (const auto& workspaceJson : workspacesJson) {\n    std::string workspaceName = workspaceJson[\"name\"].asString();\n    if ((allOutputs() || m_bar.output->name == workspaceJson[\"monitor\"].asString()) &&\n        (!workspaceName.starts_with(\"special\") || showSpecial()) &&\n        !isWorkspaceIgnored(workspaceName)) {\n      m_workspacesToCreate.emplace_back(workspaceJson, clientsJson);\n    } else {\n      extendOrphans(workspaceJson[\"id\"].asInt(), clientsJson);\n    }\n  }\n\n  spdlog::debug(\"Initializing persistent workspaces\");\n  if (m_persistentWorkspaceConfig.isObject()) {\n    // a persistent workspace config is defined, so use that instead of workspace rules\n    loadPersistentWorkspacesFromConfig(clientsJson);\n  }\n  // load Hyprland's workspace rules\n  loadPersistentWorkspacesFromWorkspaceRules(clientsJson);\n}\n\nbool isDoubleSpecial(std::string const& workspace_name) {\n  // Hyprland's IPC sometimes reports the creation of workspaces strangely named\n  // `special:special:<some_name>`. This function checks for that and is used\n  // to avoid creating (and then removing) such workspaces.\n  // See hyprwm/Hyprland#3424 for more info.\n  return workspace_name.find(\"special:special:\") != std::string::npos;\n}\n\nbool Workspaces::isWorkspaceIgnored(std::string const& name) {\n  for (auto& rule : m_ignoreWorkspaces) {\n    if (std::regex_match(name, rule)) {\n      return true;\n      break;\n    }\n  }\n\n  return false;\n}\n\nvoid Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson) {\n  spdlog::info(\"Loading persistent workspaces from Waybar config\");\n  const std::vector<std::string> keys = m_persistentWorkspaceConfig.getMemberNames();\n  std::vector<std::string> persistentWorkspacesToCreate;\n\n  const std::string currentMonitor = m_bar.output->name;\n  const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end();\n  for (const std::string& key : keys) {\n    // only add if either:\n    // 1. key is the current monitor name\n    // 2. key is \"*\" and this monitor is not already defined in the config\n    bool canCreate = key == currentMonitor || (key == \"*\" && !monitorInConfig);\n    const Json::Value& value = m_persistentWorkspaceConfig[key];\n    spdlog::trace(\"Parsing persistent workspace config: {} => {}\", key, value.toStyledString());\n\n    if (value.isInt()) {\n      // value is a number => create that many workspaces for this monitor\n      if (canCreate) {\n        int amount = value.asInt();\n        spdlog::debug(\"Creating {} persistent workspaces for monitor {}\", amount, currentMonitor);\n        for (int i = 0; i < amount; i++) {\n          persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1));\n        }\n      }\n    } else if (value.isArray() && !value.empty()) {\n      // value is an array => create defined workspaces for this monitor\n      if (canCreate) {\n        for (const Json::Value& workspace : value) {\n          spdlog::debug(\"Creating workspace {} on monitor {}\", workspace, currentMonitor);\n          persistentWorkspacesToCreate.emplace_back(workspace.asString());\n        }\n      } else {\n        // key is the workspace and value is array of monitors to create on\n        for (const Json::Value& monitor : value) {\n          if (monitor.isString() && monitor.asString() == currentMonitor) {\n            persistentWorkspacesToCreate.emplace_back(key);\n            break;\n          }\n        }\n      }\n    } else {\n      // this workspace should be displayed on all monitors\n      persistentWorkspacesToCreate.emplace_back(key);\n    }\n  }\n\n  for (auto const& workspace : persistentWorkspacesToCreate) {\n    auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);\n    workspaceData[\"persistent-config\"] = true;\n    m_workspacesToCreate.emplace_back(workspaceData, clientsJson);\n  }\n}\n\nvoid Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson) {\n  spdlog::info(\"Loading persistent workspaces from Hyprland workspace rules\");\n\n  auto const workspaceRules = m_ipc.getSocket1JsonReply(\"workspacerules\");\n  for (Json::Value const& rule : workspaceRules) {\n    if (!rule[\"workspaceString\"].isString()) {\n      spdlog::warn(\"Workspace rules: invalid workspaceString, skipping: {}\", rule);\n      continue;\n    }\n    if (!rule[\"persistent\"].asBool()) {\n      continue;\n    }\n    auto workspace = rule.isMember(\"defaultName\") ? rule[\"defaultName\"].asString()\n                                                  : rule[\"workspaceString\"].asString();\n\n    // There could be persistent special workspaces, only show those when show-special is enabled.\n    if (workspace.starts_with(\"special:\") && !showSpecial()) {\n      continue;\n    }\n\n    // The prefix \"name:\" cause mismatches with workspace names taken anywhere else.\n    if (workspace.starts_with(\"name:\")) {\n      workspace = workspace.substr(5);\n    }\n    auto const& monitor = rule[\"monitor\"].asString();\n    // create this workspace persistently if:\n    // 1. the allOutputs config option is enabled\n    // 2. the rule's monitor is the current monitor\n    // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor\n    if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) {\n      // => persistent workspace should be shown on this monitor\n      auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name);\n      workspaceData[\"persistent-rule\"] = true;\n      m_workspacesToCreate.emplace_back(workspaceData, clientsJson);\n    } else {\n      // This can be any workspace selector.\n      m_workspacesToRemove.emplace_back(workspace);\n    }\n  }\n}\n\nvoid Workspaces::onEvent(const std::string& ev) {\n  std::lock_guard<std::mutex> lock(m_mutex);\n  const auto separator = ev.find(\">>\");\n  if (separator == std::string::npos) {\n    spdlog::warn(\"Malformed Hyprland workspace event: {}\", ev);\n    return;\n  }\n  std::string eventName = ev.substr(0, separator);\n  std::string payload = ev.substr(separator + 2);\n\n  if (eventName == \"workspacev2\") {\n    onWorkspaceActivated(payload);\n  } else if (eventName == \"activespecial\") {\n    onSpecialWorkspaceActivated(payload);\n  } else if (eventName == \"destroyworkspacev2\") {\n    onWorkspaceDestroyed(payload);\n  } else if (eventName == \"createworkspacev2\") {\n    onWorkspaceCreated(payload);\n  } else if (eventName == \"focusedmonv2\") {\n    onMonitorFocused(payload);\n  } else if (eventName == \"moveworkspacev2\") {\n    onWorkspaceMoved(payload);\n  } else if (eventName == \"openwindow\") {\n    onWindowOpened(payload);\n  } else if (eventName == \"closewindow\") {\n    onWindowClosed(payload);\n  } else if (eventName == \"movewindowv2\") {\n    onWindowMoved(payload);\n  } else if (eventName == \"urgent\") {\n    setUrgentWorkspace(payload);\n  } else if (eventName == \"renameworkspace\") {\n    onWorkspaceRenamed(payload);\n  } else if (eventName == \"windowtitlev2\") {\n    onWindowTitleEvent(payload);\n  } else if (eventName == \"activewindowv2\") {\n    onActiveWindowChanged(payload);\n  } else if (eventName == \"configreloaded\") {\n    onConfigReloaded();\n  }\n\n  dp.emit();\n}\n\nvoid Workspaces::onWorkspaceActivated(std::string const& payload) {\n  const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload);\n  const auto workspaceId = parseWorkspaceId(workspaceIdStr);\n  if (workspaceId.has_value()) {\n    m_activeWorkspaceId = *workspaceId;\n  }\n}\n\nvoid Workspaces::onSpecialWorkspaceActivated(std::string const& payload) {\n  std::string name(begin(payload), begin(payload) + payload.find_first_of(','));\n  m_activeSpecialWorkspaceName = (!name.starts_with(\"special:\") ? name : name.substr(8));\n}\n\nvoid Workspaces::onWorkspaceDestroyed(std::string const& payload) {\n  const auto [workspaceId, workspaceName] = splitDoublePayload(payload);\n  if (!isDoubleSpecial(workspaceName)) {\n    m_workspacesToRemove.push_back(workspaceId);\n  }\n}\n\nvoid Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value const& clientsData) {\n  spdlog::debug(\"Workspace created: {}\", payload);\n\n  const auto [workspaceIdStr, _] = splitDoublePayload(payload);\n\n  const auto workspaceId = parseWorkspaceId(workspaceIdStr);\n  if (!workspaceId.has_value()) {\n    return;\n  }\n\n  auto const workspaceRules = m_ipc.getSocket1JsonReply(\"workspacerules\");\n  auto const workspacesJson = m_ipc.getSocket1JsonReply(\"workspaces\");\n\n  for (auto workspaceJson : workspacesJson) {\n    const auto currentId = workspaceJson[\"id\"].asInt();\n    if (currentId == *workspaceId) {\n      std::string workspaceName = workspaceJson[\"name\"].asString();\n      // This workspace name is more up-to-date than the one in the event payload.\n      if (isWorkspaceIgnored(workspaceName)) {\n        spdlog::trace(\"Not creating workspace because it is ignored: id={} name={}\", *workspaceId,\n                      workspaceName);\n        break;\n      }\n\n      if ((allOutputs() || m_bar.output->name == workspaceJson[\"monitor\"].asString()) &&\n          (showSpecial() || !workspaceName.starts_with(\"special\")) &&\n          !isDoubleSpecial(workspaceName)) {\n        for (Json::Value const& rule : workspaceRules) {\n          auto ruleWorkspaceName = rule.isMember(\"defaultName\")\n                                       ? rule[\"defaultName\"].asString()\n                                       : rule[\"workspaceString\"].asString();\n          if (ruleWorkspaceName == workspaceName) {\n            workspaceJson[\"persistent-rule\"] = rule[\"persistent\"].asBool();\n            break;\n          }\n        }\n\n        m_workspacesToCreate.emplace_back(workspaceJson, clientsData);\n        break;\n      }\n    } else {\n      extendOrphans(*workspaceId, clientsData);\n    }\n  }\n}\n\nvoid Workspaces::onWorkspaceMoved(std::string const& payload) {\n  spdlog::debug(\"Workspace moved: {}\", payload);\n\n  // Update active workspace\n  m_activeWorkspaceId = (m_ipc.getSocket1JsonReply(\"activeworkspace\"))[\"id\"].asInt();\n\n  if (allOutputs()) return;\n\n  const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload);\n\n  const auto subPayload = makePayload(workspaceIdStr, workspaceName);\n\n  if (m_bar.output->name == monitorName) {\n    Json::Value clientsData = m_ipc.getSocket1JsonReply(\"clients\");\n    onWorkspaceCreated(subPayload, clientsData);\n  } else {\n    spdlog::debug(\"Removing workspace because it was moved to another monitor: {}\", subPayload);\n    onWorkspaceDestroyed(subPayload);\n  }\n}\n\nvoid Workspaces::onWorkspaceRenamed(std::string const& payload) {\n  spdlog::debug(\"Workspace renamed: {}\", payload);\n  const auto [workspaceIdStr, newName] = splitDoublePayload(payload);\n\n  const auto workspaceId = parseWorkspaceId(workspaceIdStr);\n  if (!workspaceId.has_value()) {\n    return;\n  }\n\n  for (auto& workspace : m_workspaces) {\n    if (workspace->id() == *workspaceId) {\n      workspace->setName(newName);\n      break;\n    }\n  }\n  sortWorkspaces();\n}\n\nvoid Workspaces::onMonitorFocused(std::string const& payload) {\n  spdlog::trace(\"Monitor focused: {}\", payload);\n\n  const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload);\n\n  const auto workspaceId = parseWorkspaceId(workspaceIdStr);\n  if (!workspaceId.has_value()) {\n    return;\n  }\n\n  m_activeWorkspaceId = *workspaceId;\n\n  for (Json::Value& monitor : m_ipc.getSocket1JsonReply(\"monitors\")) {\n    if (monitor[\"name\"].asString() == monitorName) {\n      const auto name = monitor[\"specialWorkspace\"][\"name\"].asString();\n      m_activeSpecialWorkspaceName = !name.starts_with(\"special:\") ? name : name.substr(8);\n    }\n  }\n}\n\nvoid Workspaces::onWindowOpened(std::string const& payload) {\n  spdlog::trace(\"Window opened: {}\", payload);\n  updateWindowCount();\n  const auto firstComma = payload.find(',');\n  const auto secondComma =\n      firstComma == std::string::npos ? std::string::npos : payload.find(',', firstComma + 1);\n  const auto thirdComma =\n      secondComma == std::string::npos ? std::string::npos : payload.find(',', secondComma + 1);\n  if (firstComma == std::string::npos || secondComma == std::string::npos ||\n      thirdComma == std::string::npos) {\n    spdlog::warn(\"Malformed Hyprland openwindow payload: {}\", payload);\n    return;\n  }\n\n  std::string windowAddress = payload.substr(0, firstComma);\n  std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);\n  std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);\n  std::string windowTitle = payload.substr(thirdComma + 1);\n\n  bool isActive = m_currentActiveWindowAddress == windowAddress;\n  m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);\n}\n\nvoid Workspaces::onWindowClosed(std::string const& addr) {\n  spdlog::trace(\"Window closed: {}\", addr);\n  updateWindowCount();\n  m_orphanWindowMap.erase(addr);\n  for (auto& workspace : m_workspaces) {\n    if (workspace->closeWindow(addr)) {\n      break;\n    }\n  }\n}\n\nvoid Workspaces::onWindowMoved(std::string const& payload) {\n  spdlog::trace(\"Window moved: {}\", payload);\n  updateWindowCount();\n  auto [windowAddress, _, workspaceName] = splitTriplePayload(payload);\n\n  WindowRepr windowRepr;\n\n  // If the window was still queued to be created, just change its destination\n  // and exit\n  for (auto& window : m_windowsToCreate) {\n    if (window.getAddress() == windowAddress) {\n      window.moveToWorkspace(workspaceName);\n      return;\n    }\n  }\n\n  // Take the window's representation from the old workspace...\n  for (auto& workspace : m_workspaces) {\n    if (auto windowAddr = workspace->closeWindow(windowAddress); windowAddr != std::nullopt) {\n      windowRepr = windowAddr.value();\n      break;\n    }\n  }\n\n  // ...if it was empty, check if the window is an orphan...\n  if (windowRepr.empty() && m_orphanWindowMap.contains(windowAddress)) {\n    windowRepr = m_orphanWindowMap[windowAddress];\n  }\n\n  // ...and then add it to the new workspace\n  if (!windowRepr.empty()) {\n    m_orphanWindowMap.erase(windowAddress);\n    m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr);\n  }\n}\n\nvoid Workspaces::onWindowTitleEvent(std::string const& payload) {\n  spdlog::trace(\"Window title changed: {}\", payload);\n  std::optional<std::function<void(WindowCreationPayload)>> inserter;\n\n  const auto [windowAddress, _] = splitDoublePayload(payload);\n\n  // If the window was an orphan, rename it at the orphan's vector\n  if (m_orphanWindowMap.contains(windowAddress)) {\n    inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); };\n  } else {\n    auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto& workspace) {\n      return workspace->containsWindow(windowAddress);\n    });\n\n    // If the window exists on a workspace, rename it at the workspace's window\n    // map\n    if (windowWorkspace != m_workspaces.end()) {\n      inserter = [windowWorkspace](WindowCreationPayload wcp) {\n        (*windowWorkspace)->insertWindow(std::move(wcp));\n      };\n    } else {\n      auto queuedWindow =\n          std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto& windowPayload) {\n            return windowPayload.getAddress() == windowAddress;\n          });\n\n      // If the window was queued, rename it in the queue\n      if (queuedWindow != m_windowsToCreate.end()) {\n        inserter = [queuedWindow](WindowCreationPayload wcp) { *queuedWindow = std::move(wcp); };\n      }\n    }\n  }\n\n  if (inserter.has_value()) {\n    Json::Value clientsData = m_ipc.getSocket1JsonReply(\"clients\");\n    std::string jsonWindowAddress = fmt::format(\"0x{}\", windowAddress);\n\n    auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto& client) {\n      return client[\"address\"].asString() == jsonWindowAddress;\n    });\n\n    if (client != clientsData.end() && !client->empty()) {\n      (*inserter)({*client});\n    }\n  }\n}\n\nvoid Workspaces::onActiveWindowChanged(WindowAddress const& activeWindowAddress) {\n  spdlog::trace(\"Active window changed: {}\", activeWindowAddress);\n  m_currentActiveWindowAddress = activeWindowAddress;\n\n  for (auto& [address, window] : m_orphanWindowMap) {\n    window.setActive(address == activeWindowAddress);\n  }\n  for (auto const& workspace : m_workspaces) {\n    workspace->setActiveWindow(activeWindowAddress);\n  }\n  for (auto& window : m_windowsToCreate) {\n    window.setActive(window.getAddress() == activeWindowAddress);\n  }\n}\n\nvoid Workspaces::onConfigReloaded() {\n  spdlog::info(\"Hyprland config reloaded, reinitializing hyprland/workspaces module...\");\n  init();\n}\n\nauto Workspaces::parseConfig(const Json::Value& config) -> void {\n  const auto& configFormat = config[\"format\"];\n  m_formatBefore = configFormat.isString() ? configFormat.asString() : \"{name}\";\n  m_withIcon = m_formatBefore.find(\"{icon}\") != std::string::npos;\n  auto withWindows = m_formatBefore.find(\"{windows}\") != std::string::npos;\n\n  if (m_withIcon && m_iconsMap.empty()) {\n    populateIconsMap(config[\"format-icons\"]);\n  }\n\n  populateBoolConfig(config, \"all-outputs\", m_allOutputs);\n  populateBoolConfig(config, \"show-special\", m_showSpecial);\n  populateBoolConfig(config, \"special-visible-only\", m_specialVisibleOnly);\n  populateBoolConfig(config, \"persistent-only\", m_persistentOnly);\n  populateBoolConfig(config, \"active-only\", m_activeOnly);\n  populateBoolConfig(config, \"move-to-monitor\", m_moveToMonitor);\n  populateBoolConfig(config, \"enable-bar-scroll\", m_barScroll);\n\n  m_persistentWorkspaceConfig = config.get(\"persistent-workspaces\", Json::Value());\n  populateSortByConfig(config);\n  populateIgnoreWorkspacesConfig(config);\n  populateFormatWindowSeparatorConfig(config);\n  populateWindowRewriteConfig(config);\n\n  if (withWindows) {\n    populateWorkspaceTaskbarConfig(config);\n  }\n  if (m_enableTaskbar) {\n    auto parts = split(m_formatBefore, \"{windows}\", 1);\n    m_formatBefore = parts[0];\n    m_formatAfter = parts.size() > 1 ? parts[1] : \"\";\n  }\n}\n\nauto Workspaces::populateIconsMap(const Json::Value& formatIcons) -> void {\n  for (const auto& name : formatIcons.getMemberNames()) {\n    m_iconsMap.emplace(name, formatIcons[name].asString());\n  }\n  m_iconsMap.emplace(\"\", \"\");\n}\n\nauto Workspaces::populateBoolConfig(const Json::Value& config, const std::string& key, bool& member)\n    -> void {\n  const auto& configValue = config[key];\n  if (configValue.isBool()) {\n    member = configValue.asBool();\n  }\n}\n\nauto Workspaces::populateSortByConfig(const Json::Value& config) -> void {\n  const auto& configSortBy = config[\"sort-by\"];\n  if (configSortBy.isString()) {\n    auto sortByStr = configSortBy.asString();\n    try {\n      m_sortBy = m_enumParser.parseStringToEnum(sortByStr, m_sortMap);\n    } catch (const std::invalid_argument& e) {\n      m_sortBy = SortMethod::DEFAULT;\n      spdlog::warn(\n          \"Invalid string representation for sort-by. Falling back to default sort method.\");\n    }\n  }\n}\n\nauto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value& config) -> void {\n  auto ignoreWorkspaces = config[\"ignore-workspaces\"];\n  if (ignoreWorkspaces.isArray()) {\n    for (const auto& workspaceRegex : ignoreWorkspaces) {\n      if (workspaceRegex.isString()) {\n        std::string ruleString = workspaceRegex.asString();\n        try {\n          const std::regex rule{ruleString, std::regex_constants::icase};\n          m_ignoreWorkspaces.emplace_back(rule);\n        } catch (const std::regex_error& e) {\n          spdlog::error(\"Invalid rule {}: {}\", ruleString, e.what());\n        }\n      } else {\n        spdlog::error(\"Not a string: '{}'\", workspaceRegex);\n      }\n    }\n  }\n}\n\nauto Workspaces::populateFormatWindowSeparatorConfig(const Json::Value& config) -> void {\n  const auto& formatWindowSeparator = config[\"format-window-separator\"];\n  m_formatWindowSeparator =\n      formatWindowSeparator.isString() ? formatWindowSeparator.asString() : \" \";\n}\n\nauto Workspaces::populateWindowRewriteConfig(const Json::Value& config) -> void {\n  const auto& windowRewrite = config[\"window-rewrite\"];\n  if (!windowRewrite.isObject()) {\n    spdlog::debug(\"window-rewrite is not defined or is not an object, using default rules.\");\n    return;\n  }\n\n  const auto& windowRewriteDefaultConfig = config[\"window-rewrite-default\"];\n  std::string windowRewriteDefault =\n      windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : \"?\";\n\n  m_windowRewriteRules = util::RegexCollection(\n      windowRewrite, windowRewriteDefault,\n      [this](std::string& window_rule) { return windowRewritePriorityFunction(window_rule); });\n}\n\nauto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value& config) -> void {\n  const auto& workspaceTaskbar = config[\"workspace-taskbar\"];\n  if (!workspaceTaskbar.isObject()) {\n    spdlog::debug(\"workspace-taskbar is not defined or is not an object, using default rules.\");\n    return;\n  }\n\n  populateBoolConfig(workspaceTaskbar, \"enable\", m_enableTaskbar);\n  populateBoolConfig(workspaceTaskbar, \"update-active-window\", m_updateActiveWindow);\n  populateBoolConfig(workspaceTaskbar, \"reverse-direction\", m_taskbarReverseDirection);\n\n  if (workspaceTaskbar[\"format\"].isString()) {\n    /* The user defined a format string, use it */\n    std::string format = workspaceTaskbar[\"format\"].asString();\n    m_taskbarWithTitle = format.find(\"{title\") != std::string::npos; /* {title} or {title.length} */\n    auto parts = split(format, \"{icon}\", 1);\n    m_taskbarFormatBefore = parts[0];\n    if (parts.size() > 1) {\n      m_taskbarWithIcon = true;\n      m_taskbarFormatAfter = parts[1];\n    }\n  } else {\n    /* The default is to only show the icon */\n    m_taskbarWithIcon = true;\n  }\n\n  auto iconTheme = workspaceTaskbar[\"icon-theme\"];\n  if (iconTheme.isArray()) {\n    for (auto& c : iconTheme) {\n      m_iconLoader.add_custom_icon_theme(c.asString());\n    }\n  } else if (iconTheme.isString()) {\n    m_iconLoader.add_custom_icon_theme(iconTheme.asString());\n  }\n\n  if (workspaceTaskbar[\"icon-size\"].isInt()) {\n    m_taskbarIconSize = workspaceTaskbar[\"icon-size\"].asInt();\n  }\n  if (workspaceTaskbar[\"orientation\"].isString() &&\n      toLower(workspaceTaskbar[\"orientation\"].asString()) == \"vertical\") {\n    m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL;\n  }\n\n  if (workspaceTaskbar[\"on-click-window\"].isString()) {\n    m_onClickWindow = workspaceTaskbar[\"on-click-window\"].asString();\n  }\n\n  if (workspaceTaskbar[\"ignore-list\"].isArray()) {\n    for (auto& windowRegex : workspaceTaskbar[\"ignore-list\"]) {\n      std::string ruleString = windowRegex.asString();\n      try {\n        m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase);\n      } catch (const std::regex_error& e) {\n        spdlog::error(\"Invalid rule {}: {}\", ruleString, e.what());\n      }\n    }\n  }\n\n  if (workspaceTaskbar[\"active-window-position\"].isString()) {\n    auto posStr = workspaceTaskbar[\"active-window-position\"].asString();\n    try {\n      m_activeWindowPosition =\n          m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap);\n    } catch (const std::invalid_argument& e) {\n      spdlog::warn(\n          \"Invalid string representation for active-window-position. Falling back to 'none'.\");\n      m_activeWindowPosition = ActiveWindowPosition::NONE;\n    }\n  }\n}\n\nvoid Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) {\n  if (!create_window_payload.isEmpty(*this)) {\n    m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this);\n  }\n}\n\nauto Workspaces::registerIpc() -> void {\n  m_ipc.registerForIPC(\"workspacev2\", this);\n  m_ipc.registerForIPC(\"activespecial\", this);\n  m_ipc.registerForIPC(\"createworkspacev2\", this);\n  m_ipc.registerForIPC(\"destroyworkspacev2\", this);\n  m_ipc.registerForIPC(\"focusedmonv2\", this);\n  m_ipc.registerForIPC(\"moveworkspacev2\", this);\n  m_ipc.registerForIPC(\"renameworkspace\", this);\n  m_ipc.registerForIPC(\"openwindow\", this);\n  m_ipc.registerForIPC(\"closewindow\", this);\n  m_ipc.registerForIPC(\"movewindowv2\", this);\n  m_ipc.registerForIPC(\"urgent\", this);\n  m_ipc.registerForIPC(\"configreloaded\", this);\n\n  if (windowRewriteConfigUsesTitle() || m_taskbarWithTitle) {\n    spdlog::info(\n        \"Registering for Hyprland's 'windowtitlev2' events because a user-defined window \"\n        \"rewrite rule uses the 'title' field.\");\n    m_ipc.registerForIPC(\"windowtitlev2\", this);\n  }\n  if (m_updateActiveWindow) {\n    spdlog::info(\n        \"Registering for Hyprland's 'activewindowv2' events because 'update-active-window' is set \"\n        \"to true.\");\n    m_ipc.registerForIPC(\"activewindowv2\", this);\n  }\n}\n\nvoid Workspaces::removeWorkspacesToRemove() {\n  for (const auto& workspaceString : m_workspacesToRemove) {\n    removeWorkspace(workspaceString);\n  }\n  m_workspacesToRemove.clear();\n}\n\nvoid Workspaces::removeWorkspace(std::string const& workspaceString) {\n  spdlog::debug(\"Removing workspace {}\", workspaceString);\n\n  // If this succeeds, we have a workspace ID.\n  const auto workspaceId = parseWorkspaceId(workspaceString);\n\n  std::string name;\n  // TODO: At some point we want to support all workspace selectors\n  // This is just a subset.\n  // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors\n  if (workspaceString.starts_with(\"special:\")) {\n    name = workspaceString.substr(8);\n  } else if (workspaceString.starts_with(\"name:\")) {\n    name = workspaceString.substr(5);\n  } else {\n    name = workspaceString;\n  }\n\n  const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr<Workspace>& x) {\n    if (workspaceId.has_value()) {\n      return *workspaceId == x->id();\n    }\n    return name == x->name();\n  });\n\n  if (workspace == m_workspaces.end()) {\n    // happens when a workspace on another monitor is destroyed\n    return;\n  }\n\n  if ((*workspace)->isPersistentConfig()) {\n    spdlog::trace(\"Not removing config persistent workspace id={} name={}\", (*workspace)->id(),\n                  (*workspace)->name());\n    return;\n  }\n\n  m_box.remove(workspace->get()->button());\n  m_workspaces.erase(workspace);\n}\n\nvoid Workspaces::setCurrentMonitorId() {\n  // get monitor ID from name (used by persistent workspaces)\n  m_monitorId = 0;\n  auto monitors = m_ipc.getSocket1JsonReply(\"monitors\");\n  auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value& m) {\n    return m[\"name\"].asString() == m_bar.output->name;\n  });\n  if (currentMonitor == monitors.end()) {\n    spdlog::error(\"Monitor '{}' does not have an ID? Using 0\", m_bar.output->name);\n  } else {\n    m_monitorId = (*currentMonitor)[\"id\"].asInt();\n    spdlog::trace(\"Current monitor ID: {}\", m_monitorId);\n  }\n}\n\nvoid Workspaces::sortSpecialCentered() {\n  std::vector<std::unique_ptr<Workspace>> specialWorkspaces;\n  std::vector<std::unique_ptr<Workspace>> hiddenWorkspaces;\n  std::vector<std::unique_ptr<Workspace>> normalWorkspaces;\n\n  for (auto& workspace : m_workspaces) {\n    if (workspace->isSpecial()) {\n      specialWorkspaces.push_back(std::move(workspace));\n    } else {\n      if (workspace->button().is_visible()) {\n        normalWorkspaces.push_back(std::move(workspace));\n      } else {\n        hiddenWorkspaces.push_back(std::move(workspace));\n      }\n    }\n  }\n  m_workspaces.clear();\n\n  size_t center = normalWorkspaces.size() / 2;\n\n  m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()),\n                      std::make_move_iterator(normalWorkspaces.begin() + center));\n\n  m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()),\n                      std::make_move_iterator(specialWorkspaces.end()));\n\n  m_workspaces.insert(m_workspaces.end(),\n                      std::make_move_iterator(normalWorkspaces.begin() + center),\n                      std::make_move_iterator(normalWorkspaces.end()));\n\n  m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()),\n                      std::make_move_iterator(hiddenWorkspaces.end()));\n}\n\nvoid Workspaces::sortWorkspaces() {\n  std::ranges::sort(  //\n      m_workspaces, [&](std::unique_ptr<Workspace>& a, std::unique_ptr<Workspace>& b) {\n        // Helper comparisons\n        auto isIdLess = a->id() < b->id();\n        auto isNameLess = a->name() < b->name();\n\n        switch (m_sortBy) {\n          case SortMethod::ID:\n            return isIdLess;\n          case SortMethod::NAME:\n            return isNameLess;\n          case SortMethod::NUMBER:\n            try {\n              return std::stoi(a->name()) < std::stoi(b->name());\n            } catch (const std::exception& e) {\n              // Handle the exception if necessary.\n              break;\n            }\n          case SortMethod::DEFAULT:\n          default:\n            // Handle the default case here.\n            // normal -> named persistent -> named -> special -> named special\n\n            // both normal (includes numbered persistent) => sort by ID\n            if (a->id() > 0 && b->id() > 0) {\n              return isIdLess;\n            }\n\n            // one normal, one special => normal first\n            if ((a->isSpecial()) ^ (b->isSpecial())) {\n              return b->isSpecial();\n            }\n\n            // only one normal, one named\n            if ((a->id() > 0) ^ (b->id() > 0)) {\n              return a->id() > 0;\n            }\n\n            // both special\n            if (a->isSpecial() && b->isSpecial()) {\n              // if one is -99 => put it last\n              if (a->id() == -99 || b->id() == -99) {\n                return b->id() == -99;\n              }\n              // both are 0 (not yet named persistents) / named specials\n              // (-98 <= ID <= -1)\n              return isNameLess;\n            }\n\n            // sort non-special named workspaces by name (ID <= -1377)\n            return isNameLess;\n            break;\n        }\n\n        // Return a default value if none of the cases match.\n        return isNameLess;  // You can adjust this to your specific needs.\n      });\n  if (m_sortBy == SortMethod::SPECIAL_CENTERED) {\n    this->sortSpecialCentered();\n  }\n\n  for (size_t i = 0; i < m_workspaces.size(); ++i) {\n    m_box.reorder_child(m_workspaces[i]->button(), i);\n  }\n}\n\nvoid Workspaces::setUrgentWorkspace(std::string const& windowaddress) {\n  const Json::Value clientsJson = m_ipc.getSocket1JsonReply(\"clients\");\n  const std::string normalizedAddress =\n      windowaddress.starts_with(\"0x\") ? windowaddress : fmt::format(\"0x{}\", windowaddress);\n  int workspaceId = -1;\n\n  for (const auto& clientJson : clientsJson) {\n    if (clientJson[\"address\"].asString() == normalizedAddress) {\n      workspaceId = clientJson[\"workspace\"][\"id\"].asInt();\n      break;\n    }\n  }\n\n  auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr<Workspace>& x) {\n    return x->id() == workspaceId;\n  });\n  if (workspace != m_workspaces.end()) {\n    workspace->get()->setUrgent();\n  }\n}\n\nauto Workspaces::update() -> void {\n  doUpdate();\n  AModule::update();\n}\n\nvoid Workspaces::updateWindowCount() {\n  const Json::Value workspacesJson = m_ipc.getSocket1JsonReply(\"workspaces\");\n  for (auto const& workspace : m_workspaces) {\n    auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const& x) {\n      return x[\"name\"].asString() == workspace->name() ||\n             (workspace->isSpecial() && x[\"name\"].asString() == \"special:\" + workspace->name());\n    });\n    uint32_t count = 0;\n    if (workspaceJson != workspacesJson.end()) {\n      try {\n        count = (*workspaceJson)[\"windows\"].asUInt();\n      } catch (const std::exception& e) {\n        spdlog::error(\"Failed to update window count: {}\", e.what());\n      }\n    }\n    workspace->setWindows(count);\n  }\n}\n\nbool Workspaces::updateWindowsToCreate() {\n  bool anyWindowCreated = false;\n  std::vector<WindowCreationPayload> notCreated;\n  for (auto& windowPayload : m_windowsToCreate) {\n    bool created = false;\n    for (auto& workspace : m_workspaces) {\n      if (workspace->onWindowOpened(windowPayload)) {\n        created = true;\n        anyWindowCreated = true;\n        break;\n      }\n    }\n    if (!created) {\n      static auto const WINDOW_CREATION_TIMEOUT = 2;\n      if (windowPayload.incrementTimeSpentUncreated() < WINDOW_CREATION_TIMEOUT) {\n        notCreated.push_back(windowPayload);\n      } else {\n        registerOrphanWindow(windowPayload);\n      }\n    }\n  }\n  m_windowsToCreate.clear();\n  m_windowsToCreate = notCreated;\n  return anyWindowCreated;\n}\n\nvoid Workspaces::updateWorkspaceStates() {\n  const std::vector<int> visibleWorkspaces = getVisibleWorkspaces();\n  auto updatedWorkspaces = m_ipc.getSocket1JsonReply(\"workspaces\");\n\n  auto currentWorkspace = m_ipc.getSocket1JsonReply(\"activeworkspace\");\n  std::string currentWorkspaceName =\n      currentWorkspace.isMember(\"name\") ? currentWorkspace[\"name\"].asString() : \"\";\n\n  for (auto& workspace : m_workspaces) {\n    bool isActiveByName =\n        !currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName;\n\n    workspace->setActive(\n        workspace->id() == m_activeWorkspaceId || isActiveByName ||\n        (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName));\n    if (workspace->isActive() && workspace->isUrgent()) {\n      workspace->setUrgent(false);\n    }\n    workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) !=\n                          visibleWorkspaces.end());\n    std::string& workspaceIcon = m_iconsMap[\"\"];\n    if (m_withIcon) {\n      workspaceIcon = workspace->selectIcon(m_iconsMap);\n    }\n    auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto& w) {\n      auto wNameRaw = w[\"name\"].asString();\n      auto wName = wNameRaw.starts_with(\"special:\") ? wNameRaw.substr(8) : wNameRaw;\n      return wName == workspace->name();\n    });\n    if (updatedWorkspace != updatedWorkspaces.end()) {\n      workspace->setOutput((*updatedWorkspace)[\"monitor\"].asString());\n    }\n    workspace->update(workspaceIcon);\n  }\n}\n\nint Workspaces::windowRewritePriorityFunction(std::string const& window_rule) {\n  // Rules that match against title are prioritized\n  // Rules that don't specify if they're matching against either title or class are deprioritized\n  bool const hasTitle = window_rule.find(\"title\") != std::string::npos;\n  bool const hasClass = window_rule.find(\"class\") != std::string::npos;\n\n  if (hasTitle && hasClass) {\n    m_anyWindowRewriteRuleUsesTitle = true;\n    return 3;\n  }\n  if (hasTitle) {\n    m_anyWindowRewriteRuleUsesTitle = true;\n    return 2;\n  }\n  if (hasClass) {\n    return 1;\n  }\n  return 0;\n}\n\ntemplate <typename... Args>\nstd::string Workspaces::makePayload(Args const&... args) {\n  std::ostringstream result;\n  bool first = true;\n  ((result << (first ? \"\" : \",\") << args, first = false), ...);\n  return result.str();\n}\n\nstd::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) {\n  const auto separator = payload.find(',');\n  if (separator == std::string::npos) {\n    throw std::invalid_argument(\"Expected a two-part Hyprland payload\");\n  }\n  const std::string part1 = payload.substr(0, separator);\n  const std::string part2 = payload.substr(part1.size() + 1);\n  return {part1, part2};\n}\n\nstd::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload(\n    std::string const& payload) {\n  const size_t firstComma = payload.find(',');\n  const size_t secondComma = payload.find(',', firstComma + 1);\n  if (firstComma == std::string::npos || secondComma == std::string::npos) {\n    throw std::invalid_argument(\"Expected a three-part Hyprland payload\");\n  }\n\n  const std::string part1 = payload.substr(0, firstComma);\n  const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));\n  const std::string part3 = payload.substr(secondComma + 1);\n\n  return {part1, part2, part3};\n}\n\nstd::optional<int> Workspaces::parseWorkspaceId(std::string const& workspaceIdStr) {\n  try {\n    return workspaceIdStr == \"special\" ? -99 : std::stoi(workspaceIdStr);\n  } catch (std::exception const& e) {\n    spdlog::debug(\"Workspace \\\"{}\\\" is not bound to an id: {}\", workspaceIdStr, e.what());\n    return std::nullopt;\n  }\n}\n\nbool Workspaces::handleScroll(GdkEventScroll* e) {\n  // Ignore emulated scroll events on window\n  if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {\n    return false;\n  }\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) {\n    return true;\n  }\n\n  if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {\n    if (allOutputs()) {\n      m_ipc.getSocket1Reply(\"dispatch workspace e+1\");\n    } else {\n      m_ipc.getSocket1Reply(\"dispatch workspace m+1\");\n    }\n  } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {\n    if (allOutputs()) {\n      m_ipc.getSocket1Reply(\"dispatch workspace e-1\");\n    } else {\n      m_ipc.getSocket1Reply(\"dispatch workspace m-1\");\n    }\n  }\n\n  return true;\n}\n\n}  // namespace waybar::modules::hyprland\n"
  },
  {
    "path": "src/modules/idle_inhibitor.cpp",
    "content": "#include \"modules/idle_inhibitor.hpp\"\n\n#include \"idle-inhibit-unstable-v1-client-protocol.h\"\n#include \"util/command.hpp\"\n\nstd::list<waybar::AModule*> waybar::modules::IdleInhibitor::modules;\nbool waybar::modules::IdleInhibitor::status = false;\n\nwaybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,\n                                              const Json::Value& config)\n    : ALabel(config, \"idle_inhibitor\", id, \"{status}\", 0, false, true),\n      bar_(bar),\n      idle_inhibitor_(nullptr),\n      pid_(-1) {\n  if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {\n    throw std::runtime_error(\"idle-inhibit not available\");\n  }\n\n  if (waybar::modules::IdleInhibitor::modules.empty() && config_[\"start-activated\"].isBool() &&\n      config_[\"start-activated\"].asBool() != status) {\n    toggleStatus();\n  }\n\n  event_box_.add_events(Gdk::BUTTON_PRESS_MASK);\n  event_box_.signal_button_press_event().connect(\n      sigc::mem_fun(*this, &IdleInhibitor::handleToggle));\n\n  // Add this to the modules list\n  waybar::modules::IdleInhibitor::modules.push_back(this);\n\n  dp.emit();\n}\n\nwaybar::modules::IdleInhibitor::~IdleInhibitor() {\n  if (idle_inhibitor_ != nullptr) {\n    zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);\n    idle_inhibitor_ = nullptr;\n  }\n\n  // Remove this from the modules list\n  waybar::modules::IdleInhibitor::modules.remove(this);\n\n  if (pid_ != -1) {\n    kill(-pid_, 9);\n    pid_ = -1;\n  }\n}\n\nauto waybar::modules::IdleInhibitor::update() -> void {\n  // Check status\n  if (status) {\n    label_.get_style_context()->remove_class(\"deactivated\");\n    if (idle_inhibitor_ == nullptr) {\n      idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(\n          waybar::Client::inst()->idle_inhibit_manager, bar_.surface);\n    }\n  } else {\n    label_.get_style_context()->remove_class(\"activated\");\n    if (idle_inhibitor_ != nullptr) {\n      zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);\n      idle_inhibitor_ = nullptr;\n    }\n  }\n\n  std::string status_text = status ? \"activated\" : \"deactivated\";\n  label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg(\"status\", status_text),\n                                fmt::arg(\"icon\", getIcon(0, status_text))));\n  label_.get_style_context()->add_class(status_text);\n  if (tooltipEnabled()) {\n    auto config = config_[status ? \"tooltip-format-activated\" : \"tooltip-format-deactivated\"];\n    auto tooltip_format = config.isString() ? config.asString() : \"{status}\";\n    label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),\n                                          fmt::arg(\"status\", status_text),\n                                          fmt::arg(\"icon\", getIcon(0, status_text))));\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nvoid waybar::modules::IdleInhibitor::toggleStatus() {\n  status = !status;\n\n  if (timeout_.connected()) {\n    /* cancel any already active timeout handler */\n    timeout_.disconnect();\n  }\n\n  if (status && config_[\"timeout\"].isNumeric()) {\n    auto timeoutMins = config_[\"timeout\"].asDouble();\n    int timeoutSecs = timeoutMins * 60;\n\n    timeout_ = Glib::signal_timeout().connect_seconds(\n        []() {\n          /* intentionally not tied to a module instance lifetime\n           * as the output with `this` can be disconnected\n           */\n          spdlog::info(\"deactivating idle_inhibitor by timeout\");\n          status = false;\n          for (auto const& module : waybar::modules::IdleInhibitor::modules) {\n            module->update();\n          }\n          /* disconnect */\n          return false;\n        },\n        timeoutSecs);\n  }\n}\n\nbool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {\n  if (e->button == 1) {\n    toggleStatus();\n\n    // Make all other idle inhibitor modules update\n    for (auto const& module : waybar::modules::IdleInhibitor::modules) {\n      if (module != this) {\n        module->update();\n      }\n    }\n  }\n\n  ALabel::handleToggle(e);\n  return true;\n}\n"
  },
  {
    "path": "src/modules/image.cpp",
    "content": "#include \"modules/image.hpp\"\n\nwaybar::modules::Image::Image(const std::string& id, const Json::Value& config)\n    : AModule(config, \"image\", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {\n  box_.pack_start(image_);\n  box_.set_name(\"image\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  dp.emit();\n\n  size_ = config[\"size\"].asInt();\n\n  const auto once = std::chrono::milliseconds::max();\n  if (!config_.isMember(\"interval\") || config_[\"interval\"].isNull() ||\n      config_[\"interval\"] == \"once\") {\n    interval_ = once;\n  } else if (config_[\"interval\"].isNumeric()) {\n    const auto interval_seconds = config_[\"interval\"].asDouble();\n    if (interval_seconds <= 0) {\n      interval_ = once;\n    } else {\n      interval_ =\n          std::chrono::milliseconds(std::max(1L,  // Minimum 1ms due to millisecond precision\n                                             static_cast<long>(interval_seconds * 1000)));\n    }\n  } else {\n    interval_ = once;\n  }\n\n  if (size_ == 0) {\n    size_ = 16;\n  }\n\n  delayWorker();\n}\n\nvoid waybar::modules::Image::delayWorker() {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nvoid waybar::modules::Image::refresh(int sig) {\n  if (config_[\"signal\"].isInt() && sig == SIGRTMIN + config_[\"signal\"].asInt()) {\n    thread_.wake_up();\n  }\n}\n\nauto waybar::modules::Image::update() -> void {\n  if (config_[\"path\"].isString()) {\n    path_ = config_[\"path\"].asString();\n  } else if (config_[\"exec\"].isString()) {\n    output_ = util::command::exec(config_[\"exec\"].asString(), \"\");\n    parseOutputRaw();\n  } else {\n    path_ = \"\";\n  }\n\n  if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) {\n    Glib::RefPtr<Gdk::Pixbuf> pixbuf;\n\n    int scaled_icon_size = size_ * image_.get_scale_factor();\n    pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size);\n\n    auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(),\n                                                          image_.get_window());\n    image_.set(surface);\n    image_.show();\n\n    if (tooltipEnabled() && !tooltip_.empty()) {\n      if (box_.get_tooltip_markup() != tooltip_) {\n        box_.set_tooltip_markup(tooltip_);\n      }\n    }\n\n    box_.get_style_context()->remove_class(\"empty\");\n  } else {\n    image_.clear();\n    image_.hide();\n    box_.get_style_context()->add_class(\"empty\");\n  }\n\n  AModule::update();\n}\n\nvoid waybar::modules::Image::parseOutputRaw() {\n  std::istringstream output(output_.out);\n  std::string line;\n  int i = 0;\n  while (getline(output, line)) {\n    if (i == 0) {\n      path_ = line;\n    } else if (i == 1) {\n      tooltip_ = line;\n    } else {\n      break;\n    }\n    i++;\n  }\n}\n"
  },
  {
    "path": "src/modules/inhibitor.cpp",
    "content": "#include \"modules/inhibitor.hpp\"\n\n#include <gio/gio.h>\n#include <gio/gunixfdlist.h>\n#include <spdlog/spdlog.h>\n\nnamespace {\n\nusing DBus = std::unique_ptr<GDBusConnection, void (*)(GDBusConnection*)>;\n\nauto dbus() -> DBus {\n  GError* error = nullptr;\n  GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);\n\n  if (error) {\n    spdlog::error(\"g_bus_get_sync() failed: {}\", error->message);\n    g_error_free(error);\n    connection = nullptr;\n  }\n\n  auto destructor = [](GDBusConnection* connection) {\n    GError* error = nullptr;\n    g_dbus_connection_close_sync(connection, nullptr, &error);\n    if (error) {\n      spdlog::error(\"g_bus_connection_close_sync failed(): {}\", error->message);\n      g_error_free(error);\n    }\n  };\n\n  return DBus{connection, destructor};\n}\n\nauto getLocks(const DBus& bus, const std::string& inhibitors) -> int {\n  GError* error = nullptr;\n  GUnixFDList* fd_list;\n  int handle;\n\n  auto reply = g_dbus_connection_call_with_unix_fd_list_sync(\n      bus.get(), \"org.freedesktop.login1\", \"/org/freedesktop/login1\",\n      \"org.freedesktop.login1.Manager\", \"Inhibit\",\n      g_variant_new(\"(ssss)\", inhibitors.c_str(), \"waybar\", \"Asked by user\", \"block\"),\n      G_VARIANT_TYPE(\"(h)\"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &fd_list, nullptr, &error);\n  if (error) {\n    spdlog::error(\"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}\", error->message);\n    g_error_free(error);\n    handle = -1;\n  } else {\n    gint index;\n    g_variant_get(reply, \"(h)\", &index);\n    g_variant_unref(reply);\n    handle = g_unix_fd_list_get(fd_list, index, nullptr);\n    g_object_unref(fd_list);\n  }\n\n  return handle;\n}\n\nauto checkInhibitor(const std::string& inhibitor) -> const std::string& {\n  static const auto inhibitors = std::array{\"idle\",\n                                            \"shutdown\",\n                                            \"sleep\",\n                                            \"handle-power-key\",\n                                            \"handle-suspend-key\",\n                                            \"handle-hibernate-key\",\n                                            \"handle-lid-switch\"};\n\n  if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) == inhibitors.end()) {\n    throw std::runtime_error(\"invalid logind inhibitor \" + inhibitor);\n  }\n\n  return inhibitor;\n}\n\nauto getInhibitors(const Json::Value& config) -> std::string {\n  std::string inhibitors = \"idle\";\n\n  if (config[\"what\"].empty()) {\n    return inhibitors;\n  }\n\n  if (config[\"what\"].isString()) {\n    return checkInhibitor(config[\"what\"].asString());\n  }\n\n  if (config[\"what\"].isArray()) {\n    inhibitors = checkInhibitor(config[\"what\"][0].asString());\n    for (decltype(config[\"what\"].size()) i = 1; i < config[\"what\"].size(); ++i) {\n      inhibitors.append(\":\");\n      inhibitors.append(checkInhibitor(config[\"what\"][i].asString()));\n    }\n    return inhibitors;\n  }\n\n  return inhibitors;\n}\n\n}  // namespace\n\nnamespace waybar::modules {\n\nInhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config)\n    : ALabel(config, \"inhibitor\", id, \"{status}\", true),\n      dbus_(::dbus()),\n      inhibitors_(::getInhibitors(config)) {\n  event_box_.add_events(Gdk::BUTTON_PRESS_MASK);\n  event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Inhibitor::handleToggle));\n  dp.emit();\n}\n\nInhibitor::~Inhibitor() {\n  if (handle_ != -1) {\n    ::close(handle_);\n  }\n}\n\nauto Inhibitor::activated() -> bool { return handle_ != -1; }\n\nauto Inhibitor::update() -> void {\n  std::string status_text = activated() ? \"activated\" : \"deactivated\";\n\n  label_.get_style_context()->remove_class(activated() ? \"deactivated\" : \"activated\");\n  label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg(\"status\", status_text),\n                                fmt::arg(\"icon\", getIcon(0, status_text))));\n  label_.get_style_context()->add_class(status_text);\n\n  if (tooltipEnabled()) {\n    label_.set_tooltip_markup(status_text);\n  }\n\n  return ALabel::update();\n}\n\nauto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {\n  if (e->button == 1) {\n    if (activated()) {\n      ::close(handle_);\n      handle_ = -1;\n    } else {\n      handle_ = ::getLocks(dbus_, inhibitors_);\n      if (handle_ == -1) {\n        spdlog::error(\"cannot get inhibitor locks\");\n      }\n    }\n  }\n\n  return ALabel::handleToggle(e);\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/jack.cpp",
    "content": "#include \"modules/jack.hpp\"\n\nnamespace waybar::modules {\n\nJACK::JACK(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"jack\", id, \"{load}%\", 1) {\n  running_ = false;\n  client_ = NULL;\n\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nstd::string JACK::JACKState() {\n  std::lock_guard<std::mutex> lock(mutex_);\n  if (running_) {\n    load_ = jack_cpu_load(client_);\n    return state_;\n  }\n\n  xruns_ = 0;\n  load_ = 0;\n  bufsize_ = 0;\n  samplerate_ = 0;\n\n  if (client_) {\n    jack_client_close(client_);\n    client_ = NULL;\n  }\n\n  client_ = jack_client_open(\"waybar\", JackNoStartServer, NULL);\n  if (!client_) return \"disconnected\";\n\n  if (config_[\"realtime\"].isBool() && !config_[\"realtime\"].asBool()) {\n    pthread_t jack_thread = jack_client_thread_id(client_);\n    jack_drop_real_time_scheduling(jack_thread);\n  }\n\n  bufsize_ = jack_get_buffer_size(client_);\n  samplerate_ = jack_get_sample_rate(client_);\n  jack_set_sample_rate_callback(client_, sampleRateCallback, this);\n  jack_set_buffer_size_callback(client_, bufSizeCallback, this);\n  jack_set_xrun_callback(client_, xrunCallback, this);\n  jack_on_shutdown(client_, shutdownCallback, this);\n  if (jack_activate(client_)) return \"disconnected\";\n\n  running_ = true;\n  return \"connected\";\n}\n\nauto JACK::update() -> void {\n  std::string format;\n  std::string state = JACKState();\n  float latency = samplerate_ > 0 ? 1000.0f * (float)bufsize_ / (float)samplerate_ : 0.0f;\n\n  if (label_.get_style_context()->has_class(\"xrun\")) {\n    label_.get_style_context()->remove_class(\"xrun\");\n    state = \"connected\";\n  }\n\n  if (label_.get_style_context()->has_class(state_))\n    label_.get_style_context()->remove_class(state_);\n  label_.get_style_context()->add_class(state);\n  state_ = state;\n\n  if (config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  } else if (config_[\"format\"].isString()) {\n    format = config_[\"format\"].asString();\n  } else\n    format = \"{load}%\";\n\n  label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg(\"load\", std::round(load_)),\n                                fmt::arg(\"bufsize\", bufsize_), fmt::arg(\"samplerate\", samplerate_),\n                                fmt::arg(\"latency\", fmt::format(\"{:.2f}\", latency)),\n                                fmt::arg(\"xruns\", xruns_)));\n\n  if (tooltipEnabled()) {\n    std::string tooltip_format = \"{bufsize}/{samplerate} {latency}ms\";\n    if (config_[\"tooltip-format\"].isString()) tooltip_format = config_[\"tooltip-format\"].asString();\n    label_.set_tooltip_markup(fmt::format(\n        fmt::runtime(tooltip_format), fmt::arg(\"load\", std::round(load_)),\n        fmt::arg(\"bufsize\", bufsize_), fmt::arg(\"samplerate\", samplerate_),\n        fmt::arg(\"latency\", fmt::format(\"{:.2f}\", latency)), fmt::arg(\"xruns\", xruns_)));\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nint JACK::bufSize(jack_nframes_t size) {\n  std::lock_guard<std::mutex> lock(mutex_);\n  bufsize_ = size;\n  return 0;\n}\n\nint JACK::sampleRate(jack_nframes_t rate) {\n  std::lock_guard<std::mutex> lock(mutex_);\n  samplerate_ = rate;\n  return 0;\n}\n\nint JACK::xrun() {\n  std::lock_guard<std::mutex> lock(mutex_);\n  xruns_ += 1;\n  state_ = \"xrun\";\n  return 0;\n}\n\nvoid JACK::shutdown() {\n  std::lock_guard<std::mutex> lock(mutex_);\n  running_ = false;\n}\n\n}  // namespace waybar::modules\n\nint bufSizeCallback(jack_nframes_t size, void* obj) {\n  return static_cast<waybar::modules::JACK*>(obj)->bufSize(size);\n}\n\nint sampleRateCallback(jack_nframes_t rate, void* obj) {\n  return static_cast<waybar::modules::JACK*>(obj)->sampleRate(rate);\n}\n\nint xrunCallback(void* obj) { return static_cast<waybar::modules::JACK*>(obj)->xrun(); }\n\nvoid shutdownCallback(void* obj) { return static_cast<waybar::modules::JACK*>(obj)->shutdown(); }\n"
  },
  {
    "path": "src/modules/keyboard_state.cpp",
    "content": "#include \"modules/keyboard_state.hpp\"\n\n#include <errno.h>\n#include <spdlog/spdlog.h>\n#include <string.h>\n\n#include <filesystem>\n\nextern \"C\" {\n#include <fcntl.h>\n#include <libinput.h>\n#include <linux/input-event-codes.h>\n#include <poll.h>\n#include <sys/inotify.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n}\n\nclass errno_error : public std::runtime_error {\n public:\n  int code;\n  errno_error(int code, const std::string& msg)\n      : std::runtime_error(getErrorMsg(code, msg.c_str())), code(code) {}\n  errno_error(int code, const char* msg) : std::runtime_error(getErrorMsg(code, msg)), code(code) {}\n\n private:\n  static auto getErrorMsg(int err, const char* msg) -> std::string {\n    std::string error_msg{msg};\n    error_msg += \": \";\n\n#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 32)\n    // strerrorname_np gets the error code's name; it's nice to have, but it's a recent GNU\n    // extension\n    const auto errno_name = strerrorname_np(err);\n    error_msg += errno_name;\n    error_msg += \" \";\n#endif\n\n    const auto errno_str = strerror(err);\n    error_msg += errno_str;\n\n    return error_msg;\n  }\n};\n\nauto openFile(const std::string& path, int flags) -> int {\n  int fd = open(path.c_str(), flags);\n  if (fd < 0) {\n    if (errno == EACCES) {\n      throw errno_error(errno, \"Can't open \" + path + \" (are you in the input group?)\");\n    } else {\n      throw errno_error(errno, \"Can't open \" + path);\n    }\n  }\n  return fd;\n}\n\nauto closeFile(int fd) -> void {\n  int res = close(fd);\n  if (res < 0) {\n    throw errno_error(errno, \"Can't close file\");\n  }\n}\n\nauto openDevice(int fd) -> libevdev* {\n  libevdev* dev;\n  int err = libevdev_new_from_fd(fd, &dev);\n  if (err < 0) {\n    throw errno_error(-err, \"Can't create libevdev device\");\n  }\n  return dev;\n}\n\nauto supportsLockStates(const libevdev* dev) -> bool {\n  return libevdev_has_event_type(dev, EV_LED) && libevdev_has_event_code(dev, EV_LED, LED_NUML) &&\n         libevdev_has_event_code(dev, EV_LED, LED_CAPSL) &&\n         libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);\n}\n\nwaybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,\n                                              const Json::Value& config)\n    : AModule(config, \"keyboard-state\", id, false, !config[\"disable-scroll\"].asBool()),\n      box_(bar.orientation, 0),\n      numlock_label_(\"\"),\n      capslock_label_(\"\"),\n      numlock_format_(config_[\"format\"].isString() ? config_[\"format\"].asString()\n                      : config_[\"format\"][\"numlock\"].isString()\n                          ? config_[\"format\"][\"numlock\"].asString()\n                          : \"{name} {icon}\"),\n      capslock_format_(config_[\"format\"].isString() ? config_[\"format\"].asString()\n                       : config_[\"format\"][\"capslock\"].isString()\n                           ? config_[\"format\"][\"capslock\"].asString()\n                           : \"{name} {icon}\"),\n      scrolllock_format_(config_[\"format\"].isString() ? config_[\"format\"].asString()\n                         : config_[\"format\"][\"scrolllock\"].isString()\n                             ? config_[\"format\"][\"scrolllock\"].asString()\n                             : \"{name} {icon}\"),\n      interval_(\n          std::chrono::seconds(config_[\"interval\"].isUInt() ? config_[\"interval\"].asUInt() : 1)),\n      icon_locked_(config_[\"format-icons\"][\"locked\"].isString()\n                       ? config_[\"format-icons\"][\"locked\"].asString()\n                       : \"locked\"),\n      icon_unlocked_(config_[\"format-icons\"][\"unlocked\"].isString()\n                         ? config_[\"format-icons\"][\"unlocked\"].asString()\n                         : \"unlocked\"),\n      devices_path_(\"/dev/input/\"),\n      libinput_(nullptr),\n      libinput_devices_({}) {\n  static struct libinput_interface interface = {\n      [](const char* path, int flags, void* user_data) { return open(path, flags); },\n      [](int fd, void* user_data) { close(fd); }};\n  if (config_[\"interval\"].isUInt()) {\n    spdlog::warn(\"keyboard-state: interval is deprecated\");\n  }\n\n  libinput_ = libinput_path_create_context(&interface, NULL);\n\n  box_.set_name(\"keyboard-state\");\n  if (config_[\"numlock\"].asBool()) {\n    numlock_label_.get_style_context()->add_class(\"numlock\");\n    box_.pack_end(numlock_label_, false, false, 0);\n  }\n  if (config_[\"capslock\"].asBool()) {\n    capslock_label_.get_style_context()->add_class(\"capslock\");\n    box_.pack_end(capslock_label_, false, false, 0);\n  }\n  if (config_[\"scrolllock\"].asBool()) {\n    scrolllock_label_.get_style_context()->add_class(\"scrolllock\");\n    box_.pack_end(scrolllock_label_, false, false, 0);\n  }\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  if (config_[\"device-path\"].isString()) {\n    std::string dev_path = config_[\"device-path\"].asString();\n    tryAddDevice(dev_path);\n    if (libinput_devices_.empty()) {\n      spdlog::error(\"keyboard-state: Cannot find device {}\", dev_path);\n    }\n  }\n\n  auto keys = config_[\"binding-keys\"];\n  if (keys.isArray()) {\n    for (const auto& key : keys) {\n      if (key.isInt()) {\n        binding_keys.insert(key.asInt());\n      } else {\n        spdlog::warn(\"Cannot read key binding {} as int.\", key.asString());\n      }\n    }\n  } else {\n    binding_keys.insert(KEY_CAPSLOCK);\n    binding_keys.insert(KEY_NUMLOCK);\n    binding_keys.insert(KEY_SCROLLLOCK);\n  }\n\n  DIR* dev_dir = opendir(devices_path_.c_str());\n  if (dev_dir == nullptr) {\n    throw errno_error(errno, \"Failed to open \" + devices_path_);\n  }\n  dirent* ep;\n  while ((ep = readdir(dev_dir))) {\n    if (ep->d_type == DT_DIR) continue;\n    std::string dev_path = devices_path_ + ep->d_name;\n    tryAddDevice(dev_path);\n  }\n\n  if (libinput_devices_.empty()) {\n    throw errno_error(errno, \"Failed to find keyboard device\");\n  }\n\n  libinput_thread_ = [this] {\n    dp.emit();\n    while (1) {\n      struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};\n      poll(&fd, 1, -1);\n      libinput_dispatch(libinput_);\n      struct libinput_event* event;\n      while ((event = libinput_get_event(libinput_))) {\n        auto type = libinput_event_get_type(event);\n        if (type == LIBINPUT_EVENT_KEYBOARD_KEY) {\n          auto keyboard_event = libinput_event_get_keyboard_event(event);\n          auto state = libinput_event_keyboard_get_key_state(keyboard_event);\n          if (state == LIBINPUT_KEY_STATE_RELEASED) {\n            uint32_t key = libinput_event_keyboard_get_key(keyboard_event);\n            if (binding_keys.contains(key)) {\n              dp.emit();\n            }\n          }\n        }\n        libinput_event_destroy(event);\n      }\n    }\n  };\n\n  hotplug_thread_ = [this] {\n    int fd;\n    fd = inotify_init();\n    if (fd < 0) {\n      spdlog::error(\"Failed to initialize inotify: {}\", strerror(errno));\n      return;\n    }\n    inotify_add_watch(fd, devices_path_.c_str(), IN_CREATE | IN_DELETE);\n    while (1) {\n      int BUF_LEN = 1024 * (sizeof(struct inotify_event) + 16);\n      char buf[BUF_LEN];\n      int length = read(fd, buf, 1024);\n      if (length < 0) {\n        spdlog::error(\"Failed to read inotify: {}\", strerror(errno));\n        return;\n      }\n      for (int i = 0; i < length;) {\n        struct inotify_event* event = (struct inotify_event*)&buf[i];\n        std::string dev_path = devices_path_ + event->name;\n        if (event->mask & IN_CREATE) {\n          // Wait for device setup\n          int timeout = 10;\n          while (timeout--) {\n            try {\n              int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);\n              closeFile(fd);\n              break;\n            } catch (const errno_error& e) {\n              if (e.code == EACCES) {\n                sleep(1);\n              }\n            }\n          }\n          tryAddDevice(dev_path);\n        } else if (event->mask & IN_DELETE) {\n          std::lock_guard<std::mutex> lock(devices_mutex_);\n          auto it = libinput_devices_.find(dev_path);\n          if (it != libinput_devices_.end()) {\n            spdlog::info(\"Keyboard {} has been removed.\", dev_path);\n            libinput_path_remove_device(it->second);\n            libinput_device_unref(it->second);\n            libinput_devices_.erase(it);\n          }\n        }\n        i += sizeof(struct inotify_event) + event->len;\n      }\n    }\n  };\n}\n\nwaybar::modules::KeyboardState::~KeyboardState() {\n  std::lock_guard<std::mutex> lock(devices_mutex_);\n  for (const auto& [_, dev_ptr] : libinput_devices_) {\n    libinput_path_remove_device(dev_ptr);\n  }\n}\n\nauto waybar::modules::KeyboardState::update() -> void {\n  sleep(0);  // Wait for keyboard status change\n  int numl = 0, capsl = 0, scrolll = 0;\n\n  try {\n    std::string dev_path;\n    {\n      std::lock_guard<std::mutex> lock(devices_mutex_);\n      if (libinput_devices_.empty()) {\n        return;\n      }\n      if (config_[\"device-path\"].isString() &&\n          libinput_devices_.find(config_[\"device-path\"].asString()) != libinput_devices_.end()) {\n        dev_path = config_[\"device-path\"].asString();\n      } else {\n        dev_path = libinput_devices_.begin()->first;\n      }\n    }\n    int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);\n    auto dev = openDevice(fd);\n    numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);\n    capsl = libevdev_get_event_value(dev, EV_LED, LED_CAPSL);\n    scrolll = libevdev_get_event_value(dev, EV_LED, LED_SCROLLL);\n    libevdev_free(dev);\n    closeFile(fd);\n  } catch (const errno_error& e) {\n    // ENOTTY just means the device isn't an evdev device, skip it\n    if (e.code != ENOTTY) {\n      spdlog::warn(e.what());\n    }\n  }\n\n  struct {\n    bool state;\n    Gtk::Label& label;\n    const std::string& format;\n    const char* name;\n  } label_states[] = {\n      {(bool)numl, numlock_label_, numlock_format_, \"Num\"},\n      {(bool)capsl, capslock_label_, capslock_format_, \"Caps\"},\n      {(bool)scrolll, scrolllock_label_, scrolllock_format_, \"Scroll\"},\n  };\n  for (auto& label_state : label_states) {\n    std::string text;\n    text = fmt::format(fmt::runtime(label_state.format),\n                       fmt::arg(\"icon\", label_state.state ? icon_locked_ : icon_unlocked_),\n                       fmt::arg(\"name\", label_state.name));\n    label_state.label.set_markup(text);\n    if (label_state.state) {\n      label_state.label.get_style_context()->add_class(\"locked\");\n    } else {\n      label_state.label.get_style_context()->remove_class(\"locked\");\n    }\n  }\n\n  AModule::update();\n}\n\nauto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) -> void {\n  try {\n    int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);\n    auto dev = openDevice(fd);\n    if (supportsLockStates(dev)) {\n      spdlog::info(\"Found device {} at '{}'\", libevdev_get_name(dev), dev_path);\n      std::lock_guard<std::mutex> lock(devices_mutex_);\n      if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {\n        auto device = libinput_path_add_device(libinput_, dev_path.c_str());\n        if (device) {\n          libinput_device_ref(device);\n          libinput_devices_[dev_path] = device;\n        } else {\n          spdlog::warn(\"keyboard-state: Failed to add device to libinput: {}\", dev_path);\n        }\n      }\n    }\n    libevdev_free(dev);\n    closeFile(fd);\n  } catch (const errno_error& e) {\n    // ENOTTY just means the device isn't an evdev device, skip it\n    if (e.code != ENOTTY) {\n      spdlog::warn(e.what());\n    }\n  }\n}\n"
  },
  {
    "path": "src/modules/load.cpp",
    "content": "#include \"modules/load.hpp\"\n\n// In the 80000 version of fmt library authors decided to optimize imports\n// and moved declarations required for fmt::dynamic_format_arg_store in new\n// header fmt/args.h\n#if (FMT_VERSION >= 80000)\n#include <fmt/args.h>\n#else\n#include <fmt/core.h>\n#endif\n\nwaybar::modules::Load::Load(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"load\", id, \"{load1}\", 10) {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::Load::update() -> void {\n  // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both\n  auto [load1, load5, load15] = Load::getLoad();\n  if (tooltipEnabled()) {\n    auto tooltip = fmt::format(\"Load 1: {}\\nLoad 5: {}\\nLoad 15: {}\", load1, load5, load15);\n    label_.set_tooltip_markup(tooltip);\n  }\n  auto format = format_;\n  auto state = getState(load1);\n  if (!state.empty() && config_[\"format-\" + state].isString()) {\n    format = config_[\"format-\" + state].asString();\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n  } else {\n    event_box_.show();\n    auto icons = std::vector<std::string>{state};\n    fmt::dynamic_format_arg_store<fmt::format_context> store;\n    store.push_back(fmt::arg(\"load1\", load1));\n    store.push_back(fmt::arg(\"load5\", load5));\n    store.push_back(fmt::arg(\"load15\", load15));\n    store.push_back(fmt::arg(\"icon1\", getIcon(load1, icons)));\n    store.push_back(fmt::arg(\"icon5\", getIcon(load5, icons)));\n    store.push_back(fmt::arg(\"icon15\", getIcon(load15, icons)));\n    label_.set_markup(fmt::vformat(format, store));\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nstd::tuple<double, double, double> waybar::modules::Load::getLoad() {\n  double load[3];\n  if (getloadavg(load, 3) != -1) {\n    double load1 = std::ceil(load[0] * 100.0) / 100.0;\n    double load5 = std::ceil(load[1] * 100.0) / 100.0;\n    double load15 = std::ceil(load[2] * 100.0) / 100.0;\n    return {load1, load5, load15};\n  }\n  throw std::runtime_error(\"Can't get system load\");\n}\n"
  },
  {
    "path": "src/modules/memory/bsd.cpp",
    "content": "// clang-format off\n#include <sys/types.h>\n#include <sys/sysctl.h>\n// clang-format on\n#include <unistd.h>  // getpagesize\n\n#include \"modules/memory.hpp\"\n\n#if defined(__DragonFly__)\n#include <sys/vmmeter.h>  // struct vmstats\n#elif defined(__NetBSD__)\n#include <uvm/uvm_extern.h>  // struct uvmexp_sysctl\n#elif defined(__OpenBSD__)\n#include <uvm/uvmexp.h>  // struct uvmexp\n#endif\n\nstatic uint64_t get_total_memory() {\n#if defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)\n  uint64_t physmem;\n#else\n  u_long physmem;\n#endif\n  int mib[] = {\n      CTL_HW,\n#if defined(HW_MEMSIZE)\n      HW_MEMSIZE,\n#elif defined(HW_PHYSMEM64)\n      HW_PHYSMEM64,\n#else\n      HW_PHYSMEM,\n#endif\n  };\n  u_int miblen = sizeof(mib) / sizeof(mib[0]);\n  size_t sz = sizeof(physmem);\n  if (sysctl(mib, miblen, &physmem, &sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl hw.physmem failed\");\n  }\n  return physmem;\n}\n\nstatic uint64_t get_free_memory() {\n#if defined(__DragonFly__)\n  struct vmstats vms;\n  size_t sz = sizeof(vms);\n  if (sysctlbyname(\"vm.vmstats\", &vms, &sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl vm.vmstats failed\");\n  }\n  return static_cast<uint64_t>(vms.v_free_count + vms.v_inactive_count + vms.v_cache_count) *\n         getpagesize();\n#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)\n  u_int v_free_count = 0, v_inactive_count = 0, v_cache_count = 0;\n  size_t sz = sizeof(u_int);\n  sysctlbyname(\"vm.stats.vm.v_free_count\", &v_free_count, &sz, NULL, 0);\n  sysctlbyname(\"vm.stats.vm.v_inactive_count\", &v_inactive_count, &sz, NULL, 0);\n  sysctlbyname(\"vm.stats.vm.v_cache_count\", &v_cache_count, &sz, NULL, 0);\n  return static_cast<uint64_t>(v_free_count + v_inactive_count + v_cache_count) * getpagesize();\n#elif defined(__NetBSD__) || defined(__OpenBSD__)\n#ifdef VM_UVMEXP2\n#undef VM_UVMEXP\n#define VM_UVMEXP VM_UVMEXP2\n#define uvmexp uvmexp_sysctl\n#else\n#define filepages vnodepages\n#define execpages vtextpages\n#endif\n  int mib[] = {\n      CTL_VM,\n      VM_UVMEXP,\n  };\n  u_int miblen = sizeof(mib) / sizeof(mib[0]);\n  struct uvmexp uvmexp;\n  size_t sz = sizeof(uvmexp);\n  if (sysctl(mib, miblen, &uvmexp, &sz, NULL, 0)) {\n    throw std::runtime_error(\"sysctl vm.uvmexp failed\");\n  }\n  return static_cast<uint64_t>(uvmexp.free + uvmexp.inactive + uvmexp.filepages +\n                               uvmexp.execpages) *\n         uvmexp.pagesize;\n#endif\n}\n\nvoid waybar::modules::Memory::parseMeminfo() {\n  meminfo_[\"MemTotal\"] = get_total_memory() / 1024;\n  meminfo_[\"MemAvailable\"] = get_free_memory() / 1024;\n}\n"
  },
  {
    "path": "src/modules/memory/common.cpp",
    "content": "#include \"modules/memory.hpp\"\n\nwaybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"memory\", id, \"{}%\", 30) {\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::Memory::update() -> void {\n  parseMeminfo();\n\n  unsigned long memtotal = meminfo_[\"MemTotal\"];\n  unsigned long swaptotal = 0;\n  if (meminfo_.count(\"SwapTotal\")) {\n    swaptotal = meminfo_[\"SwapTotal\"];\n  }\n  unsigned long memfree;\n  unsigned long swapfree = 0;\n  if (meminfo_.count(\"SwapFree\")) {\n    swapfree = meminfo_[\"SwapFree\"];\n  }\n  if (meminfo_.count(\"MemAvailable\")) {\n    // New kernels (3.4+) have an accurate available memory field.\n    memfree = meminfo_[\"MemAvailable\"] + meminfo_[\"zfs_size\"];\n  } else {\n    // Old kernel; give a best-effort approximation of available memory.\n    memfree = meminfo_[\"MemFree\"] + meminfo_[\"Buffers\"] + meminfo_[\"Cached\"] +\n              meminfo_[\"SReclaimable\"] - meminfo_[\"Shmem\"] + meminfo_[\"zfs_size\"];\n  }\n\n  if (memtotal > 0 && memfree >= 0) {\n    float total_ram_gigabytes =\n        0.01 * round(memtotal / 10485.76);  // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB\n    float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);\n    int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;\n    int used_swap_percentage = 0;\n    if (swaptotal) {\n      used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;\n    }\n    float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);\n    float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);\n    float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);\n    float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);\n\n    auto format = format_;\n    auto state = getState(used_ram_percentage);\n    if (!state.empty() && config_[\"format-\" + state].isString()) {\n      format = config_[\"format-\" + state].asString();\n    }\n\n    if (format.empty()) {\n      event_box_.hide();\n    } else {\n      event_box_.show();\n      auto icons = std::vector<std::string>{state};\n      label_.set_markup(fmt::format(\n          fmt::runtime(format), used_ram_percentage,\n          fmt::arg(\"icon\", getIcon(used_ram_percentage, icons)),\n          fmt::arg(\"total\", total_ram_gigabytes), fmt::arg(\"swapTotal\", total_swap_gigabytes),\n          fmt::arg(\"percentage\", used_ram_percentage),\n          fmt::arg(\"swapState\", swaptotal == 0 ? \"Off\" : \"On\"),\n          fmt::arg(\"swapPercentage\", used_swap_percentage), fmt::arg(\"used\", used_ram_gigabytes),\n          fmt::arg(\"swapUsed\", used_swap_gigabytes), fmt::arg(\"avail\", available_ram_gigabytes),\n          fmt::arg(\"swapAvail\", available_swap_gigabytes)));\n    }\n\n    if (tooltipEnabled()) {\n      if (config_[\"tooltip-format\"].isString()) {\n        auto tooltip_format = config_[\"tooltip-format\"].asString();\n        label_.set_tooltip_markup(fmt::format(\n            fmt::runtime(tooltip_format), used_ram_percentage,\n            fmt::arg(\"total\", total_ram_gigabytes), fmt::arg(\"swapTotal\", total_swap_gigabytes),\n            fmt::arg(\"percentage\", used_ram_percentage),\n            fmt::arg(\"swapState\", swaptotal == 0 ? \"Off\" : \"On\"),\n            fmt::arg(\"swapPercentage\", used_swap_percentage), fmt::arg(\"used\", used_ram_gigabytes),\n            fmt::arg(\"swapUsed\", used_swap_gigabytes), fmt::arg(\"avail\", available_ram_gigabytes),\n            fmt::arg(\"swapAvail\", available_swap_gigabytes)));\n      } else {\n        label_.set_tooltip_markup(fmt::format(\"{:.{}f}GiB used\", used_ram_gigabytes, 1));\n      }\n    }\n  } else {\n    event_box_.hide();\n  }\n  // Call parent update\n  ALabel::update();\n}\n"
  },
  {
    "path": "src/modules/memory/linux.cpp",
    "content": "#include \"modules/memory.hpp\"\n\nstatic unsigned zfsArcSize() {\n  std::ifstream zfs_arc_stats{\"/proc/spl/kstat/zfs/arcstats\"};\n\n  if (zfs_arc_stats.is_open()) {\n    std::string name;\n    std::string type;\n    unsigned long data{0};\n\n    std::string line;\n    while (std::getline(zfs_arc_stats, line)) {\n      std::stringstream(line) >> name >> type >> data;\n\n      if (name == \"size\") {\n        return data / 1024;  // convert to kB\n      }\n    }\n  }\n\n  return 0;\n}\n\nvoid waybar::modules::Memory::parseMeminfo() {\n  const std::string data_dir_ = \"/proc/meminfo\";\n  std::ifstream info(data_dir_);\n  if (!info.is_open()) {\n    throw std::runtime_error(\"Can't open \" + data_dir_);\n  }\n  std::string line;\n  while (getline(info, line)) {\n    auto posDelim = line.find(':');\n    if (posDelim == std::string::npos) {\n      continue;\n    }\n\n    std::string name = line.substr(0, posDelim);\n    int64_t value = std::stol(line.substr(posDelim + 1));\n    meminfo_[name] = value;\n  }\n\n  meminfo_[\"zfs_size\"] = zfsArcSize();\n}\n"
  },
  {
    "path": "src/modules/mpd/mpd.cpp",
    "content": "#include \"modules/mpd/mpd.hpp\"\n\n#include <fmt/chrono.h>\n#include <glibmm/ustring.h>\n#include <spdlog/spdlog.h>\n\n#include <system_error>\n#include <util/sanitize_str.hpp>\nusing namespace waybar::util;\n\n#include \"modules/mpd/state.hpp\"\n#if defined(MPD_NOINLINE)\nnamespace waybar::modules {\n#include \"modules/mpd/state.inl.hpp\"\n}  // namespace waybar::modules\n#endif\n\nwaybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"mpd\", id, \"{album} - {artist} - {title}\", 5, false, true),\n      module_name_(id.empty() ? \"mpd\" : \"mpd#\" + id),\n      server_(nullptr),\n      port_(config_[\"port\"].isUInt() ? config[\"port\"].asUInt() : 0),\n      password_(config_[\"password\"].empty() ? \"\" : config_[\"password\"].asString()),\n      timeout_(config_[\"timeout\"].isUInt() ? config_[\"timeout\"].asUInt() * 1'000 : 30'000),\n      connection_(nullptr, &mpd_connection_free),\n      status_(nullptr, &mpd_status_free),\n      song_(nullptr, &mpd_song_free) {\n  if (!config_[\"port\"].isNull() && !config_[\"port\"].isUInt()) {\n    spdlog::warn(\"{}: `port` configuration should be an unsigned int\", module_name_);\n  }\n\n  if (!config_[\"timeout\"].isNull() && !config_[\"timeout\"].isUInt()) {\n    spdlog::warn(\"{}: `timeout` configuration should be an unsigned int\", module_name_);\n  }\n\n  if (!config[\"server\"].isNull()) {\n    if (!config_[\"server\"].isString()) {\n      spdlog::warn(\"{}:`server` configuration should be a string\", module_name_);\n    }\n    server_ = config[\"server\"].asCString();\n  }\n\n  event_box_.add_events(Gdk::BUTTON_PRESS_MASK);\n  event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));\n}\n\nauto waybar::modules::MPD::update() -> void {\n  context_.update();\n\n  // Call parent update\n  ALabel::update();\n}\n\nvoid waybar::modules::MPD::queryMPD() {\n  if (connection_ != nullptr) {\n    spdlog::trace(\"{}: fetching state information\", module_name_);\n    try {\n      fetchState();\n      spdlog::trace(\"{}: fetch complete\", module_name_);\n    } catch (std::exception const& e) {\n      spdlog::error(\"{}: {}\", module_name_, e.what());\n      state_ = MPD_STATE_UNKNOWN;\n    }\n\n    dp.emit();\n  }\n}\n\nstd::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {\n  std::string result =\n      config_[\"unknown-tag\"].isString() ? config_[\"unknown-tag\"].asString() : \"N/A\";\n  const char* tag = mpd_song_get_tag(song_.get(), type, idx);\n\n  // mpd_song_get_tag can return NULL, so make sure it's valid before setting\n  if (tag) result = tag;\n\n  return result;\n}\n\nstd::string waybar::modules::MPD::getFilename() const {\n  std::string path = mpd_song_get_uri(song_.get());\n  size_t position = path.find_last_of(\"/\");\n  if (position == std::string::npos) {\n    return path;\n  } else {\n    return path.substr(position + 1);\n  }\n}\n\nvoid waybar::modules::MPD::setLabel() {\n  if (connection_ == nullptr) {\n    label_.get_style_context()->add_class(\"disconnected\");\n    label_.get_style_context()->remove_class(\"stopped\");\n    label_.get_style_context()->remove_class(\"playing\");\n    label_.get_style_context()->remove_class(\"paused\");\n\n    auto format = config_[\"format-disconnected\"].isString()\n                      ? config_[\"format-disconnected\"].asString()\n                      : \"disconnected\";\n    if (format.empty()) {\n      label_.set_markup(format);\n      label_.show();\n    } else {\n      label_.hide();\n    }\n\n    if (tooltipEnabled()) {\n      std::string tooltip_format;\n      tooltip_format = config_[\"tooltip-format-disconnected\"].isString()\n                           ? config_[\"tooltip-format-disconnected\"].asString()\n                           : \"MPD (disconnected)\";\n      // Nothing to format\n      label_.set_tooltip_markup(tooltip_format);\n    }\n    return;\n  }\n  label_.get_style_context()->remove_class(\"disconnected\");\n\n  auto format = format_;\n  Glib::ustring artist, album_artist, album, title;\n  std::string date, filename, uri;\n  int song_pos = 0, queue_length = 0, volume = 0;\n  std::chrono::seconds elapsedTime, totalTime;\n\n  std::string stateIcon = \"\";\n  bool no_song = song_.get() == nullptr;\n  if (stopped() || no_song) {\n    if (no_song) spdlog::warn(\"Bug in mpd: no current song but state is not stopped.\");\n    format =\n        config_[\"format-stopped\"].isString() ? config_[\"format-stopped\"].asString() : \"stopped\";\n    label_.get_style_context()->add_class(\"stopped\");\n    label_.get_style_context()->remove_class(\"playing\");\n    label_.get_style_context()->remove_class(\"paused\");\n  } else {\n    label_.get_style_context()->remove_class(\"stopped\");\n    if (playing()) {\n      label_.get_style_context()->add_class(\"playing\");\n      label_.get_style_context()->remove_class(\"paused\");\n    } else if (paused()) {\n      format = config_[\"format-paused\"].isString() ? config_[\"format-paused\"].asString()\n                                                   : config_[\"format\"].asString();\n      label_.get_style_context()->add_class(\"paused\");\n      label_.get_style_context()->remove_class(\"playing\");\n    }\n\n    stateIcon = getStateIcon();\n\n    artist = sanitize_string(getTag(MPD_TAG_ARTIST));\n    album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));\n    album = sanitize_string(getTag(MPD_TAG_ALBUM));\n    title = sanitize_string(getTag(MPD_TAG_TITLE));\n    date = sanitize_string(getTag(MPD_TAG_DATE));\n    filename = sanitize_string(getFilename());\n    uri = mpd_song_get_uri(song_.get());\n    song_pos = mpd_status_get_song_pos(status_.get()) + 1;\n    volume = mpd_status_get_volume(status_.get());\n    if (volume < 0) {\n      volume = 0;\n    }\n    queue_length = mpd_status_get_queue_length(status_.get());\n    elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));\n    totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));\n  }\n\n  bool consumeActivated = mpd_status_get_consume(status_.get());\n  std::string consumeIcon = getOptionIcon(\"consume\", consumeActivated);\n  bool randomActivated = mpd_status_get_random(status_.get());\n  std::string randomIcon = getOptionIcon(\"random\", randomActivated);\n  bool repeatActivated = mpd_status_get_repeat(status_.get());\n  std::string repeatIcon = getOptionIcon(\"repeat\", repeatActivated);\n  bool singleActivated = mpd_status_get_single(status_.get());\n  std::string singleIcon = getOptionIcon(\"single\", singleActivated);\n  if (config_[\"artist-len\"].isInt()) artist = artist.substr(0, config_[\"artist-len\"].asInt());\n  if (config_[\"album-artist-len\"].isInt())\n    album_artist = album_artist.substr(0, config_[\"album-artist-len\"].asInt());\n  if (config_[\"album-len\"].isInt()) album = album.substr(0, config_[\"album-len\"].asInt());\n  if (config_[\"title-len\"].isInt()) title = title.substr(0, config_[\"title-len\"].asInt());\n\n  try {\n    auto text = fmt::format(\n        fmt::runtime(format), fmt::arg(\"artist\", artist.raw()),\n        fmt::arg(\"albumArtist\", album_artist.raw()), fmt::arg(\"album\", album.raw()),\n        fmt::arg(\"title\", title.raw()), fmt::arg(\"date\", date), fmt::arg(\"volume\", volume),\n        fmt::arg(\"elapsedTime\", elapsedTime), fmt::arg(\"totalTime\", totalTime),\n        fmt::arg(\"songPosition\", song_pos), fmt::arg(\"queueLength\", queue_length),\n        fmt::arg(\"stateIcon\", stateIcon), fmt::arg(\"consumeIcon\", consumeIcon),\n        fmt::arg(\"randomIcon\", randomIcon), fmt::arg(\"repeatIcon\", repeatIcon),\n        fmt::arg(\"singleIcon\", singleIcon), fmt::arg(\"filename\", filename), fmt::arg(\"uri\", uri));\n    if (text.empty()) {\n      label_.hide();\n    } else {\n      label_.show();\n      label_.set_markup(text);\n    }\n  } catch (fmt::format_error const& e) {\n    spdlog::warn(\"mpd: format error: {}\", e.what());\n  }\n\n  if (tooltipEnabled()) {\n    std::string tooltip_format;\n    tooltip_format = config_[\"tooltip-format\"].isString() ? config_[\"tooltip-format\"].asString()\n                                                          : \"MPD (connected)\";\n    try {\n      auto tooltip_text = fmt::format(\n          fmt::runtime(tooltip_format), fmt::arg(\"artist\", artist.raw()),\n          fmt::arg(\"albumArtist\", album_artist.raw()), fmt::arg(\"album\", album.raw()),\n          fmt::arg(\"title\", title.raw()), fmt::arg(\"date\", date), fmt::arg(\"volume\", volume),\n          fmt::arg(\"elapsedTime\", elapsedTime), fmt::arg(\"totalTime\", totalTime),\n          fmt::arg(\"songPosition\", song_pos), fmt::arg(\"queueLength\", queue_length),\n          fmt::arg(\"stateIcon\", stateIcon), fmt::arg(\"consumeIcon\", consumeIcon),\n          fmt::arg(\"randomIcon\", randomIcon), fmt::arg(\"repeatIcon\", repeatIcon),\n          fmt::arg(\"singleIcon\", singleIcon), fmt::arg(\"filename\", filename), fmt::arg(\"uri\", uri));\n      label_.set_tooltip_markup(tooltip_text);\n    } catch (fmt::format_error const& e) {\n      spdlog::warn(\"mpd: format error (tooltip): {}\", e.what());\n    }\n  }\n}\n\nstd::string waybar::modules::MPD::getStateIcon() const {\n  if (!config_[\"state-icons\"].isObject()) {\n    return \"\";\n  }\n\n  if (connection_ == nullptr) {\n    spdlog::warn(\"{}: Trying to fetch state icon while disconnected\", module_name_);\n    return \"\";\n  }\n\n  if (stopped()) {\n    spdlog::warn(\"{}: Trying to fetch state icon while stopped\", module_name_);\n    return \"\";\n  }\n\n  if (playing()) {\n    return config_[\"state-icons\"][\"playing\"].asString();\n  } else {\n    return config_[\"state-icons\"][\"paused\"].asString();\n  }\n}\n\nstd::string waybar::modules::MPD::getOptionIcon(const std::string& optionName,\n                                                bool activated) const {\n  if (!config_[optionName + \"-icons\"].isObject()) {\n    return \"\";\n  }\n\n  if (connection_ == nullptr) {\n    spdlog::warn(\"{}: Trying to fetch option icon while disconnected\", module_name_);\n    return \"\";\n  }\n\n  if (activated) {\n    return config_[optionName + \"-icons\"][\"on\"].asString();\n  } else {\n    return config_[optionName + \"-icons\"][\"off\"].asString();\n  }\n}\n\nstatic bool isServerUnavailable(const std::error_code& ec) {\n  if (ec.category() == std::system_category()) {\n    switch (ec.value()) {\n      case ECONNREFUSED:\n      case ECONNRESET:\n      case ENETDOWN:\n      case ENETUNREACH:\n      case EHOSTDOWN:\n      case ENOENT:\n        return true;\n    }\n  }\n  return false;\n}\n\nvoid waybar::modules::MPD::tryConnect() {\n  if (connection_ != nullptr) {\n    return;\n  }\n\n  connection_ =\n      detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);\n\n  if (connection_ == nullptr) {\n    spdlog::error(\"{}: Failed to connect to MPD\", module_name_);\n    connection_.reset();\n    return;\n  }\n\n  try {\n    checkErrors(connection_.get());\n    spdlog::debug(\"{}: Connected to MPD\", module_name_);\n\n    if (!password_.empty()) {\n      bool res = mpd_run_password(connection_.get(), password_.c_str());\n      if (!res) {\n        spdlog::error(\"{}: Wrong MPD password\", module_name_);\n        connection_.reset();\n        return;\n      }\n      checkErrors(connection_.get());\n    }\n  } catch (std::system_error& e) {\n    /* Tone down logs if it's likely that the mpd server is not running */\n    auto level = isServerUnavailable(e.code()) ? spdlog::level::debug : spdlog::level::err;\n    spdlog::log(level, \"{}: Failed to connect to MPD: {}\", module_name_, e.what());\n    connection_.reset();\n  } catch (std::runtime_error& e) {\n    spdlog::error(\"{}: Failed to connect to MPD: {}\", module_name_, e.what());\n    connection_.reset();\n  }\n}\n\nvoid waybar::modules::MPD::checkErrors(mpd_connection* conn) {\n  switch (mpd_connection_get_error(conn)) {\n    case MPD_ERROR_SUCCESS:\n      mpd_connection_clear_error(conn);\n      return;\n    case MPD_ERROR_TIMEOUT:\n    case MPD_ERROR_CLOSED:\n      mpd_connection_clear_error(conn);\n      connection_.reset();\n      state_ = MPD_STATE_UNKNOWN;\n      throw std::runtime_error(\"Connection to MPD closed\");\n    case MPD_ERROR_SYSTEM:\n      if (auto ec = mpd_connection_get_system_error(conn); ec != 0) {\n        mpd_connection_clear_error(conn);\n        connection_.reset();\n        throw std::system_error(ec, std::system_category());\n      }\n      G_GNUC_FALLTHROUGH;\n    default:\n      if (conn) {\n        auto error_message = mpd_connection_get_error_message(conn);\n        std::string error(error_message);\n        mpd_connection_clear_error(conn);\n        throw std::runtime_error(error);\n      }\n      throw std::runtime_error(\"Invalid connection\");\n  }\n}\n\nvoid waybar::modules::MPD::fetchState() {\n  if (connection_ == nullptr) {\n    spdlog::error(\"{}: Not connected to MPD\", module_name_);\n    return;\n  }\n\n  auto conn = connection_.get();\n\n  status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free);\n  checkErrors(conn);\n\n  state_ = mpd_status_get_state(status_.get());\n  checkErrors(conn);\n\n  song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free);\n  checkErrors(conn);\n}\n\nbool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {\n  if (e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS || connection_ == nullptr) {\n    return false;\n  }\n\n  if (e->button == 1) {\n    if (state_ == MPD_STATE_PLAY)\n      context_.pause();\n    else\n      context_.play();\n  } else if (e->button == 3) {\n    context_.stop();\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "src/modules/mpd/state.cpp",
    "content": "#include \"modules/mpd/state.hpp\"\n\n#include <fmt/chrono.h>\n#include <spdlog/spdlog.h>\n\n#include \"modules/mpd/mpd.hpp\"\n#if defined(MPD_NOINLINE)\nnamespace waybar::modules {\n#include \"modules/mpd/state.inl.hpp\"\n}  // namespace waybar::modules\n#endif\n\n#if FMT_VERSION >= 90000\n/* Satisfy fmt 9.x deprecation of implicit conversion of enums to int */\nauto format_as(enum mpd_idle val) {\n  return static_cast<std::underlying_type_t<enum mpd_idle>>(val);\n}\n#endif\n\nnamespace waybar::modules::detail {\n\n#define IDLE_RUN_NOIDLE_AND_CMD(...)                                      \\\n  if (idle_connection_.connected()) {                                     \\\n    idle_connection_.disconnect();                                        \\\n    auto conn = ctx_->connection().get();                                 \\\n    if (!mpd_run_noidle(conn)) {                                          \\\n      if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) {          \\\n        spdlog::error(\"mpd: Idle: failed to unregister for IDLE events\"); \\\n        ctx_->checkErrors(conn);                                          \\\n      }                                                                   \\\n    }                                                                     \\\n    __VA_ARGS__;                                                          \\\n  }\n\nvoid Idle::play() {\n  IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));\n\n  ctx_->setState(std::make_unique<Playing>(ctx_));\n}\n\nvoid Idle::pause() {\n  IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));\n\n  ctx_->setState(std::make_unique<Paused>(ctx_));\n}\n\nvoid Idle::stop() {\n  IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));\n\n  ctx_->setState(std::make_unique<Stopped>(ctx_));\n}\n\n#undef IDLE_RUN_NOIDLE_AND_CMD\n\nvoid Idle::update() noexcept {\n  // This is intentionally blank.\n}\n\nvoid Idle::entry() noexcept {\n  auto conn = ctx_->connection().get();\n  assert(conn != nullptr);\n\n  if (!mpd_send_idle_mask(\n          conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {\n    ctx_->checkErrors(conn);\n    spdlog::error(\"mpd: Idle: failed to register for IDLE events\");\n  } else {\n    spdlog::debug(\"mpd: Idle: watching FD\");\n    sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Idle::on_io);\n    idle_connection_ =\n        Glib::signal_io().connect(idle_slot, mpd_connection_get_fd(conn),\n                                  Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);\n  }\n}\n\nvoid Idle::exit() noexcept {\n  if (idle_connection_.connected()) {\n    idle_connection_.disconnect();\n    spdlog::debug(\"mpd: Idle: unwatching FD\");\n  }\n}\n\nbool Idle::on_io(Glib::IOCondition const&) {\n  auto conn = ctx_->connection().get();\n\n  // callback should do this:\n  enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);\n  spdlog::debug(\"mpd: Idle: recv_idle events -> {}\", events);\n\n  mpd_response_finish(conn);\n  try {\n    ctx_->checkErrors(conn);\n  } catch (std::exception const& e) {\n    spdlog::warn(\"mpd: Idle: error: {}\", e.what());\n    ctx_->setState(std::make_unique<Disconnected>(ctx_));\n    return false;\n  }\n\n  ctx_->fetchState();\n  mpd_state state = ctx_->state();\n\n  if (state == MPD_STATE_STOP) {\n    ctx_->emit();\n    ctx_->setState(std::make_unique<Stopped>(ctx_));\n  } else if (state == MPD_STATE_PLAY) {\n    ctx_->emit();\n    ctx_->setState(std::make_unique<Playing>(ctx_));\n  } else if (state == MPD_STATE_PAUSE) {\n    ctx_->emit();\n    ctx_->setState(std::make_unique<Paused>(ctx_));\n  } else {\n    ctx_->emit();\n    // self transition\n    ctx_->setState(std::make_unique<Idle>(ctx_));\n  }\n\n  return false;\n}\n\nvoid Playing::entry() noexcept {\n  sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);\n  timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1);\n  spdlog::debug(\"mpd: Playing: enabled 1 second periodic timer.\");\n}\n\nvoid Playing::exit() noexcept {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n    spdlog::debug(\"mpd: Playing: disabled 1 second periodic timer.\");\n  }\n}\n\nbool Playing::on_timer() {\n  // Attempt to connect with MPD.\n  try {\n    ctx_->tryConnect();\n\n    // Success?\n    if (!ctx_->is_connected()) {\n      ctx_->setState(std::make_unique<Disconnected>(ctx_));\n      return false;\n    }\n\n    ctx_->fetchState();\n\n    if (!ctx_->is_playing()) {\n      if (ctx_->is_paused()) {\n        ctx_->setState(std::make_unique<Paused>(ctx_));\n      } else {\n        ctx_->setState(std::make_unique<Stopped>(ctx_));\n      }\n      return false;\n    }\n\n    ctx_->queryMPD();\n    ctx_->emit();\n  } catch (std::exception const& e) {\n    spdlog::warn(\"mpd: Playing: error: {}\", e.what());\n    ctx_->setState(std::make_unique<Disconnected>(ctx_));\n    return false;\n  }\n\n  return true;\n}\n\nvoid Playing::stop() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_stop(ctx_->connection().get());\n  }\n\n  ctx_->setState(std::make_unique<Stopped>(ctx_));\n}\n\nvoid Playing::pause() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_pause(ctx_->connection().get(), true);\n  }\n\n  ctx_->setState(std::make_unique<Paused>(ctx_));\n}\n\nvoid Playing::update() noexcept { ctx_->do_update(); }\n\nvoid Paused::entry() noexcept {\n  sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Paused::on_timer);\n  timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);\n  spdlog::debug(\"mpd: Paused: enabled 200 ms periodic timer.\");\n}\n\nvoid Paused::exit() noexcept {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n    spdlog::debug(\"mpd: Paused: disabled 200 ms periodic timer.\");\n  }\n}\n\nbool Paused::on_timer() {\n  bool rc = true;\n\n  // Attempt to connect with MPD.\n  try {\n    ctx_->tryConnect();\n\n    // Success?\n    if (!ctx_->is_connected()) {\n      ctx_->setState(std::make_unique<Disconnected>(ctx_));\n      return false;\n    }\n\n    ctx_->fetchState();\n\n    ctx_->emit();\n\n    if (ctx_->is_paused()) {\n      ctx_->setState(std::make_unique<Idle>(ctx_));\n      rc = false;\n    } else if (ctx_->is_playing()) {\n      ctx_->setState(std::make_unique<Playing>(ctx_));\n      rc = false;\n    } else if (ctx_->is_stopped()) {\n      ctx_->setState(std::make_unique<Stopped>(ctx_));\n      rc = false;\n    }\n  } catch (std::exception const& e) {\n    spdlog::warn(\"mpd: Paused: error: {}\", e.what());\n    ctx_->setState(std::make_unique<Disconnected>(ctx_));\n    rc = false;\n  }\n\n  return rc;\n}\n\nvoid Paused::play() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_play(ctx_->connection().get());\n  }\n\n  ctx_->setState(std::make_unique<Playing>(ctx_));\n}\n\nvoid Paused::stop() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_stop(ctx_->connection().get());\n  }\n\n  ctx_->setState(std::make_unique<Stopped>(ctx_));\n}\n\nvoid Paused::update() noexcept { ctx_->do_update(); }\n\nvoid Stopped::entry() noexcept {\n  sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Stopped::on_timer);\n  timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);\n  spdlog::debug(\"mpd: Stopped: enabled 200 ms periodic timer.\");\n}\n\nvoid Stopped::exit() noexcept {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n    spdlog::debug(\"mpd: Stopped: disabled 200 ms periodic timer.\");\n  }\n}\n\nbool Stopped::on_timer() {\n  bool rc = true;\n\n  // Attempt to connect with MPD.\n  try {\n    ctx_->tryConnect();\n\n    // Success?\n    if (!ctx_->is_connected()) {\n      ctx_->setState(std::make_unique<Disconnected>(ctx_));\n      return false;\n    }\n\n    ctx_->fetchState();\n\n    ctx_->emit();\n\n    if (ctx_->is_stopped()) {\n      ctx_->setState(std::make_unique<Idle>(ctx_));\n      rc = false;\n    } else if (ctx_->is_playing()) {\n      ctx_->setState(std::make_unique<Playing>(ctx_));\n      rc = false;\n    } else if (ctx_->is_paused()) {\n      ctx_->setState(std::make_unique<Paused>(ctx_));\n      rc = false;\n    }\n  } catch (std::exception const& e) {\n    spdlog::warn(\"mpd: Stopped: error: {}\", e.what());\n    ctx_->setState(std::make_unique<Disconnected>(ctx_));\n    rc = false;\n  }\n\n  return rc;\n}\n\nvoid Stopped::play() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_play(ctx_->connection().get());\n  }\n\n  ctx_->setState(std::make_unique<Playing>(ctx_));\n}\n\nvoid Stopped::pause() {\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n\n    mpd_run_pause(ctx_->connection().get(), true);\n  }\n\n  ctx_->setState(std::make_unique<Paused>(ctx_));\n}\n\nvoid Stopped::update() noexcept { ctx_->do_update(); }\n\nbool Disconnected::arm_timer(int interval) noexcept {\n  // check if it's necessary to modify the timer\n  if (timer_connection_ && last_interval_ == interval) {\n    return true;\n  }\n  // unregister timer, if present\n  disarm_timer();\n\n  // register timer\n  last_interval_ = interval;\n  sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);\n  timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, interval);\n  spdlog::debug(\"mpd: Disconnected: enabled {}s interval timer.\", interval);\n  return false;\n}\n\nvoid Disconnected::disarm_timer() noexcept {\n  // unregister timer, if present\n  if (timer_connection_.connected()) {\n    timer_connection_.disconnect();\n    spdlog::debug(\"mpd: Disconnected: disabled interval timer.\");\n  }\n}\n\nvoid Disconnected::entry() noexcept {\n  ctx_->emit();\n  arm_timer(1 /* second */);\n}\n\nvoid Disconnected::exit() noexcept { disarm_timer(); }\n\nbool Disconnected::on_timer() {\n  // Attempt to connect with MPD.\n  try {\n    ctx_->tryConnect();\n\n    // Success?\n    if (ctx_->is_connected()) {\n      ctx_->fetchState();\n      ctx_->emit();\n\n      if (ctx_->is_playing()) {\n        ctx_->setState(std::make_unique<Playing>(ctx_));\n      } else if (ctx_->is_paused()) {\n        ctx_->setState(std::make_unique<Paused>(ctx_));\n      } else {\n        ctx_->setState(std::make_unique<Stopped>(ctx_));\n      }\n\n      return false;  // do not rearm timer\n    }\n  } catch (std::exception const& e) {\n    spdlog::warn(\"mpd: Disconnected: error: {}\", e.what());\n  }\n\n  return arm_timer(ctx_->interval());\n}\n\nvoid Disconnected::update() noexcept { ctx_->do_update(); }\n\n}  // namespace waybar::modules::detail\n"
  },
  {
    "path": "src/modules/mpris/mpris.cpp",
    "content": "#include \"modules/mpris/mpris.hpp\"\n\n#include <fmt/core.h>\n\n#include <optional>\n#include <sstream>\n#include <string>\n\n#include \"util/scope_guard.hpp\"\n\nextern \"C\" {\n#include <playerctl/playerctl.h>\n}\n\n#include <glib.h>\n#include <spdlog/spdlog.h>\nnamespace waybar::modules::mpris {\n\nconst std::string DEFAULT_FORMAT = \"{player} ({status}): {dynamic}\";\n\nMpris::Mpris(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"mpris\", id, DEFAULT_FORMAT, 0, false, true),\n      tooltip_(DEFAULT_FORMAT),\n      artist_len_(-1),\n      album_len_(-1),\n      title_len_(-1),\n      dynamic_len_(-1),\n      dynamic_prio_({\"title\", \"artist\", \"album\", \"position\", \"length\"}),\n      dynamic_order_({\"title\", \"artist\", \"album\", \"position\", \"length\"}),\n      dynamic_separator_(\" - \"),\n      truncate_hours_(true),\n      tooltip_len_limits_(false),\n      // this character is used in Gnome so it's fine to use it here\n      ellipsis_(\"\\u2026\"),\n      player_(\"playerctld\"),\n      manager(),\n      player(),\n      last_update_(std::chrono::system_clock::now() - interval_) {\n  if (config_[\"format-playing\"].isString()) {\n    format_playing_ = config_[\"format-playing\"].asString();\n  }\n  if (config_[\"format-paused\"].isString()) {\n    format_paused_ = config_[\"format-paused\"].asString();\n  }\n  if (config_[\"format-stopped\"].isString()) {\n    format_stopped_ = config_[\"format-stopped\"].asString();\n  }\n  if (config_[\"ellipsis\"].isString()) {\n    ellipsis_ = config_[\"ellipsis\"].asString();\n  }\n  if (config_[\"dynamic-separator\"].isString()) {\n    dynamic_separator_ = config_[\"dynamic-separator\"].asString();\n  }\n  if (tooltipEnabled()) {\n    if (config_[\"tooltip-format\"].isString()) {\n      tooltip_ = config_[\"tooltip-format\"].asString();\n    }\n    if (config_[\"tooltip-format-playing\"].isString()) {\n      tooltip_playing_ = config_[\"tooltip-format-playing\"].asString();\n    }\n    if (config_[\"tooltip-format-paused\"].isString()) {\n      tooltip_paused_ = config_[\"tooltip-format-paused\"].asString();\n    }\n    if (config_[\"tooltip-format-stopped\"].isString()) {\n      tooltip_stopped_ = config_[\"tooltip-format-stopped\"].asString();\n    }\n    if (config_[\"enable-tooltip-len-limits\"].isBool()) {\n      tooltip_len_limits_ = config[\"enable-tooltip-len-limits\"].asBool();\n    }\n  }\n\n  if (config[\"artist-len\"].isUInt()) {\n    artist_len_ = config[\"artist-len\"].asUInt();\n  }\n  if (config[\"album-len\"].isUInt()) {\n    album_len_ = config[\"album-len\"].asUInt();\n  }\n  if (config[\"title-len\"].isUInt()) {\n    title_len_ = config[\"title-len\"].asUInt();\n  }\n  if (config[\"dynamic-len\"].isUInt()) {\n    dynamic_len_ = config[\"dynamic-len\"].asUInt();\n  }\n  // \"dynamic-priority\" has been kept for backward compatibility\n  if (config_[\"dynamic-importance-order\"].isArray() || config_[\"dynamic-priority\"].isArray()) {\n    dynamic_prio_.clear();\n    const auto& dynamic_priority = config_[\"dynamic-importance-order\"].isArray()\n                                       ? config_[\"dynamic-importance-order\"]\n                                       : config_[\"dynamic-priority\"];\n    for (const auto& value : dynamic_priority) {\n      if (value.isString()) {\n        dynamic_prio_.push_back(value.asString());\n      }\n    }\n  }\n  if (config_[\"dynamic-order\"].isArray()) {\n    dynamic_order_.clear();\n    for (const auto& item : config_[\"dynamic-order\"]) {\n      if (item.isString()) {\n        dynamic_order_.push_back(item.asString());\n      }\n    }\n  }\n\n  if (config_[\"truncate-hours\"].isBool()) {\n    truncate_hours_ = config[\"truncate-hours\"].asBool();\n  }\n  if (config_[\"player\"].isString()) {\n    player_ = config_[\"player\"].asString();\n  }\n  if (config_[\"ignored-players\"].isArray()) {\n    ignored_players_.reserve(config_[\"ignored-players\"].size());\n    for (const auto& item : config_[\"ignored-players\"]) {\n      if (item.isString()) {\n        ignored_players_.push_back(item.asString());\n      }\n    }\n  }\n\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error) {\n      g_error_free(error);\n    }\n  });\n  manager = playerctl_player_manager_new(&error);\n  if (error) {\n    throw std::runtime_error(fmt::format(\"unable to create MPRIS client: {}\", error->message));\n  }\n\n  g_object_connect(manager, \"signal::name-appeared\", G_CALLBACK(onPlayerNameAppeared), this, NULL);\n  g_object_connect(manager, \"signal::name-vanished\", G_CALLBACK(onPlayerNameVanished), this, NULL);\n\n  if (player_ == \"playerctld\") {\n    // use playerctld proxy\n    PlayerctlPlayerName name = {\n        .instance = (gchar*)player_.c_str(),\n        .source = PLAYERCTL_SOURCE_DBUS_SESSION,\n    };\n    player = playerctl_player_new_from_name(&name, &error);\n\n  } else {\n    GList* players = playerctl_list_players(&error);\n    if (error) {\n      throw std::runtime_error(fmt::format(\"unable to list players: {}\", error->message));\n    }\n\n    for (auto* p = players; p != nullptr; p = p->next) {\n      auto* pn = static_cast<PlayerctlPlayerName*>(p->data);\n      if (strcmp(pn->name, player_.c_str()) == 0) {\n        player = playerctl_player_new_from_name(pn, &error);\n        break;\n      }\n    }\n  }\n\n  if (error) {\n    throw std::runtime_error(\n        fmt::format(\"unable to connect to player {}: {}\", player_, error->message));\n  }\n\n  if (player) {\n    g_object_connect(player, \"signal::play\", G_CALLBACK(onPlayerPlay), this, \"signal::pause\",\n                     G_CALLBACK(onPlayerPause), this, \"signal::stop\", G_CALLBACK(onPlayerStop),\n                     this, \"signal::metadata\", G_CALLBACK(onPlayerMetadata), this, NULL);\n  }\n\n  // allow setting an interval count that triggers periodic refreshes\n  if (interval_.count() > 0) {\n    thread_ = [this] {\n      dp.emit();\n      thread_.sleep_for(interval_);\n    };\n  }\n\n  // trigger initial update\n  dp.emit();\n}\n\nMpris::~Mpris() {\n  if (manager != nullptr) {\n    g_signal_handlers_disconnect_by_data(manager, this);\n  }\n  if (player != nullptr) {\n    g_signal_handlers_disconnect_by_data(player, this);\n  }\n  if (last_active_player_ != nullptr && last_active_player_ != player) {\n    g_object_unref(last_active_player_);\n  }\n  g_clear_object(&manager);\n  g_clear_object(&player);\n}\n\nauto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {\n  if (icons.isObject()) {\n    if (icons[key].isString()) return icons[key].asString();\n    if (icons[\"default\"].isString()) return icons[\"default\"].asString();\n  }\n  return \"\";\n}\n\n// Wide characters count as two, zero-width characters count as zero\n// Modifies str in-place (unless width = std::string::npos)\n// Returns the total width of the string pre-truncating\nsize_t utf8_truncate(std::string& str, size_t width = std::string::npos) {\n  if (str.length() == 0) return 0;\n\n  const gchar* trunc_end = nullptr;\n\n  size_t total_width = 0;\n\n  for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) {\n    gunichar c = g_utf8_get_char_validated(data, end - data);\n    if (c == -1U || c == -2U) {\n      // invalid unicode, treat string as ascii\n      if (width != std::string::npos && str.length() > width) str.resize(width);\n      return str.length();\n    } else if (g_unichar_iswide(c)) {\n      total_width += 2;\n    } else if (!g_unichar_iszerowidth(c) && c != 0xAD) {  // neither zero-width nor soft hyphen\n      total_width += 1;\n    }\n\n    data = g_utf8_find_next_char(data, end);\n    if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))\n      trunc_end = data;\n  }\n\n  if (trunc_end) str.resize(trunc_end - str.data());\n\n  return total_width;\n}\n\nsize_t utf8_width(const std::string& str) { return utf8_truncate(const_cast<std::string&>(str)); }\n\nvoid truncate(std::string& s, const std::string& ellipsis, size_t max_len) {\n  if (max_len == 0) {\n    s.resize(0);\n    return;\n  }\n  size_t len = utf8_truncate(s, max_len);\n  if (len > max_len) {\n    size_t ellipsis_len = utf8_width(ellipsis);\n    if (max_len >= ellipsis_len) {\n      if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len);\n      s += ellipsis;\n    } else {\n      s.resize(0);\n    }\n  }\n}\n\nauto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {\n  auto artist = info.artist.value_or(std::string());\n  if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_);\n  return artist;\n}\n\nauto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {\n  auto album = info.album.value_or(std::string());\n  if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_);\n  return album;\n}\n\nauto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {\n  auto title = info.title.value_or(std::string());\n  if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_);\n  return title;\n}\n\nauto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string {\n  if (info.length.has_value()) {\n    auto length = info.length.value();\n    return (truncated && length.substr(0, 3) == \"00:\") ? length.substr(3) : length;\n  }\n  return {};\n}\n\nauto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string {\n  if (info.position.has_value()) {\n    auto position = info.position.value();\n    return (truncated && position.substr(0, 3) == \"00:\") ? position.substr(3) : position;\n  }\n  return {};\n}\n\nauto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string {\n  auto artist = getArtistStr(info, truncated);\n  auto album = getAlbumStr(info, truncated);\n  auto title = getTitleStr(info, truncated);\n  auto length = getLengthStr(info, truncated && truncate_hours_);\n  // keep position format same as length format\n  auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);\n\n  size_t artistLen = utf8_width(artist);\n  size_t albumLen = utf8_width(album);\n  size_t titleLen = utf8_width(title);\n  size_t lengthLen = length.length();\n  size_t posLen = position.length();\n\n  bool showArtist = (artistLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),\n                                                   \"artist\") != dynamic_order_.end());\n  bool showAlbum = (albumLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),\n                                                 \"album\") != dynamic_order_.end());\n  bool showTitle = (titleLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),\n                                                 \"title\") != dynamic_order_.end());\n  bool showLength = (lengthLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),\n                                                   \"length\") != dynamic_order_.end());\n  bool showPos = (posLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),\n                                             \"position\") != dynamic_order_.end());\n\n  if (truncated && dynamic_len_ >= 0) {\n    // Since the first element doesn't present a separator and we don't know a priori which one\n    // it will be, we add a \"virtual separatorLen\" to the dynamicLen, since we are adding the\n    // separatorLen to all the other lengths.\n    size_t separatorLen = utf8_width(dynamic_separator_);\n    size_t dynamicLen = dynamic_len_ + separatorLen;\n    if (showArtist) artistLen += separatorLen;\n    if (showAlbum) albumLen += separatorLen;\n    if (showTitle) albumLen += separatorLen;\n    if (showLength) lengthLen += separatorLen;\n    if (showPos) posLen += separatorLen;\n\n    size_t totalLen = 0;\n\n    for (const auto& item : dynamic_prio_) {\n      if (item == \"artist\") {\n        if (totalLen + artistLen > dynamicLen) {\n          showArtist = false;\n        } else if (showArtist) {\n          totalLen += artistLen;\n        }\n      } else if (item == \"album\") {\n        if (totalLen + albumLen > dynamicLen) {\n          showAlbum = false;\n        } else if (showAlbum) {\n          totalLen += albumLen;\n        }\n      } else if (item == \"title\") {\n        if (totalLen + titleLen > dynamicLen) {\n          showTitle = false;\n        } else if (showTitle) {\n          totalLen += titleLen;\n        }\n      } else if (item == \"length\") {\n        if (totalLen + lengthLen > dynamicLen) {\n          showLength = false;\n        } else if (showLength) {\n          totalLen += lengthLen;\n          posLen = std::max((size_t)2, posLen) - 2;\n        }\n      } else if (item == \"position\") {\n        if (totalLen + posLen > dynamicLen) {\n          showPos = false;\n        } else if (showPos) {\n          totalLen += posLen;\n          lengthLen = std::max((size_t)2, lengthLen) - 2;\n        }\n      }\n    }\n  }\n\n  std::stringstream dynamic;\n  if (html) {\n    artist = Glib::Markup::escape_text(artist);\n    album = Glib::Markup::escape_text(album);\n    title = Glib::Markup::escape_text(title);\n  }\n\n  bool lengthOrPositionShown = false;\n  bool previousShown = false;\n  std::string previousOrder = \"\";\n\n  for (const std::string& order : dynamic_order_) {\n    if ((order == \"artist\" && showArtist) || (order == \"album\" && showAlbum) ||\n        (order == \"title\" && showTitle)) {\n      if (previousShown && previousOrder != \"length\" && previousOrder != \"position\") {\n        dynamic << dynamic_separator_;\n      }\n\n      if (order == \"artist\") {\n        dynamic << artist;\n      } else if (order == \"album\") {\n        dynamic << album;\n      } else if (order == \"title\") {\n        dynamic << title;\n      }\n\n      previousShown = true;\n    } else if (order == \"length\" || order == \"position\") {\n      if (!lengthOrPositionShown && (showLength || showPos)) {\n        if (html) dynamic << \"<small>\";\n        if (previousShown) dynamic << ' ';\n        dynamic << '[';\n        if (showPos) {\n          dynamic << position;\n          if (showLength) dynamic << '/';\n        }\n        if (showLength) dynamic << length;\n        dynamic << ']';\n        if (!dynamic.str().empty()) dynamic << ' ';\n        if (html) dynamic << \"</small>\";\n        lengthOrPositionShown = true;\n      }\n    }\n    previousOrder = order;\n  }\n  return dynamic.str();\n}\n\nauto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,\n                                 gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: name-appeared callback: {}\", player_name->name);\n\n  if (std::string(player_name->name) != mpris->player_) {\n    return;\n  }\n\n  if (mpris->player != nullptr) {\n    g_signal_handlers_disconnect_by_data(mpris->player, mpris);\n    g_clear_object(&mpris->player);\n  }\n  mpris->player = playerctl_player_new_from_name(player_name, nullptr);\n  g_object_connect(mpris->player, \"signal::play\", G_CALLBACK(onPlayerPlay), mpris, \"signal::pause\",\n                   G_CALLBACK(onPlayerPause), mpris, \"signal::stop\", G_CALLBACK(onPlayerStop),\n                   mpris, \"signal::metadata\", G_CALLBACK(onPlayerMetadata), mpris, NULL);\n\n  mpris->dp.emit();\n}\n\nauto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,\n                                 gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: name-vanished callback: {}\", player_name->name);\n\n  if (mpris->player_ == \"playerctld\") {\n    mpris->dp.emit();\n  } else if (mpris->player_ == player_name->name) {\n    mpris->player = nullptr;\n    mpris->event_box_.set_visible(false);\n    mpris->dp.emit();\n  }\n}\n\nauto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: player-play callback\");\n  // update widget\n  mpris->dp.emit();\n}\n\nauto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: player-pause callback\");\n  // update widget\n  mpris->dp.emit();\n}\n\nauto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: player-stop callback\");\n  // update widget (update() handles visibility)\n  mpris->dp.emit();\n}\n\nauto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void {\n  auto* mpris = static_cast<Mpris*>(data);\n  if (!mpris) return;\n\n  spdlog::debug(\"mpris: player-metadata callback\");\n  // update widget\n  mpris->dp.emit();\n}\n\nauto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {\n  if (!player) {\n    return std::nullopt;\n  }\n\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error) {\n      g_error_free(error);\n    }\n  });\n\n  char* player_status = nullptr;\n  auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;\n\n  // Clean up previous fallback player\n  if (last_active_player_ && last_active_player_ != player) {\n    g_object_unref(last_active_player_);\n    last_active_player_ = nullptr;\n  }\n\n  std::string player_name = player_;\n  if (player_name == \"playerctld\") {\n    GList* players = playerctl_list_players(&error);\n    if (error) {\n      throw std::runtime_error(fmt::format(\"unable to list players: {}\", error->message));\n    }\n    // > get the list of players [..] in order of activity\n    // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249\n    PlayerctlPlayer* first_valid_player = nullptr;\n    std::string first_valid_name;\n    for (auto* p = g_list_first(players); p != nullptr; p = p->next) {\n      auto* pn = static_cast<PlayerctlPlayerName*>(p->data);\n      std::string name = pn->name;\n      if (std::any_of(ignored_players_.begin(), ignored_players_.end(),\n                      [&](const std::string& ignored) { return name == ignored; })) {\n        spdlog::warn(\"mpris[{}]: ignoring player update\", name);\n        continue;\n      }\n      auto* tmp = playerctl_player_new_from_name(pn, &error);\n      if (error || !tmp) continue;\n      if (!first_valid_player) {\n        first_valid_player = tmp;\n        first_valid_name = name;\n      }\n      PlayerctlPlaybackStatus status;\n      g_object_get(tmp, \"playback-status\", &status, NULL);\n      if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) {\n        if (tmp != first_valid_player) g_object_unref(first_valid_player);\n        last_active_player_ = tmp;\n        player_name = name;\n        break;\n      }\n      if (tmp != first_valid_player) g_object_unref(tmp);\n    }\n    if (!last_active_player_) {\n      if (!first_valid_player) return std::nullopt;\n      last_active_player_ = first_valid_player;\n      player_name = first_valid_name;\n    }\n  } else if (std::any_of(ignored_players_.begin(), ignored_players_.end(),\n                         [&](const std::string& pn) { return player_name == pn; })) {\n    spdlog::warn(\"mpris[{}]: ignoring player update\", player_name);\n    return std::nullopt;\n  } else {\n    last_active_player_ = player;\n  }\n\n  g_object_get(last_active_player_, \"status\", &player_status, \"playback-status\",\n               &player_playback_status, NULL);\n\n  if (!player_status) {\n    spdlog::error(\"mpris: failed to get player status\");\n    return std::nullopt;\n  }\n  // make status lowercase\n  player_status[0] = std::tolower(player_status[0]);\n\n  PlayerInfo info = {\n      .name = player_name,\n      .status = player_playback_status,\n      .status_string = player_status,\n      .artist = std::nullopt,\n      .album = std::nullopt,\n      .title = std::nullopt,\n      .length = std::nullopt,\n  };\n\n  if (auto* artist_ = playerctl_player_get_artist(last_active_player_, &error)) {\n    spdlog::debug(\"mpris[{}]: artist = {}\", info.name, artist_);\n    info.artist = artist_;\n    g_free(artist_);\n  }\n  if (error) goto errorexit;\n\n  if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) {\n    spdlog::debug(\"mpris[{}]: album = {}\", info.name, album_);\n    info.album = album_;\n    g_free(album_);\n  }\n  if (error) goto errorexit;\n\n  if (auto* title_ = playerctl_player_get_title(last_active_player_, &error)) {\n    spdlog::debug(\"mpris[{}]: title = {}\", info.name, title_);\n    info.title = title_;\n    g_free(title_);\n  }\n  if (error) goto errorexit;\n\n  if (auto* length_ =\n          playerctl_player_print_metadata_prop(last_active_player_, \"mpris:length\", &error)) {\n    spdlog::debug(\"mpris[{}]: mpris:length = {}\", info.name, length_);\n    auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));\n    auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);\n    auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);\n    auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);\n    info.length = fmt::format(\"{:02}:{:02}:{:02}\", len_h.count(), len_m.count(), len_s.count());\n    g_free(length_);\n  }\n  if (error) goto errorexit;\n\n  {\n    auto position_ = playerctl_player_get_position(last_active_player_, &error);\n    if (error) {\n      // it's fine to have an error here because not all players report a position\n      g_error_free(error);\n      error = nullptr;\n    } else {\n      spdlog::debug(\"mpris[{}]: position = {}\", info.name, position_);\n      auto len = std::chrono::microseconds(position_);\n      auto len_h = std::chrono::duration_cast<std::chrono::hours>(len);\n      auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);\n      auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - len_h - len_m);\n      info.position = fmt::format(\"{:02}:{:02}:{:02}\", len_h.count(), len_m.count(), len_s.count());\n    }\n  }\n\n  return info;\n\nerrorexit:\n  std::string errorMsg = error->message;\n  //  When mpris checks for  active player sessions periodically(5 secs), NoActivePlayer error\n  //  message is\n  // thrown when there are no active sessions. This error message is spamming logs without having\n  // any value addition. Log the error only if the error we recceived is not NoActivePlayer.\n  if (errorMsg.rfind(\"GDBus.Error:com.github.altdesktop.playerctld.NoActivePlayer\") ==\n      std::string::npos) {\n    spdlog::error(\"mpris[{}]: {}\", info.name, error->message);\n  }\n  return std::nullopt;\n}\n\nbool Mpris::handleToggle(GdkEventButton* const& e) {\n  if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) {\n    return false;\n  }\n\n  auto info = getPlayerInfo();\n  if (!info) return false;\n\n  struct ButtonAction {\n    guint button;\n    const char* config_key;\n    std::function<void()> builtin_action;\n  };\n\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error) {\n      g_error_free(error);\n    }\n  });\n\n  // Command pattern: encapsulate each button's action\n  auto* target = last_active_player_ ? last_active_player_ : player;\n  const ButtonAction actions[] = {\n      {1, \"on-click\", [&]() { playerctl_player_play_pause(target, &error); }},\n      {2, \"on-click-middle\", [&]() { playerctl_player_previous(target, &error); }},\n      {3, \"on-click-right\", [&]() { playerctl_player_next(target, &error); }},\n      {8, \"on-click-backward\", [&]() { playerctl_player_previous(target, &error); }},\n      {9, \"on-click-forward\", [&]() { playerctl_player_next(target, &error); }},\n  };\n\n  for (const auto& action : actions) {\n    if (e->button == action.button) {\n      if (config_[action.config_key].isString()) {\n        return ALabel::handleToggle(e);\n      }\n      action.builtin_action();\n      break;\n    }\n  }\n\n  if (error) {\n    spdlog::error(\"mpris[{}]: error running builtin on-click action: {}\", (*info).name,\n                  error->message);\n    return false;\n  }\n  return true;\n}\n\nauto Mpris::update() -> void {\n  const auto now = std::chrono::system_clock::now();\n  if (now - last_update_ < interval_) return;\n  last_update_ = now;\n\n  auto opt = getPlayerInfo();\n  if (!opt) {\n    event_box_.set_visible(false);\n    ALabel::update();\n    return;\n  }\n  auto info = *opt;\n\n  if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) {\n    spdlog::debug(\"mpris[{}]: player stopped, skipping update\", info.name);\n    return;\n  }\n\n  spdlog::debug(\"mpris[{}]: running update\", info.name);\n\n  // set css class for player status\n  if (!lastStatus.empty() && label_.get_style_context()->has_class(lastStatus)) {\n    label_.get_style_context()->remove_class(lastStatus);\n  }\n  if (!label_.get_style_context()->has_class(info.status_string)) {\n    label_.get_style_context()->add_class(info.status_string);\n  }\n  lastStatus = info.status_string;\n\n  // set css class for player name\n  if (!lastPlayer.empty() && label_.get_style_context()->has_class(lastPlayer)) {\n    label_.get_style_context()->remove_class(lastPlayer);\n  }\n  if (!label_.get_style_context()->has_class(info.name)) {\n    label_.get_style_context()->add_class(info.name);\n  }\n  lastPlayer = info.name;\n\n  auto formatstr = format_;\n  auto tooltipstr = tooltip_;\n  switch (info.status) {\n    case PLAYERCTL_PLAYBACK_STATUS_PLAYING:\n      if (!format_playing_.empty()) formatstr = format_playing_;\n      if (!tooltip_playing_.empty()) tooltipstr = tooltip_playing_;\n      break;\n    case PLAYERCTL_PLAYBACK_STATUS_PAUSED:\n      if (!format_paused_.empty()) formatstr = format_paused_;\n      if (!tooltip_paused_.empty()) tooltipstr = tooltip_paused_;\n      break;\n    case PLAYERCTL_PLAYBACK_STATUS_STOPPED:\n      if (!format_stopped_.empty()) formatstr = format_stopped_;\n      if (!tooltip_stopped_.empty()) tooltipstr = tooltip_stopped_;\n      break;\n  }\n\n  std::string length = getLengthStr(info, truncate_hours_);\n  std::string tooltipLength =\n      (tooltip_len_limits_ || length.length() > 5) ? length : getLengthStr(info, false);\n  // keep position format same as length format\n  std::string position = getPositionStr(info, truncate_hours_ && length.length() < 6);\n  std::string tooltipPosition =\n      (tooltip_len_limits_ || position.length() > 5) ? position : getPositionStr(info, false);\n\n  try {\n    auto label_format = fmt::format(\n        fmt::runtime(formatstr),\n        fmt::arg(\"player\", std::string(Glib::Markup::escape_text(info.name))),\n        fmt::arg(\"status\", info.status_string),\n        fmt::arg(\"artist\", std::string(Glib::Markup::escape_text(getArtistStr(info, true)))),\n        fmt::arg(\"title\", std::string(Glib::Markup::escape_text(getTitleStr(info, true)))),\n        fmt::arg(\"album\", std::string(Glib::Markup::escape_text(getAlbumStr(info, true)))),\n        fmt::arg(\"length\", length), fmt::arg(\"position\", position),\n        fmt::arg(\"dynamic\", getDynamicStr(info, true, true)),\n        fmt::arg(\"player_icon\", getIconFromJson(config_[\"player-icons\"], info.name)),\n        fmt::arg(\"status_icon\", getIconFromJson(config_[\"status-icons\"], info.status_string)));\n\n    if (label_format.empty()) {\n      label_.hide();\n    } else {\n      label_.set_markup(label_format);\n      label_.show();\n    }\n  } catch (fmt::format_error const& e) {\n    spdlog::warn(\"mpris: format error: {}\", e.what());\n  }\n\n  if (tooltipEnabled()) {\n    try {\n      auto tooltip_text = fmt::format(\n          fmt::runtime(tooltipstr), fmt::arg(\"player\", info.name),\n          fmt::arg(\"status\", info.status_string),\n          fmt::arg(\"artist\", getArtistStr(info, tooltip_len_limits_)),\n          fmt::arg(\"title\", getTitleStr(info, tooltip_len_limits_)),\n          fmt::arg(\"album\", getAlbumStr(info, tooltip_len_limits_)),\n          fmt::arg(\"length\", tooltipLength), fmt::arg(\"position\", tooltipPosition),\n          fmt::arg(\"dynamic\", getDynamicStr(info, tooltip_len_limits_, false)),\n          fmt::arg(\"player_icon\", getIconFromJson(config_[\"player-icons\"], info.name)),\n          fmt::arg(\"status_icon\", getIconFromJson(config_[\"status-icons\"], info.status_string)));\n\n      label_.set_tooltip_markup(tooltip_text);\n    } catch (fmt::format_error const& e) {\n      spdlog::warn(\"mpris: format error (tooltip): {}\", e.what());\n    }\n  }\n\n  event_box_.set_visible(true);\n  // call parent update\n  ALabel::update();\n}\n\n}  // namespace waybar::modules::mpris\n"
  },
  {
    "path": "src/modules/network.cpp",
    "content": "#include \"modules/network.hpp\"\n\n#include <linux/if.h>\n#include <linux/if_link.h>\n#include <netlink/netlink.h>\n#include <spdlog/spdlog.h>\n#include <sys/eventfd.h>\n\n#include <cassert>\n#include <cstring>\n#include <fstream>\n#include <optional>\n#include <sstream>\n#include <string>\n#include <vector>\n\n#include \"util/format.hpp\"\n#ifdef WANT_RFKILL\n#include \"util/rfkill.hpp\"\n#endif\n\nnamespace {\nusing namespace waybar::util;\nconstexpr const char* DEFAULT_FORMAT = \"{ifname}\";\n}  // namespace\n\nconstexpr const char* NETDEV_FILE =\n    \"/proc/net/dev\";  // std::ifstream does not take std::string_view as param\nstd::optional<std::pair<unsigned long long, unsigned long long>>\nwaybar::modules::Network::readBandwidthUsage() {\n  std::ifstream netdev(NETDEV_FILE);\n  if (!netdev) {\n    spdlog::warn(\"Failed to open netdev file {}\", NETDEV_FILE);\n    return {};\n  }\n\n  std::string line;\n  // skip the headers (first two lines)\n  std::getline(netdev, line);\n  std::getline(netdev, line);\n\n  unsigned long long receivedBytes = 0ull;\n  unsigned long long transmittedBytes = 0ull;\n  while (std::getline(netdev, line)) {\n    std::istringstream iss(line);\n\n    std::string ifacename;\n    iss >> ifacename;  // ifacename contains \"eth0:\"\n    if (ifacename.empty()) continue;\n    ifacename.pop_back();  // remove trailing ':'\n    if (ifacename != ifname_) {\n      continue;\n    }\n\n    // The rest of the line consists of whitespace separated counts divided\n    // into two groups (receive and transmit). Each group has the following\n    // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast\n    //\n    // We only care about the bytes count, so we'll just ignore the 7 other\n    // columns.\n    unsigned long long r = 0ull;\n    unsigned long long t = 0ull;\n    // Read received bytes\n    iss >> r;\n    // Skip all the other columns in the received group\n    for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) {\n      // skip whitespace between columns\n      while (iss.peek() == ' ') {\n        iss.ignore();\n      }\n      // skip the irrelevant column\n      while (iss.peek() != ' ') {\n        iss.ignore();\n      }\n    }\n    // Read transmit bytes\n    iss >> t;\n\n    receivedBytes += r;\n    transmittedBytes += t;\n  }\n\n  return {{receivedBytes, transmittedBytes}};\n}\n\nwaybar::modules::Network::Network(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"network\", id, DEFAULT_FORMAT, 60) {\n  // Start with some \"text\" in the module's label_. update() will then\n  // update it. Since the text should be different, update() will be able\n  // to show or hide the event_box_. This is to work around the case where\n  // the module start with no text, but the event_box_ is shown.\n  label_.set_markup(\"<s></s>\");\n\n  if (config_[\"family\"] == \"ipv6\") {\n    addr_pref_ = IPV6;\n  } else if (config[\"family\"] == \"ipv4_6\") {\n    addr_pref_ = IPV4_6;\n  }\n\n  auto bandwidth = readBandwidthUsage();\n  if (bandwidth.has_value()) {\n    bandwidth_down_total_ = (*bandwidth).first;\n    bandwidth_up_total_ = (*bandwidth).second;\n  } else {\n    bandwidth_down_total_ = 0;\n    bandwidth_up_total_ = 0;\n  }\n\n  if (!config_[\"interface\"].isString()) {\n    // \"interface\" isn't configured, then try to guess the external\n    // interface currently used for internet.\n    want_route_dump_ = true;\n  } else {\n    // Look for an interface that match \"interface\"\n    // and then find the address associated with it.\n    want_link_dump_ = true;\n    want_addr_dump_ = true;\n  }\n\n  createEventSocket();\n  createInfoSocket();\n\n  dp.emit();\n  // Ask for a dump of interfaces and then addresses to populate our\n  // information. First the interface dump, and once done, the callback\n  // will be called again which will ask for addresses dump.\n  askForStateDump();\n  worker();\n}\n\nwaybar::modules::Network::~Network() {\n  if (ev_fd_ > -1) {\n    close(ev_fd_);\n  }\n  if (efd_ > -1) {\n    close(efd_);\n  }\n  if (ev_sock_ != nullptr) {\n    nl_socket_drop_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR);\n    nl_close(ev_sock_);\n    nl_socket_free(ev_sock_);\n  }\n  if (sock_ != nullptr) {\n    nl_close(sock_);\n    nl_socket_free(sock_);\n  }\n}\n\nvoid waybar::modules::Network::createEventSocket() {\n  ev_sock_ = nl_socket_alloc();\n  nl_socket_disable_seq_check(ev_sock_);\n  nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);\n  nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this);\n  auto groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;\n  nl_join_groups(ev_sock_, groups);  // Deprecated\n  if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) {\n    throw std::runtime_error(\"Can't connect network socket\");\n  }\n  if (nl_socket_set_nonblocking(ev_sock_)) {\n    throw std::runtime_error(\"Can't set non-blocking on network socket\");\n  }\n  nl_socket_add_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, 0);\n  if (!config_[\"interface\"].isString()) {\n    nl_socket_add_memberships(ev_sock_, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, 0);\n  }\n\n  efd_ = epoll_create1(EPOLL_CLOEXEC);\n  if (efd_ < 0) {\n    throw std::runtime_error(\"Can't create epoll\");\n  }\n  {\n    ev_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);\n    struct epoll_event event;\n    memset(&event, 0, sizeof(event));\n    event.events = EPOLLIN | EPOLLET;\n    event.data.fd = ev_fd_;\n    if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {\n      throw std::runtime_error(\"Can't add epoll event\");\n    }\n  }\n  {\n    auto fd = nl_socket_get_fd(ev_sock_);\n    struct epoll_event event;\n    memset(&event, 0, sizeof(event));\n    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;\n    event.data.fd = fd;\n    if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {\n      throw std::runtime_error(\"Can't add epoll event\");\n    }\n  }\n}\n\nvoid waybar::modules::Network::createInfoSocket() {\n  sock_ = nl_socket_alloc();\n  if (genl_connect(sock_) != 0) {\n    throw std::runtime_error(\"Can't connect to netlink socket\");\n  }\n  if (nl_socket_modify_cb(sock_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) {\n    throw std::runtime_error(\"Can't set callback\");\n  }\n  nl80211_id_ = genl_ctrl_resolve(sock_, \"nl80211\");\n  if (nl80211_id_ < 0) {\n    spdlog::warn(\"Can't resolve nl80211 interface\");\n  }\n}\n\nvoid waybar::modules::Network::worker() {\n  // update via here not working\n  thread_timer_ = [this] {\n    {\n      std::lock_guard<std::mutex> lock(mutex_);\n      if (ifid_ > 0) {\n        getInfo();\n      }\n      dp.emit();\n    }\n    thread_timer_.sleep_for(interval_);\n  };\n#ifdef WANT_RFKILL\n  rfkill_.on_update.connect([this](auto&) {\n    /* If we are here, it's likely that the network thread already holds the mutex and will be\n     * holding it for a next few seconds.\n     * Let's delegate the update to the timer thread instead of blocking the main thread.\n     */\n    thread_timer_.wake_up();\n  });\n#else\n  spdlog::warn(\"Waybar has been built without rfkill support.\");\n#endif\n  thread_ = [this] {\n    std::array<struct epoll_event, EPOLL_MAX> events{};\n\n    int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);\n    if (ec > 0) {\n      for (auto i = 0; i < ec; i++) {\n        if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) {\n          int rc = 0;\n          // Read as many message as possible, until the socket blocks\n          while (true) {\n            errno = 0;\n            rc = nl_recvmsgs_default(ev_sock_);\n            if (rc == -NLE_AGAIN || errno == EAGAIN) {\n              rc = 0;\n              break;\n            }\n          }\n          if (rc < 0) {\n            spdlog::error(\"nl_recvmsgs_default error: {}\", nl_geterror(-rc));\n            thread_.stop();\n            break;\n          }\n        } else {\n          thread_.stop();\n          break;\n        }\n      }\n    }\n  };\n}\n\nconst std::string waybar::modules::Network::getNetworkState() const {\n  if (ifid_ == -1 || !carrier_) {\n#ifdef WANT_RFKILL\n    bool display_rfkill = true;\n    if (config_[\"rfkill\"].isBool()) {\n      display_rfkill = config_[\"rfkill\"].asBool();\n    }\n    if (rfkill_.getState() && display_rfkill) return \"disabled\";\n#endif\n    return \"disconnected\";\n  }\n  if (ipaddr_.empty() && ipaddr6_.empty()) return \"linked\";\n  if (essid_.empty()) return \"ethernet\";\n  return \"wifi\";\n}\n\nauto waybar::modules::Network::update() -> void {\n  std::lock_guard<std::mutex> lock(mutex_);\n  std::string tooltip_format;\n\n  auto bandwidth = readBandwidthUsage();\n  auto bandwidth_down = 0ull;\n  auto bandwidth_up = 0ull;\n  if (bandwidth.has_value()) {\n    auto down_octets = (*bandwidth).first;\n    auto up_octets = (*bandwidth).second;\n\n    bandwidth_down = down_octets - bandwidth_down_total_;\n    bandwidth_down_total_ = down_octets;\n\n    bandwidth_up = up_octets - bandwidth_up_total_;\n    bandwidth_up_total_ = up_octets;\n  }\n\n  if (!alt_) {\n    auto state = getNetworkState();\n    if (!state_.empty() && label_.get_style_context()->has_class(state_)) {\n      label_.get_style_context()->remove_class(state_);\n    }\n    if (config_[\"format-\" + state].isString()) {\n      default_format_ = config_[\"format-\" + state].asString();\n    } else if (config_[\"format\"].isString()) {\n      default_format_ = config_[\"format\"].asString();\n    } else {\n      default_format_ = DEFAULT_FORMAT;\n    }\n    if (config_[\"tooltip-format-\" + state].isString()) {\n      tooltip_format = config_[\"tooltip-format-\" + state].asString();\n    }\n    if (!label_.get_style_context()->has_class(state)) {\n      label_.get_style_context()->add_class(state);\n    }\n    format_ = default_format_;\n    state_ = state;\n  }\n  getState(signal_strength_);\n\n  std::string final_ipaddr_;\n  if (addr_pref_ == ip_addr_pref::IPV4) {\n    final_ipaddr_ = ipaddr_;\n  } else if (addr_pref_ == ip_addr_pref::IPV6) {\n    final_ipaddr_ = ipaddr6_;\n  } else if (addr_pref_ == ip_addr_pref::IPV4_6) {\n    final_ipaddr_.reserve(ipaddr_.length() + ipaddr6_.length() + 1);\n    final_ipaddr_ = ipaddr_;\n    final_ipaddr_ += '\\n';\n    final_ipaddr_ += ipaddr6_;\n  }\n\n  auto text = fmt::format(\n      fmt::runtime(format_), fmt::arg(\"essid\", essid_), fmt::arg(\"bssid\", bssid_),\n      fmt::arg(\"signaldBm\", signal_strength_dbm_), fmt::arg(\"signalStrength\", signal_strength_),\n      fmt::arg(\"signalStrengthApp\", signal_strength_app_), fmt::arg(\"ifname\", ifname_),\n      fmt::arg(\"netmask\", netmask_), fmt::arg(\"netmask6\", netmask6_),\n      fmt::arg(\"ipaddr\", final_ipaddr_), fmt::arg(\"gwaddr\", gwaddr_), fmt::arg(\"cidr\", cidr_),\n      fmt::arg(\"cidr6\", cidr6_), fmt::arg(\"frequency\", fmt::format(\"{:.1f}\", frequency_)),\n      fmt::arg(\"icon\", getIcon(signal_strength_, state_)),\n      fmt::arg(\"bandwidthDownBits\",\n               pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), \"b/s\")),\n      fmt::arg(\"bandwidthUpBits\",\n               pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), \"b/s\")),\n      fmt::arg(\n          \"bandwidthTotalBits\",\n          pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), \"b/s\")),\n      fmt::arg(\"bandwidthDownOctets\",\n               pow_format(bandwidth_down / (interval_.count() / 1000.0), \"o/s\")),\n      fmt::arg(\"bandwidthUpOctets\", pow_format(bandwidth_up / (interval_.count() / 1000.0), \"o/s\")),\n      fmt::arg(\"bandwidthTotalOctets\",\n               pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), \"o/s\")),\n      fmt::arg(\"bandwidthDownBytes\",\n               pow_format(bandwidth_down / (interval_.count() / 1000.0), \"B/s\")),\n      fmt::arg(\"bandwidthUpBytes\", pow_format(bandwidth_up / (interval_.count() / 1000.0), \"B/s\")),\n      fmt::arg(\"bandwidthTotalBytes\",\n               pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), \"B/s\")));\n  if (text.compare(label_.get_label()) != 0) {\n    label_.set_markup(text);\n    if (text.empty()) {\n      event_box_.hide();\n    } else {\n      event_box_.show();\n    }\n  }\n  if (tooltipEnabled()) {\n    if (tooltip_format.empty() && config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    if (!tooltip_format.empty()) {\n      auto tooltip_text = fmt::format(\n          fmt::runtime(tooltip_format), fmt::arg(\"essid\", essid_), fmt::arg(\"bssid\", bssid_),\n          fmt::arg(\"signaldBm\", signal_strength_dbm_), fmt::arg(\"signalStrength\", signal_strength_),\n          fmt::arg(\"signalStrengthApp\", signal_strength_app_), fmt::arg(\"ifname\", ifname_),\n          fmt::arg(\"netmask\", netmask_), fmt::arg(\"netmask6\", netmask6_),\n          fmt::arg(\"ipaddr\", final_ipaddr_), fmt::arg(\"gwaddr\", gwaddr_), fmt::arg(\"cidr\", cidr_),\n          fmt::arg(\"cidr6\", cidr6_), fmt::arg(\"frequency\", fmt::format(\"{:.1f}\", frequency_)),\n          fmt::arg(\"icon\", getIcon(signal_strength_, state_)),\n          fmt::arg(\"bandwidthDownBits\",\n                   pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), \"b/s\")),\n          fmt::arg(\"bandwidthUpBits\",\n                   pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), \"b/s\")),\n          fmt::arg(\"bandwidthTotalBits\",\n                   pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0),\n                              \"b/s\")),\n          fmt::arg(\"bandwidthDownOctets\",\n                   pow_format(bandwidth_down / (interval_.count() / 1000.0), \"o/s\")),\n          fmt::arg(\"bandwidthUpOctets\",\n                   pow_format(bandwidth_up / (interval_.count() / 1000.0), \"o/s\")),\n          fmt::arg(\n              \"bandwidthTotalOctets\",\n              pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), \"o/s\")),\n          fmt::arg(\"bandwidthDownBytes\",\n                   pow_format(bandwidth_down / (interval_.count() / 1000.0), \"B/s\")),\n          fmt::arg(\"bandwidthUpBytes\",\n                   pow_format(bandwidth_up / (interval_.count() / 1000.0), \"B/s\")),\n          fmt::arg(\n              \"bandwidthTotalBytes\",\n              pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), \"B/s\")));\n      if (label_.get_tooltip_text() != tooltip_text) {\n        label_.set_tooltip_markup(tooltip_text);\n      }\n    } else if (label_.get_tooltip_text() != text) {\n      label_.set_tooltip_markup(text);\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\n// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495\nstatic bool wildcardMatch(const std::string& pattern, const std::string& text) {\n  auto P = int(pattern.size());\n  auto T = int(text.size());\n\n  auto p = 0, fallback_p = -1;\n  auto t = 0, fallback_t = -1;\n\n  while (t < T) {\n    // Wildcard match:\n    if (p < P && pattern[p] == '*') {\n      fallback_p = p++;  // starting point after failures\n      fallback_t = t;    // starting point after failures\n    }\n\n    // Simple match:\n    else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) {\n      p++;\n      t++;\n    }\n\n    // Failure, fall back just after last matched '*':\n    else if (fallback_p >= 0) {\n      p = fallback_p + 1;  // position just after last matched '*\"\n      t = ++fallback_t;    // re-try to match text from here\n    }\n\n    // There were no '*' before, so we fail here:\n    else {\n      return false;\n    }\n  }\n\n  // Consume all '*' at the end of pattern:\n  while (p < P && pattern[p] == '*') p++;\n\n  return p == P;\n}\n\nbool waybar::modules::Network::matchInterface(const std::string& ifname,\n                                              const std::vector<std::string>& altnames,\n                                              std::string& matched) const {\n  if (!config_[\"interface\"].isString()) {\n    return false;\n  }\n\n  auto config_ifname = config_[\"interface\"].asString();\n  if (config_ifname == ifname || wildcardMatch(config_ifname, ifname)) {\n    matched = ifname;\n    return true;\n  }\n\n  for (const auto& altname : altnames) {\n    if (config_ifname == altname || wildcardMatch(config_ifname, altname)) {\n      matched = altname;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid waybar::modules::Network::clearIface() {\n  ifid_ = -1;\n  ifname_.clear();\n  essid_.clear();\n  bssid_.clear();\n  ipaddr_.clear();\n  ipaddr6_.clear();\n  gwaddr_.clear();\n  netmask_.clear();\n  netmask6_.clear();\n  carrier_ = false;\n  is_p2p_ = false;\n  cidr_ = 0;\n  cidr6_ = 0;\n  signal_strength_dbm_ = 0;\n  signal_strength_ = 0;\n  signal_strength_app_.clear();\n  frequency_ = 0.0;\n}\n\nint waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) {\n  auto net = static_cast<waybar::modules::Network*>(data);\n  std::lock_guard<std::mutex> lock(net->mutex_);\n  auto nh = nlmsg_hdr(msg);\n  bool is_del_event = false;\n\n  switch (nh->nlmsg_type) {\n    case RTM_DELLINK:\n      is_del_event = true;\n    case RTM_NEWLINK: {\n      struct ifinfomsg* ifi = static_cast<struct ifinfomsg*>(NLMSG_DATA(nh));\n      struct nlattr* attrs[IFLA_MAX + 1];\n      std::string ifname;\n      std::vector<std::string> altnames;\n      std::optional<bool> carrier;\n\n      if (nlmsg_parse(nh, sizeof(*ifi), attrs, IFLA_MAX, nullptr) < 0) {\n        spdlog::error(\"network: failed to parse netlink attributes\");\n        return NL_SKIP;\n      }\n\n      if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) {\n        return NL_OK;\n      }\n\n      // Check if the interface goes \"down\" and if we want to detect the\n      // external interface.\n      if (net->ifid_ != -1 && !(ifi->ifi_flags & IFF_UP) && !net->config_[\"interface\"].isString()) {\n        // The current interface is now down, all the routes associated with\n        // it have been deleted, so start looking for a new default route.\n        spdlog::debug(\"network: if{} down\", net->ifid_);\n        net->clearIface();\n        net->dp.emit();\n        net->want_route_dump_ = true;\n        net->askForStateDump();\n        return NL_OK;\n      }\n\n      if (attrs[IFLA_IFNAME] != nullptr) {\n        const char* ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]);\n        size_t ifname_len = nla_len(attrs[IFLA_IFNAME]) - 1;  // minus \\0\n        ifname = std::string(ifname_ptr, ifname_len);\n      }\n\n      if (attrs[IFLA_CARRIER] != nullptr) {\n        carrier = nla_get_u8(attrs[IFLA_CARRIER]) == 1;\n      }\n\n      if (attrs[IFLA_PROP_LIST] != nullptr) {\n        struct nlattr* prop;\n        int rem;\n\n        nla_for_each_nested(prop, attrs[IFLA_PROP_LIST], rem) {\n          if (nla_type(prop) == IFLA_ALT_IFNAME) {\n            const char* altname_ptr = nla_get_string(prop);\n            size_t altname_len = nla_len(prop) - 1;  // minus \\0\n            altnames.emplace_back(altname_ptr, altname_len);\n          }\n        }\n      }\n\n      if (!is_del_event && ifi->ifi_index == net->ifid_) {\n        // Update interface information\n        if (net->ifname_.empty() && !ifname.empty()) {\n          net->ifname_ = ifname;\n        }\n        if (carrier.has_value()) {\n          if (net->carrier_ != *carrier) {\n            if (*carrier) {\n              // Ask for WiFi information\n              net->thread_timer_.wake_up();\n            } else {\n              // clear state related to WiFi connection\n              net->essid_.clear();\n              net->bssid_.clear();\n              net->signal_strength_dbm_ = 0;\n              net->signal_strength_ = 0;\n              net->signal_strength_app_.clear();\n              net->frequency_ = 0.0;\n            }\n          }\n          net->carrier_ = carrier.value();\n        }\n      } else if (!is_del_event && net->ifid_ == -1) {\n        // Checking if it's an interface we care about.\n        std::string matched;\n        if (net->matchInterface(ifname, altnames, matched)) {\n          if (ifname == matched) {\n            spdlog::debug(\"network: selecting new interface {}/{}\", ifname, ifi->ifi_index);\n          } else {\n            spdlog::debug(\"network: selecting new interface {}/{} (matched altname {})\", ifname,\n                          ifi->ifi_index, matched);\n          }\n\n          net->ifname_ = ifname;\n          net->ifid_ = ifi->ifi_index;\n          if ((ifi->ifi_flags & IFF_POINTOPOINT) != 0) {\n            net->is_p2p_ = true;\n          }\n          if ((ifi->ifi_flags & IFF_UP) == 0) {\n            // With some network drivers (e.g. mt7921e), the interface may\n            // report having a carrier even though interface is down.\n            carrier = false;\n          }\n          if (carrier.has_value()) {\n            net->carrier_ = carrier.value();\n          }\n          net->thread_timer_.wake_up();\n          /* An address for this new interface should be received via an\n           * RTM_NEWADDR event either because we ask for a dump of both links\n           * and addrs, or because this interface has just been created and\n           * the addr will be sent after the RTM_NEWLINK event.\n           * So we don't need to do anything. */\n        }\n      } else if (is_del_event && net->ifid_ >= 0) {\n        // Our interface has been deleted, start looking/waiting for one we care.\n        spdlog::debug(\"network: interface {}/{} deleted\", net->ifname_, net->ifid_);\n\n        net->clearIface();\n        net->dp.emit();\n      }\n      break;\n    }\n\n    case RTM_DELADDR:\n      is_del_event = true;\n    case RTM_NEWADDR: {\n      struct ifaddrmsg* ifa = static_cast<struct ifaddrmsg*>(NLMSG_DATA(nh));\n      ssize_t attrlen = IFA_PAYLOAD(nh);\n      struct rtattr* ifa_rta = IFA_RTA(ifa);\n\n      if ((int)ifa->ifa_index != net->ifid_) {\n        return NL_OK;\n      }\n\n      // We ignore address mark as scope for the link or host,\n      // which should leave scope global addresses.\n      if (ifa->ifa_scope >= RT_SCOPE_LINK) {\n        return NL_OK;\n      }\n      for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {\n        switch (ifa_rta->rta_type) {\n          case IFA_ADDRESS:\n            if (net->is_p2p_) continue;\n          case IFA_LOCAL:\n            char ipaddr[INET6_ADDRSTRLEN];\n            if (!is_del_event) {\n              bool addr_changed = false;\n              std::string changed_ipaddr;\n              int changed_cidr = 0;\n              if ((net->addr_pref_ == ip_addr_pref::IPV4 ||\n                   net->addr_pref_ == ip_addr_pref::IPV4_6) &&\n                  net->cidr_ == 0 && ifa->ifa_family == AF_INET) {\n                if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=\n                    nullptr) {\n                  net->ipaddr_ = ipaddr;\n                  net->cidr_ = ifa->ifa_prefixlen;\n                  addr_changed = true;\n                  changed_ipaddr = net->ipaddr_;\n                  changed_cidr = net->cidr_;\n                }\n              } else if ((net->addr_pref_ == ip_addr_pref::IPV6 ||\n                          net->addr_pref_ == ip_addr_pref::IPV4_6) &&\n                         net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) {\n                if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) !=\n                    nullptr) {\n                  net->ipaddr6_ = ipaddr;\n                  net->cidr6_ = ifa->ifa_prefixlen;\n                  addr_changed = true;\n                  changed_ipaddr = net->ipaddr6_;\n                  changed_cidr = net->cidr6_;\n                }\n              }\n\n              switch (ifa->ifa_family) {\n                case AF_INET: {\n                  struct in_addr netmask;\n                  netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen));\n                  net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr));\n                }\n                case AF_INET6: {\n                  struct in6_addr netmask6;\n                  for (int i = 0; i < 16; i++) {\n                    int v = (i + 1) * 8 - ifa->ifa_prefixlen;\n                    if (v < 0) v = 0;\n                    if (v > 8) v = 8;\n                    netmask6.s6_addr[i] = ~0 << v;\n                  }\n                  net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr));\n                }\n              }\n              if (addr_changed) {\n                spdlog::debug(\"network: {}, new addr {}/{}\", net->ifname_, changed_ipaddr,\n                              changed_cidr);\n              }\n            } else {\n              net->ipaddr_.clear();\n              net->ipaddr6_.clear();\n              net->cidr_ = 0;\n              net->cidr6_ = 0;\n              net->netmask_.clear();\n              net->netmask6_.clear();\n              spdlog::debug(\"network: {} addr deleted {}/{}\", net->ifname_,\n                            inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)),\n                            ifa->ifa_prefixlen);\n            }\n            net->dp.emit();\n            break;\n        }\n      }\n      break;\n    }\n\n    case RTM_DELROUTE:\n      is_del_event = true;\n    case RTM_NEWROUTE: {\n      // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698\n      // to find the interface used to reach the outside world\n\n      struct rtmsg* rtm = static_cast<struct rtmsg*>(NLMSG_DATA(nh));\n      int family = rtm->rtm_family;\n      ssize_t attrlen = RTM_PAYLOAD(nh);\n      struct rtattr* attr = RTM_RTA(rtm);\n      char gateway_addr[INET6_ADDRSTRLEN];\n      bool has_gateway = false;\n      bool has_destination = false;\n      int temp_idx = -1;\n      uint32_t priority = 0;\n\n      /* Find the message(s) concerting the main routing table, each message\n       * corresponds to a single routing table entry.\n       */\n      if (rtm->rtm_table != RT_TABLE_MAIN) {\n        return NL_OK;\n      }\n\n      /* Parse all the attributes for a single routing table entry. */\n      for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {\n        /* Determine if this routing table entry corresponds to the default\n         * route by seeing if it has a gateway, and if a destination addr is\n         * set, that it is all 0s.\n         */\n        switch (attr->rta_type) {\n          case RTA_GATEWAY:\n            /* The gateway of the route.\n             *\n             * If someone ever needs to figure out the gateway address as well,\n             * it's here as the attribute payload.\n             */\n            inet_ntop(family, RTA_DATA(attr), gateway_addr, sizeof(gateway_addr));\n            has_gateway = true;\n            break;\n          case RTA_DST: {\n            /* The destination address.\n             * Should be either missing, or maybe all 0s.  Accept both.\n             */\n            const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16;\n            unsigned char c = 0;\n            size_t dstlen = RTA_PAYLOAD(attr);\n            if (dstlen != nr_zeroes) {\n              break;\n            }\n            for (uint32_t i = 0; i < dstlen; i += 1) {\n              c |= *((unsigned char*)RTA_DATA(attr) + i);\n            }\n            has_destination = (c == 0);\n            break;\n          }\n          case RTA_OIF:\n            /* The output interface index. */\n            temp_idx = *static_cast<int*>(RTA_DATA(attr));\n            break;\n          case RTA_PRIORITY:\n            priority = *(uint32_t*)RTA_DATA(attr);\n            break;\n          default:\n            break;\n        }\n      }\n\n      // Check if we have a default route.\n      if (has_gateway && !has_destination && temp_idx != -1) {\n        // Check if this is the first default route we see, or if this new\n        // route have a higher priority.\n        /** Module doesn`t update state, because RTA_GATEWAY call before enable new router and set\n        higher priority. Disable router -> RTA_GATEWAY -> up new router -> set higher priority added\n        checking route id\n        **/\n        if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {\n          // Clear if's state for the case were there is a higher priority\n          // route on a different interface.\n          net->clearIface();\n          net->ifid_ = temp_idx;\n          net->route_priority = priority;\n          net->gwaddr_ = gateway_addr;\n          spdlog::debug(\"network: new default route via {} on if{} metric {}\", gateway_addr,\n                        temp_idx, priority);\n\n          /* Ask ifname associated with temp_idx as well as carrier status */\n          struct ifinfomsg ifinfo_hdr = {\n              .ifi_family = AF_UNSPEC,\n              .ifi_index = temp_idx,\n          };\n          int err;\n          err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST, &ifinfo_hdr,\n                               sizeof(ifinfo_hdr));\n          if (err < 0) {\n            spdlog::error(\"network: failed to ask link info: {}\", err);\n            /* Ask for a dump of all links instead */\n            net->want_link_dump_ = true;\n          }\n\n          /* Also ask for the address. Asking for a addresses of a specific\n           * interface doesn't seems to work so ask for a dump of all\n           * addresses. */\n          net->want_addr_dump_ = true;\n          net->askForStateDump();\n          net->thread_timer_.wake_up();\n        } else if (is_del_event && temp_idx == net->ifid_ && net->route_priority == priority) {\n          spdlog::debug(\"network: default route deleted {}/if{} metric {}\", net->ifname_, temp_idx,\n                        priority);\n\n          net->clearIface();\n          net->dp.emit();\n          /* Ask for a dump of all routes in case another one is already\n           * setup. If there's none, there'll be an event with new one\n           * later. */\n          net->want_route_dump_ = true;\n          net->askForStateDump();\n        }\n      }\n      break;\n    }\n  }\n\n  return NL_OK;\n}\n\nvoid waybar::modules::Network::askForStateDump(void) {\n  /* We need to wait until the current dump is done before sending new\n   * messages. handleEventsDone() is called when a dump is done. */\n  if (dump_in_progress_) return;\n\n  struct rtgenmsg rt_hdr = {\n      .rtgen_family = AF_UNSPEC,\n  };\n\n  if (want_route_dump_) {\n    nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));\n    want_route_dump_ = false;\n    dump_in_progress_ = true;\n\n  } else if (want_link_dump_) {\n    nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));\n    want_link_dump_ = false;\n    dump_in_progress_ = true;\n\n  } else if (want_addr_dump_) {\n    nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr));\n    want_addr_dump_ = false;\n    dump_in_progress_ = true;\n  }\n}\n\nint waybar::modules::Network::handleEventsDone(struct nl_msg* msg, void* data) {\n  auto net = static_cast<waybar::modules::Network*>(data);\n  net->dump_in_progress_ = false;\n  net->askForStateDump();\n  return NL_OK;\n}\n\nint waybar::modules::Network::handleScan(struct nl_msg* msg, void* data) {\n  auto net = static_cast<waybar::modules::Network*>(data);\n  auto gnlh = static_cast<genlmsghdr*>(nlmsg_data(nlmsg_hdr(msg)));\n  struct nlattr* tb[NL80211_ATTR_MAX + 1];\n  struct nlattr* bss[NL80211_BSS_MAX + 1];\n  struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};\n  bss_policy[NL80211_BSS_TSF].type = NLA_U64;\n  bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;\n  bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC;\n  bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16;\n  bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16;\n  bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC;\n  bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32;\n  bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;\n  bss_policy[NL80211_BSS_STATUS].type = NLA_U32;\n\n  if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0),\n                nullptr) < 0) {\n    return NL_SKIP;\n  }\n  if (tb[NL80211_ATTR_BSS] == nullptr) {\n    return NL_SKIP;\n  }\n  if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {\n    return NL_SKIP;\n  }\n  if (!net->associatedOrJoined(bss)) {\n    return NL_SKIP;\n  }\n  net->parseEssid(bss);\n  net->parseSignal(bss);\n  net->parseFreq(bss);\n  net->parseBssid(bss);\n  return NL_OK;\n}\n\nvoid waybar::modules::Network::parseEssid(struct nlattr** bss) {\n  if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {\n    auto ies = static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));\n    auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);\n    const auto hdr_len = 2;\n    while (ies_len > hdr_len && ies[0] != 0) {\n      ies_len -= ies[1] + hdr_len;\n      ies += ies[1] + hdr_len;\n    }\n    if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {\n      auto essid_begin = ies + hdr_len;\n      auto essid_end = essid_begin + ies[1];\n      std::string essid_raw;\n      std::copy(essid_begin, essid_end, std::back_inserter(essid_raw));\n      essid_ = Glib::Markup::escape_text(essid_raw);\n    }\n  }\n}\n\nvoid waybar::modules::Network::parseSignal(struct nlattr** bss) {\n  if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {\n    // signalstrength in dBm from mBm\n    signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;\n    // WiFi-hardware usually operates in the range -90 to -30dBm.\n\n    // If a signal is too strong, it can overwhelm receiving circuity that is designed\n    // to pick up and process a certain signal level. The following percentage is scaled to\n    // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).\n    const int hardwareOptimum = -45;\n    const int hardwareMin = -90;\n    const int strength =\n        100 -\n        ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) *\n         100);\n    signal_strength_ = std::clamp(strength, 0, 100);\n\n    if (signal_strength_dbm_ >= -50) {\n      signal_strength_app_ = \"Great Connectivity\";\n    } else if (signal_strength_dbm_ >= -60) {\n      signal_strength_app_ = \"Good Connectivity\";\n    } else if (signal_strength_dbm_ >= -67) {\n      signal_strength_app_ = \"Streaming\";\n    } else if (signal_strength_dbm_ >= -70) {\n      signal_strength_app_ = \"Web Surfing\";\n    } else if (signal_strength_dbm_ >= -80) {\n      signal_strength_app_ = \"Basic Connectivity\";\n    } else {\n      signal_strength_app_ = \"Poor Connectivity\";\n    }\n  }\n  if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {\n    signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);\n  }\n}\n\nvoid waybar::modules::Network::parseFreq(struct nlattr** bss) {\n  if (bss[NL80211_BSS_FREQUENCY] != nullptr) {\n    // in GHz\n    frequency_ = (double)nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000;\n  }\n}\n\nvoid waybar::modules::Network::parseBssid(struct nlattr** bss) {\n  if (bss[NL80211_BSS_BSSID] != nullptr) {\n    auto bssid = static_cast<uint8_t*>(nla_data(bss[NL80211_BSS_BSSID]));\n    auto bssid_len = nla_len(bss[NL80211_BSS_BSSID]);\n    if (bssid_len == 6) {\n      bssid_ = fmt::format(\"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}\", bssid[0], bssid[1], bssid[2], bssid[3],\n                           bssid[4], bssid[5]);\n    }\n  }\n}\n\nbool waybar::modules::Network::associatedOrJoined(struct nlattr** bss) {\n  if (bss[NL80211_BSS_STATUS] == nullptr) {\n    return false;\n  }\n  auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);\n  switch (status) {\n    case NL80211_BSS_STATUS_ASSOCIATED:\n    case NL80211_BSS_STATUS_IBSS_JOINED:\n    case NL80211_BSS_STATUS_AUTHENTICATED:\n      return true;\n    default:\n      return false;\n  }\n}\n\nauto waybar::modules::Network::getInfo() -> void {\n  struct nl_msg* nl_msg = nlmsg_alloc();\n  if (nl_msg == nullptr) {\n    return;\n  }\n  if (genlmsg_put(nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP,\n                  NL80211_CMD_GET_SCAN, 0) == nullptr ||\n      nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) {\n    nlmsg_free(nl_msg);\n    return;\n  }\n  nl_send_sync(sock_, nl_msg);\n}\n"
  },
  {
    "path": "src/modules/niri/backend.cpp",
    "content": "#include \"modules/niri/backend.hpp\"\n\n#include <netdb.h>\n#include <netinet/in.h>\n#include <spdlog/spdlog.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <unistd.h>\n\n#include <iostream>\n#include <string>\n#include <thread>\n\n#include \"giomm/datainputstream.h\"\n#include \"giomm/dataoutputstream.h\"\n#include \"giomm/unixinputstream.h\"\n#include \"giomm/unixoutputstream.h\"\n#include \"util/scoped_fd.hpp\"\n\nnamespace waybar::modules::niri {\n\nIPC::IPC() { startIPC(); }\n\nint IPC::connectToSocket() {\n  const char* socket_path = getenv(\"NIRI_SOCKET\");\n\n  if (socket_path == nullptr) {\n    throw std::runtime_error(\"Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)\");\n  }\n\n  struct sockaddr_un addr;\n  util::ScopedFd socketfd(socket(AF_UNIX, SOCK_STREAM, 0));\n\n  if (socketfd == -1) {\n    throw std::runtime_error(\"socketfd failed\");\n  }\n\n  addr.sun_family = AF_UNIX;\n\n  strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);\n\n  addr.sun_path[sizeof(addr.sun_path) - 1] = 0;\n\n  int l = sizeof(struct sockaddr_un);\n\n  if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {\n    throw std::runtime_error(\"unable to connect\");\n  }\n\n  return socketfd.release();\n}\n\nvoid IPC::startIPC() {\n  // will start IPC and relay events to parseIPC\n\n  int socketfd = connectToSocket();\n\n  std::thread([this, socketfd]() {\n    spdlog::info(\"Niri IPC starting\");\n\n    auto unix_istream = Gio::UnixInputStream::create(socketfd, true);\n    auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);\n    auto istream = Gio::DataInputStream::create(unix_istream);\n    auto ostream = Gio::DataOutputStream::create(unix_ostream);\n\n    if (!ostream->put_string(\"\\\"EventStream\\\"\\n\") || !ostream->flush()) {\n      spdlog::error(\"Niri IPC: failed to start event stream\");\n      return;\n    }\n\n    std::string line;\n    if (!istream->read_line(line) || line != R\"({\"Ok\":\"Handled\"})\") {\n      spdlog::error(\"Niri IPC: failed to start event stream\");\n      return;\n    }\n\n    while (istream->read_line(line)) {\n      spdlog::debug(\"Niri IPC: received {}\", line);\n\n      try {\n        parseIPC(line);\n      } catch (std::exception& e) {\n        spdlog::warn(\"Failed to parse IPC message: {}, reason: {}\", line, e.what());\n      } catch (...) {\n        throw;\n      }\n\n      std::this_thread::sleep_for(std::chrono::milliseconds(1));\n    }\n  }).detach();\n}\n\nvoid IPC::parseIPC(const std::string& line) {\n  const auto ev = parser_.parse(line);\n  const auto members = ev.getMemberNames();\n  if (members.size() != 1) throw std::runtime_error(\"Event must have a single member\");\n\n  {\n    auto lock = lockData();\n\n    if (const auto& payload = ev[\"WorkspacesChanged\"]) {\n      workspaces_.clear();\n      const auto& values = payload[\"workspaces\"];\n      std::copy(values.begin(), values.end(), std::back_inserter(workspaces_));\n\n      std::sort(workspaces_.begin(), workspaces_.end(), [](const auto& a, const auto& b) {\n        const auto& aOutput = a[\"output\"].asString();\n        const auto& bOutput = b[\"output\"].asString();\n        const auto aIdx = a[\"idx\"].asUInt();\n        const auto bIdx = b[\"idx\"].asUInt();\n        if (aOutput == bOutput) return aIdx < bIdx;\n        return aOutput < bOutput;\n      });\n    } else if (const auto& payload = ev[\"WorkspaceActivated\"]) {\n      const auto id = payload[\"id\"].asUInt64();\n      const auto focused = payload[\"focused\"].asBool();\n      auto it = std::find_if(workspaces_.begin(), workspaces_.end(),\n                             [id](const auto& ws) { return ws[\"id\"].asUInt64() == id; });\n      if (it != workspaces_.end()) {\n        const auto& ws = *it;\n        const auto& output = ws[\"output\"].asString();\n        for (auto& ws : workspaces_) {\n          const auto got_activated = (ws[\"id\"].asUInt64() == id);\n          if (ws[\"output\"] == output) ws[\"is_active\"] = got_activated;\n\n          if (focused) ws[\"is_focused\"] = got_activated;\n        }\n      } else {\n        spdlog::error(\"Activated unknown workspace\");\n      }\n    } else if (const auto& payload = ev[\"WorkspaceActiveWindowChanged\"]) {\n      const auto workspaceId = payload[\"workspace_id\"].asUInt64();\n      auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto& ws) {\n        return ws[\"id\"].asUInt64() == workspaceId;\n      });\n      if (it != workspaces_.end()) {\n        auto& ws = *it;\n        ws[\"active_window_id\"] = payload[\"active_window_id\"];\n      } else {\n        spdlog::error(\"Active window changed on unknown workspace\");\n      }\n    } else if (const auto& payload = ev[\"WorkspaceUrgencyChanged\"]) {\n      const auto id = payload[\"id\"].asUInt64();\n      const auto urgent = payload[\"urgent\"].asBool();\n      auto it = std::find_if(workspaces_.begin(), workspaces_.end(),\n                             [id](const auto& ws) { return ws[\"id\"].asUInt64() == id; });\n      if (it != workspaces_.end()) {\n        auto& ws = *it;\n        ws[\"is_urgent\"] = urgent;\n      } else {\n        spdlog::error(\"Urgency changed for unknown workspace\");\n      }\n    } else if (const auto& payload = ev[\"KeyboardLayoutsChanged\"]) {\n      const auto& layouts = payload[\"keyboard_layouts\"];\n      const auto& names = layouts[\"names\"];\n      keyboardLayoutCurrent_ = layouts[\"current_idx\"].asUInt();\n\n      keyboardLayoutNames_.clear();\n      for (const auto& fullName : names) keyboardLayoutNames_.push_back(fullName.asString());\n    } else if (const auto& payload = ev[\"KeyboardLayoutSwitched\"]) {\n      keyboardLayoutCurrent_ = payload[\"idx\"].asUInt();\n    } else if (const auto& payload = ev[\"WindowsChanged\"]) {\n      windows_.clear();\n      const auto& values = payload[\"windows\"];\n      std::copy(values.begin(), values.end(), std::back_inserter(windows_));\n    } else if (const auto& payload = ev[\"WindowOpenedOrChanged\"]) {\n      const auto& window = payload[\"window\"];\n      const auto id = window[\"id\"].asUInt64();\n      auto it = std::find_if(windows_.begin(), windows_.end(),\n                             [id](const auto& win) { return win[\"id\"].asUInt64() == id; });\n      if (it == windows_.end()) {\n        windows_.push_back(window);\n\n        if (window[\"is_focused\"].asBool()) {\n          for (auto& win : windows_) {\n            win[\"is_focused\"] = win[\"id\"].asUInt64() == id;\n          }\n        }\n      } else {\n        *it = window;\n      }\n    } else if (const auto& payload = ev[\"WindowClosed\"]) {\n      const auto id = payload[\"id\"].asUInt64();\n      auto it = std::find_if(windows_.begin(), windows_.end(),\n                             [id](const auto& win) { return win[\"id\"].asUInt64() == id; });\n      if (it != windows_.end()) {\n        windows_.erase(it);\n      } else {\n        spdlog::error(\"Unknown window closed\");\n      }\n    } else if (const auto& payload = ev[\"WindowFocusChanged\"]) {\n      const auto focused = !payload[\"id\"].isNull();\n      const auto id = payload[\"id\"].asUInt64();\n      for (auto& win : windows_) {\n        win[\"is_focused\"] = focused && win[\"id\"].asUInt64() == id;\n      }\n    }\n  }\n\n  std::unique_lock lock(callbackMutex_);\n\n  for (auto& [eventname, handler] : callbacks_) {\n    if (eventname == members[0]) {\n      handler->onEvent(ev);\n    }\n  }\n}\n\nvoid IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {\n  if (ev_handler == nullptr) {\n    return;\n  }\n\n  std::unique_lock lock(callbackMutex_);\n  callbacks_.emplace_back(ev, ev_handler);\n}\n\nvoid IPC::unregisterForIPC(EventHandler* ev_handler) {\n  if (ev_handler == nullptr) {\n    return;\n  }\n\n  std::unique_lock lock(callbackMutex_);\n\n  for (auto it = callbacks_.begin(); it != callbacks_.end();) {\n    auto& [eventname, handler] = *it;\n    if (handler == ev_handler) {\n      it = callbacks_.erase(it);\n    } else {\n      ++it;\n    }\n  }\n}\n\nJson::Value IPC::send(const Json::Value& request) {\n  util::ScopedFd socketfd(connectToSocket());\n\n  auto unix_istream = Gio::UnixInputStream::create(socketfd, true);\n  auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false);\n  auto istream = Gio::DataInputStream::create(unix_istream);\n  auto ostream = Gio::DataOutputStream::create(unix_ostream);\n\n  // Niri needs the request on a single line.\n  Json::StreamWriterBuilder builder;\n  builder[\"indentation\"] = \"\";\n  std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());\n  std::ostringstream oss;\n  writer->write(request, &oss);\n  oss << '\\n';\n\n  if (!ostream->put_string(oss.str()) || !ostream->flush())\n    throw std::runtime_error(\"error writing to niri socket\");\n\n  std::string line;\n  if (!istream->read_line(line)) throw std::runtime_error(\"error reading from niri socket\");\n\n  std::istringstream iss(std::move(line));\n  Json::Value response;\n  iss >> response;\n  return response;\n}\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "src/modules/niri/language.cpp",
    "content": "#include \"modules/niri/language.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <xkbcommon/xkbcommon.h>\n#include <xkbcommon/xkbregistry.h>\n\n#include \"util/string.hpp\"\n\nnamespace waybar::modules::niri {\n\nLanguage::Language(const std::string& id, const Bar& bar, const Json::Value& config)\n    : ALabel(config, \"language\", id, \"{}\", 0, false), bar_(bar) {\n  label_.hide();\n\n  if (!gIPC) gIPC = std::make_unique<IPC>();\n\n  gIPC->registerForIPC(\"KeyboardLayoutsChanged\", this);\n  gIPC->registerForIPC(\"KeyboardLayoutSwitched\", this);\n\n  updateFromIPC();\n  dp.emit();\n}\n\nLanguage::~Language() {\n  gIPC->unregisterForIPC(this);\n  // wait for possible event handler to finish\n  std::lock_guard<std::mutex> lock(mutex_);\n}\n\nvoid Language::updateFromIPC() {\n  std::lock_guard<std::mutex> lock(mutex_);\n  auto ipcLock = gIPC->lockData();\n\n  layouts_.clear();\n  layouts_.reserve(gIPC->keyboardLayoutNames().size());\n  for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName));\n\n  current_idx_ = gIPC->keyboardLayoutCurrent();\n}\n\n/**\n *  Language::doUpdate - update workspaces in UI thread.\n *\n * Note: some member fields are modified by both UI thread and event listener thread, use mutex_ to\n *       protect these member fields, and lock should released before calling ALabel::update().\n */\nvoid Language::doUpdate() {\n  std::lock_guard<std::mutex> lock(mutex_);\n\n  if (layouts_.size() <= current_idx_) {\n    spdlog::error(\"niri language layout index out of bounds\");\n    label_.hide();\n    return;\n  }\n  const auto& layout = layouts_[current_idx_];\n\n  spdlog::debug(\"niri language update with full name {}\", layout.full_name);\n  spdlog::debug(\"niri language update with short name {}\", layout.short_name);\n  spdlog::debug(\"niri language update with short description {}\", layout.short_description);\n  spdlog::debug(\"niri language update with variant {}\", layout.variant);\n\n  if (!last_short_name_.empty()) {\n    label_.get_style_context()->remove_class(last_short_name_);\n  }\n  if (!layout.short_name.empty()) {\n    label_.get_style_context()->add_class(layout.short_name);\n    last_short_name_ = layout.short_name;\n  } else {\n    last_short_name_.clear();\n  }\n\n  std::string layoutName = std::string{};\n  if (config_.isMember(\"format-\" + layout.short_description + \"-\" + layout.variant)) {\n    const auto propName = \"format-\" + layout.short_description + \"-\" + layout.variant;\n    layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());\n  } else if (config_.isMember(\"format-\" + layout.short_description)) {\n    const auto propName = \"format-\" + layout.short_description;\n    layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());\n  } else {\n    layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg(\"long\", layout.full_name),\n                                  fmt::arg(\"short\", layout.short_name),\n                                  fmt::arg(\"shortDescription\", layout.short_description),\n                                  fmt::arg(\"variant\", layout.variant)));\n  }\n\n  spdlog::debug(\"niri language formatted layout name {}\", layoutName);\n\n  if (!format_.empty()) {\n    label_.show();\n    label_.set_markup(layoutName);\n  } else {\n    label_.hide();\n  }\n}\n\nvoid Language::update() {\n  doUpdate();\n  ALabel::update();\n}\n\nvoid Language::onEvent(const Json::Value& ev) {\n  if (ev[\"KeyboardLayoutsChanged\"]) {\n    updateFromIPC();\n  } else if (ev[\"KeyboardLayoutSwitched\"]) {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto ipcLock = gIPC->lockData();\n    current_idx_ = gIPC->keyboardLayoutCurrent();\n  }\n\n  dp.emit();\n}\n\nLanguage::Layout Language::getLayout(const std::string& fullName) {\n  auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);\n  rxkb_context_parse_default_ruleset(context);\n\n  rxkb_layout* layout = rxkb_layout_first(context);\n  while (layout != nullptr) {\n    std::string nameOfLayout = rxkb_layout_get_description(layout);\n\n    if (nameOfLayout != fullName) {\n      layout = rxkb_layout_next(layout);\n      continue;\n    }\n\n    auto name = std::string(rxkb_layout_get_name(layout));\n    const auto* variantPtr = rxkb_layout_get_variant(layout);\n    std::string variant = variantPtr == nullptr ? \"\" : std::string(variantPtr);\n\n    const auto* descriptionPtr = rxkb_layout_get_brief(layout);\n    std::string description = descriptionPtr == nullptr ? \"\" : std::string(descriptionPtr);\n\n    Layout info = Layout{nameOfLayout, name, variant, description};\n\n    rxkb_context_unref(context);\n\n    return info;\n  }\n\n  rxkb_context_unref(context);\n\n  spdlog::debug(\"niri language didn't find matching layout for {}\", fullName);\n\n  return Layout{\"\", \"\", \"\", \"\"};\n}\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "src/modules/niri/window.cpp",
    "content": "#include \"modules/niri/window.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n\n#include \"util/rewrite_string.hpp\"\n#include \"util/sanitize_str.hpp\"\n\nnamespace waybar::modules::niri {\n\nWindow::Window(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"window\", id, \"{title}\", 0, true), bar_(bar) {\n  if (!gIPC) gIPC = std::make_unique<IPC>();\n\n  gIPC->registerForIPC(\"WindowsChanged\", this);\n  gIPC->registerForIPC(\"WindowOpenedOrChanged\", this);\n  gIPC->registerForIPC(\"WindowClosed\", this);\n  gIPC->registerForIPC(\"WindowFocusChanged\", this);\n\n  dp.emit();\n}\n\nWindow::~Window() { gIPC->unregisterForIPC(this); }\n\nvoid Window::onEvent(const Json::Value& ev) { dp.emit(); }\n\nvoid Window::doUpdate() {\n  auto ipcLock = gIPC->lockData();\n\n  const auto& windows = gIPC->windows();\n  const auto& workspaces = gIPC->workspaces();\n\n  const auto separateOutputs = config_[\"separate-outputs\"].asBool();\n  const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto& ws) {\n    if (separateOutputs) {\n      return ws[\"is_active\"].asBool() && ws[\"output\"].asString() == bar_.output->name;\n    }\n\n    return ws[\"is_focused\"].asBool();\n  });\n\n  std::vector<Json::Value>::const_iterator it;\n  if (ws_it == workspaces.cend() || (*ws_it)[\"active_window_id\"].isNull()) {\n    it = windows.cend();\n  } else {\n    const auto id = (*ws_it)[\"active_window_id\"].asUInt64();\n    it = std::find_if(windows.cbegin(), windows.cend(),\n                      [id](const auto& win) { return win[\"id\"].asUInt64() == id; });\n  }\n\n  setClass(\"empty\", ws_it == workspaces.cend() || (*ws_it)[\"active_window_id\"].isNull());\n\n  if (it != windows.cend()) {\n    const auto& window = *it;\n\n    const auto title = window[\"title\"].asString();\n    const auto appId = window[\"app_id\"].asString();\n    const auto sanitizedTitle = waybar::util::sanitize_string(title);\n    const auto sanitizedAppId = waybar::util::sanitize_string(appId);\n\n    label_.show();\n    label_.set_markup(waybar::util::rewriteString(\n        fmt::format(fmt::runtime(format_), fmt::arg(\"title\", sanitizedTitle),\n                    fmt::arg(\"app_id\", sanitizedAppId)),\n        config_[\"rewrite\"]));\n\n    updateAppIconName(appId, \"\");\n\n    if (tooltipEnabled()) label_.set_tooltip_markup(title);\n\n    const auto id = window[\"id\"].asUInt64();\n    const auto workspaceId = window[\"workspace_id\"].asUInt64();\n    const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto& win) {\n      return win[\"id\"].asUInt64() != id && win[\"workspace_id\"].asUInt64() == workspaceId;\n    });\n    setClass(\"solo\", isSolo);\n    if (!appId.empty()) setClass(appId, isSolo);\n\n    if (oldAppId_ != appId) {\n      if (!oldAppId_.empty()) setClass(oldAppId_, false);\n      oldAppId_ = appId;\n    }\n  } else {\n    label_.hide();\n    updateAppIconName(\"\", \"\");\n    setClass(\"solo\", false);\n    if (!oldAppId_.empty()) setClass(oldAppId_, false);\n    oldAppId_.clear();\n  }\n}\n\nvoid Window::update() {\n  doUpdate();\n  AAppIconLabel::update();\n}\n\nvoid Window::setClass(const std::string& className, bool enable) {\n  auto styleContext = bar_.window.get_style_context();\n  if (enable) {\n    if (!styleContext->has_class(className)) {\n      styleContext->add_class(className);\n    }\n  } else {\n    styleContext->remove_class(className);\n  }\n}\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "src/modules/niri/workspaces.cpp",
    "content": "#include \"modules/niri/workspaces.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n\nnamespace waybar::modules::niri {\n\nWorkspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AModule(config, \"workspaces\", id, false, false), bar_(bar), box_(bar.orientation, 0) {\n  box_.set_name(\"workspaces\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  if (!gIPC) gIPC = std::make_unique<IPC>();\n\n  gIPC->registerForIPC(\"WorkspacesChanged\", this);\n  gIPC->registerForIPC(\"WorkspaceActivated\", this);\n  gIPC->registerForIPC(\"WorkspaceActiveWindowChanged\", this);\n  gIPC->registerForIPC(\"WorkspaceUrgencyChanged\", this);\n\n  dp.emit();\n}\n\nWorkspaces::~Workspaces() { gIPC->unregisterForIPC(this); }\n\nvoid Workspaces::onEvent(const Json::Value& ev) { dp.emit(); }\n\nvoid Workspaces::doUpdate() {\n  auto ipcLock = gIPC->lockData();\n\n  const auto alloutputs = config_[\"all-outputs\"].asBool();\n  std::vector<Json::Value> my_workspaces;\n  const auto& workspaces = gIPC->workspaces();\n  std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces),\n               [&](const auto& ws) {\n                 if (alloutputs) return true;\n                 return ws[\"output\"].asString() == bar_.output->name;\n               });\n\n  // Remove buttons for removed workspaces.\n  for (auto it = buttons_.begin(); it != buttons_.end();) {\n    auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),\n                           [it](const auto& ws) { return ws[\"id\"].asUInt64() == it->first; });\n    if (ws == my_workspaces.end()) {\n      it = buttons_.erase(it);\n    } else {\n      ++it;\n    }\n  }\n\n  // Add buttons for new workspaces, update existing ones.\n  for (const auto& ws : my_workspaces) {\n    auto bit = buttons_.find(ws[\"id\"].asUInt64());\n    auto& button = bit == buttons_.end() ? addButton(ws) : bit->second;\n    auto style_context = button.get_style_context();\n\n    if (ws[\"is_focused\"].asBool())\n      style_context->add_class(\"focused\");\n    else\n      style_context->remove_class(\"focused\");\n\n    if (ws[\"is_active\"].asBool())\n      style_context->add_class(\"active\");\n    else\n      style_context->remove_class(\"active\");\n\n    if (ws[\"is_urgent\"].asBool())\n      style_context->add_class(\"urgent\");\n    else\n      style_context->remove_class(\"urgent\");\n\n    if (ws[\"output\"]) {\n      if (ws[\"output\"].asString() == bar_.output->name)\n        style_context->add_class(\"current_output\");\n      else\n        style_context->remove_class(\"current_output\");\n    } else {\n      style_context->remove_class(\"current_output\");\n    }\n\n    if (ws[\"active_window_id\"].isNull())\n      style_context->add_class(\"empty\");\n    else\n      style_context->remove_class(\"empty\");\n\n    std::string name;\n    if (ws[\"name\"]) {\n      name = ws[\"name\"].asString();\n    } else {\n      name = std::to_string(ws[\"idx\"].asUInt());\n    }\n    button.set_name(\"niri-workspace-\" + name);\n\n    if (config_[\"format\"].isString()) {\n      auto format = config_[\"format\"].asString();\n      name = fmt::format(fmt::runtime(format), fmt::arg(\"icon\", getIcon(name, ws)),\n                         fmt::arg(\"value\", name), fmt::arg(\"name\", ws[\"name\"].asString()),\n                         fmt::arg(\"index\", ws[\"idx\"].asUInt()),\n                         fmt::arg(\"output\", ws[\"output\"].asString()));\n    }\n    if (!config_[\"disable-markup\"].asBool()) {\n      static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(name);\n    } else {\n      button.set_label(name);\n    }\n\n    if (config_[\"current-only\"].asBool()) {\n      const auto* property = alloutputs ? \"is_focused\" : \"is_active\";\n      if (ws[property].asBool())\n        button.show();\n      else\n        button.hide();\n    } else {\n      button.show();\n    }\n  }\n\n  // Refresh the button order.\n  for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {\n    const auto& ws = *it;\n\n    auto pos = ws[\"idx\"].asUInt() - 1;\n    if (alloutputs) pos = it - my_workspaces.cbegin();\n\n    auto& button = buttons_[ws[\"id\"].asUInt64()];\n    box_.reorder_child(button, pos);\n  }\n}\n\nvoid Workspaces::update() {\n  doUpdate();\n  AModule::update();\n}\n\nGtk::Button& Workspaces::addButton(const Json::Value& ws) {\n  std::string name;\n  if (ws[\"name\"]) {\n    name = ws[\"name\"].asString();\n  } else {\n    name = std::to_string(ws[\"idx\"].asUInt());\n  }\n\n  auto pair = buttons_.emplace(ws[\"id\"].asUInt64(), name);\n  auto&& button = pair.first->second;\n  box_.pack_start(button, false, false, 0);\n  button.set_relief(Gtk::RELIEF_NONE);\n  if (!config_[\"disable-click\"].asBool()) {\n    const auto id = ws[\"id\"].asUInt64();\n    button.signal_pressed().connect([=] {\n      try {\n        // {\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\":1}}}}\n        Json::Value request(Json::objectValue);\n        auto& action = (request[\"Action\"] = Json::Value(Json::objectValue));\n        auto& focusWorkspace = (action[\"FocusWorkspace\"] = Json::Value(Json::objectValue));\n        auto& reference = (focusWorkspace[\"reference\"] = Json::Value(Json::objectValue));\n        reference[\"Id\"] = id;\n\n        IPC::send(request);\n      } catch (const std::exception& e) {\n        spdlog::error(\"Error switching workspace: {}\", e.what());\n      }\n    });\n  }\n  return button;\n}\n\nstd::string Workspaces::getIcon(const std::string& value, const Json::Value& ws) {\n  const auto& icons = config_[\"format-icons\"];\n  if (!icons) return value;\n\n  if (ws[\"is_urgent\"].asBool() && icons[\"urgent\"]) return icons[\"urgent\"].asString();\n\n  if (ws[\"is_active\"].asBool() && icons[\"active\"]) return icons[\"active\"].asString();\n\n  if (ws[\"is_focused\"].asBool() && icons[\"focused\"]) return icons[\"focused\"].asString();\n\n  if (ws[\"active_window_id\"].isNull() && icons[\"empty\"]) return icons[\"empty\"].asString();\n\n  if (ws[\"name\"]) {\n    const auto& name = ws[\"name\"].asString();\n    if (icons[name]) return icons[name].asString();\n  }\n\n  const auto idx = ws[\"idx\"].asString();\n  if (icons[idx]) return icons[idx].asString();\n\n  if (icons[\"default\"]) return icons[\"default\"].asString();\n\n  return value;\n}\n\n}  // namespace waybar::modules::niri\n"
  },
  {
    "path": "src/modules/power_profiles_daemon.cpp",
    "content": "#include \"modules/power_profiles_daemon.hpp\"\n\n#include <fmt/args.h>\n#include <glibmm.h>\n#include <glibmm/variant.h>\n#include <spdlog/spdlog.h>\n\nnamespace waybar::modules {\n\nPowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"power-profiles-daemon\", id, \"{icon}\", 0, false, true), connected_(false) {\n  if (config_[\"tooltip-format\"].isString()) {\n    tooltipFormat_ = config_[\"tooltip-format\"].asString();\n  } else {\n    tooltipFormat_ = \"Power profile: {profile}\\nDriver: {driver}\";\n  }\n  // Fasten your seatbelt, we're up for quite a ride. The rest of the\n  // init is performed asynchronously. There's 2 callbacks involved.\n  // Here's the overall idea:\n  // 1. Async connect to the system bus.\n  // 2. In the system bus connect callback, try to call\n  //    org.freedesktop.DBus.Properties.GetAll to see if\n  //    power-profiles-daemon is able to respond.\n  // 3. In the GetAll callback, connect the activeProfile monitoring\n  //    callback, consider the init to be successful. Meaning start\n  //    drawing the module.\n  //\n  // There's sadly no other way around that, we have to try to call a\n  // method on the proxy to see whether or not something's responding\n  // on the other side.\n\n  // NOTE: the DBus addresses are under migration. They should be\n  // changed to org.freedesktop.UPower.PowerProfiles at some point.\n  //\n  // See\n  // https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20\n  //\n  // The old name is still announced for now. Let's rather use the old\n  // addresses for compatibility sake.\n  //\n  // Revisit this in 2026, systems should be updated by then.\n\n  Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"net.hadess.PowerProfiles\",\n                                   \"/net/hadess/PowerProfiles\", \"net.hadess.PowerProfiles\",\n                                   sigc::mem_fun(*this, &PowerProfilesDaemon::busConnectedCb));\n  // Schedule update to set the initial visibility\n  dp.emit();\n}\n\nvoid PowerProfilesDaemon::busConnectedCb(Glib::RefPtr<Gio::AsyncResult>& r) {\n  try {\n    powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_finish(r);\n    using GetAllProfilesVar = Glib::Variant<std::tuple<Glib::ustring>>;\n    auto callArgs = GetAllProfilesVar::create(std::make_tuple(\"net.hadess.PowerProfiles\"));\n    powerProfilesProxy_->call(\"org.freedesktop.DBus.Properties.GetAll\",\n                              sigc::mem_fun(*this, &PowerProfilesDaemon::getAllPropsCb), callArgs);\n    // Connect active profile callback\n  } catch (const std::exception& e) {\n    spdlog::error(\"Failed to create the power profiles daemon DBus proxy: {}\", e.what());\n  } catch (const Glib::Error& e) {\n    spdlog::error(\"Failed to create the power profiles daemon DBus proxy: {}\",\n                  std::string(e.what()));\n  }\n}\n\n// Callback for the GetAll call.\n//\n// We're abusing this call to make sure power-profiles-daemon is\n// available on the host. We're not really using\nvoid PowerProfilesDaemon::getAllPropsCb(Glib::RefPtr<Gio::AsyncResult>& r) {\n  try {\n    auto _ = powerProfilesProxy_->call_finish(r);\n    // Power-profiles-daemon responded something, we can assume it's\n    // available, we can safely attach the activeProfile monitoring\n    // now.\n    connected_ = true;\n    powerProfilesProxy_->signal_properties_changed().connect(\n        sigc::mem_fun(*this, &PowerProfilesDaemon::profileChangedCb));\n    populateInitState();\n  } catch (const std::exception& err) {\n    spdlog::error(\"Failed to query power-profiles-daemon via dbus: {}\", err.what());\n  } catch (const Glib::Error& err) {\n    spdlog::error(\"Failed to query power-profiles-daemon via dbus: {}\", std::string(err.what()));\n  }\n}\n\nvoid PowerProfilesDaemon::populateInitState() {\n  // Retrieve current active profile\n  Glib::Variant<std::string> profileStr;\n  powerProfilesProxy_->get_cached_property(profileStr, \"ActiveProfile\");\n\n  // Retrieve profiles list, it's aa{sv}.\n  using ProfilesType = std::vector<std::map<Glib::ustring, Glib::Variant<std::string>>>;\n  Glib::Variant<ProfilesType> profilesVariant;\n  powerProfilesProxy_->get_cached_property(profilesVariant, \"Profiles\");\n  for (auto& variantDict : profilesVariant.get()) {\n    Glib::ustring name;\n    Glib::ustring driver;\n    if (auto p = variantDict.find(\"Profile\"); p != variantDict.end()) {\n      name = p->second.get();\n    }\n    if (auto d = variantDict.find(\"Driver\"); d != variantDict.end()) {\n      driver = d->second.get();\n    }\n    if (!name.empty()) {\n      availableProfiles_.emplace_back(std::move(name), std::move(driver));\n    } else {\n      spdlog::error(\n          \"Power profiles daemon: power-profiles-daemon sent us an empty power profile name. \"\n          \"Something is wrong.\");\n    }\n  }\n\n  // Find the index of the current activated mode (to toggle)\n  std::string str = profileStr.get();\n  switchToProfile(str);\n}\n\nvoid PowerProfilesDaemon::profileChangedCb(\n    const Gio::DBus::Proxy::MapChangedProperties& changedProperties,\n    const std::vector<Glib::ustring>& invalidatedProperties) {\n  // We're likely connected if this callback gets triggered.\n  // But better be safe than sorry.\n  if (connected_) {\n    if (auto activeProfileVariant = changedProperties.find(\"ActiveProfile\");\n        activeProfileVariant != changedProperties.end()) {\n      std::string activeProfile =\n          Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(activeProfileVariant->second)\n              .get();\n      switchToProfile(activeProfile);\n    }\n  }\n}\n\n// Look for the profile str in our internal profiles list. Using a\n// vector to store the profiles ain't the smartest move\n// complexity-wise, but it makes toggling between the mode easy. This\n// vector is 3 elements max, we'll be fine :P\nvoid PowerProfilesDaemon::switchToProfile(std::string const& str) {\n  auto pred = [str](Profile const& p) { return p.name == str; };\n  this->activeProfile_ = std::find_if(availableProfiles_.begin(), availableProfiles_.end(), pred);\n  if (activeProfile_ == availableProfiles_.end()) {\n    spdlog::error(\n        \"Power profile daemon: can't find the active profile {} in the available profiles list\",\n        str);\n  }\n  dp.emit();\n}\n\nauto PowerProfilesDaemon::update() -> void {\n  if (connected_ && activeProfile_ != availableProfiles_.end()) {\n    auto profile = (*activeProfile_);\n    // Set label\n    fmt::dynamic_format_arg_store<fmt::format_context> store;\n    store.push_back(fmt::arg(\"profile\", profile.name));\n    store.push_back(fmt::arg(\"driver\", profile.driver));\n    store.push_back(fmt::arg(\"icon\", getIcon(0, profile.name)));\n    label_.set_markup(fmt::vformat(format_, store));\n    if (tooltipEnabled()) {\n      label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store));\n    }\n\n    // Set CSS class\n    if (!currentStyle_.empty()) {\n      label_.get_style_context()->remove_class(currentStyle_);\n    }\n    label_.get_style_context()->add_class(profile.name);\n    currentStyle_ = profile.name;\n    event_box_.set_visible(true);\n  } else {\n    event_box_.set_visible(false);\n  }\n\n  ALabel::update();\n}\n\nbool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {\n  if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) {\n    if (availableProfiles_.empty()) return true;\n    if (activeProfile_ == availableProfiles_.end()) {\n      activeProfile_ = availableProfiles_.begin();\n    }\n    if (e->button == 1) /* left click */ {\n      activeProfile_++;\n      if (activeProfile_ == availableProfiles_.end()) {\n        activeProfile_ = availableProfiles_.begin();\n      }\n    } else {\n      if (activeProfile_ == availableProfiles_.begin()) {\n        activeProfile_ = availableProfiles_.end();\n      }\n      activeProfile_--;\n    }\n\n    using VarStr = Glib::Variant<Glib::ustring>;\n    using SetPowerProfileVar = Glib::Variant<std::tuple<Glib::ustring, Glib::ustring, VarStr>>;\n    VarStr activeProfileVariant = VarStr::create(activeProfile_->name);\n    auto callArgs = SetPowerProfileVar::create(\n        std::make_tuple(\"net.hadess.PowerProfiles\", \"ActiveProfile\", activeProfileVariant));\n    powerProfilesProxy_->call(\"org.freedesktop.DBus.Properties.Set\",\n                              sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), callArgs);\n  }\n  return true;\n}\n\nvoid PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {\n  try {\n    auto _ = powerProfilesProxy_->call_finish(r);\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Failed to set the active power profile: {}\", e.what());\n  } catch (const Glib::Error& e) {\n    spdlog::error(\"Failed to set the active power profile: {}\", std::string(e.what()));\n  }\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/privacy/privacy.cpp",
    "content": "#include \"modules/privacy/privacy.hpp\"\n\n#include <json/value.h>\n#include <spdlog/spdlog.h>\n\n#include <string>\n\n#include \"AModule.hpp\"\n#include \"modules/privacy/privacy_item.hpp\"\n\nnamespace waybar::modules::privacy {\n\nusing util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT;\nusing util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;\nusing util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;\nusing util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;\n\nPrivacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation,\n                 const std::string& pos)\n    : AModule(config, \"privacy\", id),\n      nodes_screenshare(),\n      nodes_audio_in(),\n      nodes_audio_out(),\n      visibility_conn(),\n      box_(orientation, 0) {\n  box_.set_name(name_);\n\n  event_box_.add(box_);\n\n  // Icon Spacing\n  if (config_[\"icon-spacing\"].isUInt()) {\n    iconSpacing = config_[\"icon-spacing\"].asUInt();\n  }\n  box_.set_spacing(iconSpacing);\n\n  // Icon Size\n  if (config_[\"icon-size\"].isUInt()) {\n    iconSize = config_[\"icon-size\"].asUInt();\n  }\n\n  // Transition Duration\n  if (config_[\"transition-duration\"].isUInt()) {\n    transition_duration = config_[\"transition-duration\"].asUInt();\n  }\n\n  // Initialize each privacy module\n  Json::Value modules = config_[\"modules\"];\n  // Add Screenshare and Mic usage as default modules if none are specified\n  if (!modules.isArray() || modules.empty()) {\n    modules = Json::Value(Json::arrayValue);\n    for (const auto& type : {\"screenshare\", \"audio-in\"}) {\n      Json::Value obj = Json::Value(Json::objectValue);\n      obj[\"type\"] = type;\n      modules.append(obj);\n    }\n  }\n\n  std::map<std::string, std::tuple<decltype(&nodes_audio_in), PrivacyNodeType> > typeMap = {\n      {\"screenshare\", {&nodes_screenshare, PRIVACY_NODE_TYPE_VIDEO_INPUT}},\n      {\"audio-in\", {&nodes_audio_in, PRIVACY_NODE_TYPE_AUDIO_INPUT}},\n      {\"audio-out\", {&nodes_audio_out, PRIVACY_NODE_TYPE_AUDIO_OUTPUT}},\n  };\n\n  for (const auto& module : modules) {\n    if (!module.isObject() || !module[\"type\"].isString()) continue;\n    const std::string type = module[\"type\"].asString();\n\n    auto iter = typeMap.find(type);\n    if (iter != typeMap.end()) {\n      auto& [nodePtr, nodeType] = iter->second;\n      auto* item = Gtk::make_managed<PrivacyItem>(module, nodeType, nodePtr, orientation, pos,\n                                                  iconSize, transition_duration);\n      box_.add(*item);\n    }\n  }\n\n  for (const auto& ignore_item : config_[\"ignore\"]) {\n    if (!ignore_item.isObject() || !ignore_item[\"type\"].isString() ||\n        !ignore_item[\"name\"].isString())\n      continue;\n    const std::string type = ignore_item[\"type\"].asString();\n    const std::string name = ignore_item[\"name\"].asString();\n\n    auto iter = typeMap.find(type);\n    if (iter != typeMap.end()) {\n      auto& [_, nodeType] = iter->second;\n      ignore.emplace(nodeType, std::move(name));\n    }\n  }\n\n  if (config_[\"ignore-monitor\"].isBool()) {\n    ignore_monitor = config_[\"ignore-monitor\"].asBool();\n  }\n\n  backend = util::PipewireBackend::PipewireBackend::getInstance();\n  backend->privacy_nodes_changed_signal_event.connect(\n      sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));\n\n  dp.emit();\n}\n\nvoid Privacy::onPrivacyNodesChanged() {\n  mutex_.lock();\n  nodes_audio_out.clear();\n  nodes_audio_in.clear();\n  nodes_screenshare.clear();\n\n  for (auto& node : backend->privacy_nodes) {\n    if (ignore_monitor && node.second->is_monitor) continue;\n\n    auto iter = ignore.find(std::pair(node.second->type, node.second->node_name));\n    if (iter != ignore.end()) continue;\n\n    switch (node.second->state) {\n      case PW_NODE_STATE_RUNNING:\n        switch (node.second->type) {\n          case PRIVACY_NODE_TYPE_VIDEO_INPUT:\n            nodes_screenshare.push_back(node.second);\n            break;\n          case PRIVACY_NODE_TYPE_AUDIO_INPUT:\n            nodes_audio_in.push_back(node.second);\n            break;\n          case PRIVACY_NODE_TYPE_AUDIO_OUTPUT:\n            nodes_audio_out.push_back(node.second);\n            break;\n          case PRIVACY_NODE_TYPE_NONE:\n            continue;\n        }\n        break;\n      default:\n        break;\n    }\n  }\n\n  mutex_.unlock();\n  dp.emit();\n}\n\nauto Privacy::update() -> void {\n  // set in modules or not\n  bool setScreenshare = false;\n  bool setAudioIn = false;\n  bool setAudioOut = false;\n\n  // used or not\n  bool useScreenshare = false;\n  bool useAudioIn = false;\n  bool useAudioOut = false;\n\n  mutex_.lock();\n  for (Gtk::Widget* widget : box_.get_children()) {\n    auto* module = dynamic_cast<PrivacyItem*>(widget);\n    if (module == nullptr) continue;\n    switch (module->privacy_type) {\n      case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:\n        setScreenshare = true;\n        useScreenshare = !nodes_screenshare.empty();\n        module->set_in_use(useScreenshare);\n        break;\n      case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:\n        setAudioIn = true;\n        useAudioIn = !nodes_audio_in.empty();\n        module->set_in_use(useAudioIn);\n        break;\n      case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:\n        setAudioOut = true;\n        useAudioOut = !nodes_audio_out.empty();\n        module->set_in_use(useAudioOut);\n        break;\n      case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:\n        break;\n    }\n  }\n  mutex_.unlock();\n\n  // Hide the whole widget if none are in use\n  bool isVisible = (setScreenshare && useScreenshare) || (setAudioIn && useAudioIn) ||\n                   (setAudioOut && useAudioOut);\n\n  if (isVisible != event_box_.get_visible()) {\n    // Disconnect any previous connection so that it doesn't get activated in\n    // the future, hiding the module when it should be visible\n    visibility_conn.disconnect();\n    if (isVisible) {\n      event_box_.set_visible(true);\n    } else {\n      // Hides the widget when all of the privacy_item revealers animations\n      // have finished animating\n      visibility_conn = Glib::signal_timeout().connect(\n          sigc::track_obj(\n              [this, setScreenshare, setAudioOut, setAudioIn]() {\n                mutex_.lock();\n                bool visible = false;\n                visible |= setScreenshare && !nodes_screenshare.empty();\n                visible |= setAudioIn && !nodes_audio_in.empty();\n                visible |= setAudioOut && !nodes_audio_out.empty();\n                mutex_.unlock();\n                event_box_.set_visible(visible);\n                return false;\n              },\n              *this),\n          transition_duration);\n    }\n  }\n\n  // Call parent update\n  AModule::update();\n}\n\n}  // namespace waybar::modules::privacy\n"
  },
  {
    "path": "src/modules/privacy/privacy_item.cpp",
    "content": "#include \"modules/privacy/privacy_item.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <string>\n\n#include \"glibmm/main.h\"\n#include \"gtkmm/label.h\"\n#include \"gtkmm/revealer.h\"\n#include \"gtkmm/tooltip.h\"\n#include \"util/pipewire/privacy_node_info.hpp\"\n\nnamespace waybar::modules::privacy {\n\nPrivacyItem::PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_,\n                         std::list<PrivacyNodeInfo*>* nodes_, Gtk::Orientation orientation,\n                         const std::string& pos, const uint icon_size,\n                         const uint transition_duration)\n    : Gtk::Revealer(),\n      privacy_type(privacy_type_),\n      nodes(nodes_),\n      signal_conn(),\n      tooltip_window(Gtk::ORIENTATION_VERTICAL, 0),\n      box_(Gtk::ORIENTATION_HORIZONTAL, 0),\n      icon_() {\n  switch (privacy_type) {\n    case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:\n      box_.get_style_context()->add_class(\"audio-in\");\n      iconName = \"waybar-privacy-audio-input-symbolic\";\n      break;\n    case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:\n      box_.get_style_context()->add_class(\"audio-out\");\n      iconName = \"waybar-privacy-audio-output-symbolic\";\n      break;\n    case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:\n      box_.get_style_context()->add_class(\"screenshare\");\n      iconName = \"waybar-privacy-screen-share-symbolic\";\n      break;\n    default:\n    case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:\n      return;\n  }\n\n  // Set the reveal transition to not look weird when sliding in\n  if (pos == \"modules-left\") {\n    set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL\n                            ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT\n                            : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN);\n  } else if (pos == \"modules-center\") {\n    set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);\n  } else if (pos == \"modules-right\") {\n    set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL\n                            ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT\n                            : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);\n  }\n  set_transition_duration(transition_duration);\n\n  box_.set_name(\"privacy-item\");\n\n  // We use `set_center_widget` instead of `add` to make sure the icon is\n  // centered even if the orientation is vertical\n  box_.set_center_widget(icon_);\n\n  icon_.set_pixel_size(icon_size);\n  add(box_);\n\n  // Icon Name\n  if (config_[\"icon-name\"].isString()) {\n    iconName = config_[\"icon-name\"].asString();\n  }\n  icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);\n\n  // Tooltip Icon Size\n  if (config_[\"tooltip-icon-size\"].isUInt()) {\n    tooltipIconSize = config_[\"tooltip-icon-size\"].asUInt();\n  }\n  // Tooltip\n  if (config_[\"tooltip\"].isString()) {\n    tooltip = config_[\"tooltip\"].asBool();\n  }\n  set_has_tooltip(tooltip);\n  if (tooltip) {\n    // Sets the window to use when showing the tooltip\n    update_tooltip();\n    this->signal_query_tooltip().connect(sigc::track_obj(\n        [this](int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip) {\n          tooltip->set_custom(tooltip_window);\n          return true;\n        },\n        *this));\n  }\n\n  // Don't show by default\n  set_reveal_child(true);\n  set_visible(false);\n}\n\nvoid PrivacyItem::update_tooltip() {\n  // Removes all old nodes\n  for (auto* child : tooltip_window.get_children()) {\n    tooltip_window.remove(*child);\n    // despite the remove, still needs a delete to prevent memory leak. Speculating that this might\n    // work differently in GTK4.\n    delete child;\n  }\n  for (auto* node : *nodes) {\n    auto* box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL, 4);\n\n    // Set device icon\n    auto* node_icon = Gtk::make_managed<Gtk::Image>();\n    node_icon->set_pixel_size(tooltipIconSize);\n    node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID);\n    box->add(*node_icon);\n\n    // Set model\n    auto* nodeName = Gtk::make_managed<Gtk::Label>(node->getName());\n    box->add(*nodeName);\n\n    tooltip_window.add(*box);\n  }\n\n  tooltip_window.show_all();\n}\n\nvoid PrivacyItem::set_in_use(bool in_use) {\n  if (in_use) {\n    update_tooltip();\n  }\n\n  if (this->in_use == in_use && init) return;\n\n  if (init) {\n    // Disconnect any previous connection so that it doesn't get activated in\n    // the future, hiding the module when it should be visible\n    signal_conn.disconnect();\n\n    this->in_use = in_use;\n    guint duration = 0;\n    if (this->in_use) {\n      set_visible(true);\n    } else {\n      set_reveal_child(false);\n      duration = get_transition_duration();\n    }\n\n    signal_conn = Glib::signal_timeout().connect(sigc::track_obj(\n                                                     [this] {\n                                                       if (this->in_use) {\n                                                         set_reveal_child(true);\n                                                       } else {\n                                                         set_visible(false);\n                                                       }\n                                                       return false;\n                                                     },\n                                                     *this),\n                                                 duration);\n  } else {\n    set_visible(false);\n    set_reveal_child(false);\n  }\n  this->init = true;\n}\n\n}  // namespace waybar::modules::privacy\n"
  },
  {
    "path": "src/modules/pulseaudio.cpp",
    "content": "#include \"modules/pulseaudio.hpp\"\n\nwaybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"pulseaudio\", id, \"{volume}%\") {\n  event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n  event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));\n\n  backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });\n  backend->setIgnoredSinks(config_[\"ignored-sinks\"]);\n}\n\nbool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {\n  // change the pulse volume only when no user provided\n  // events are configured\n  if (config_[\"on-scroll-up\"].isString() || config_[\"on-scroll-down\"].isString()) {\n    return AModule::handleScroll(e);\n  }\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) {\n    return true;\n  }\n  int max_volume = 100;\n  double step = 1;\n  // isDouble returns true for integers as well, just in case\n  if (config_[\"scroll-step\"].isDouble()) {\n    step = config_[\"scroll-step\"].asDouble();\n  }\n  if (config_[\"max-volume\"].isInt()) {\n    max_volume = config_[\"max-volume\"].asInt();\n  }\n\n  auto change_type = (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::RIGHT)\n                         ? util::ChangeType::Increase\n                         : util::ChangeType::Decrease;\n\n  backend->changeVolume(change_type, step, max_volume);\n  return true;\n}\n\nstatic const std::array<std::string, 9> ports = {\n    \"headphone\", \"speaker\", \"hdmi\", \"headset\", \"hands-free\", \"portable\", \"car\", \"hifi\", \"phone\",\n};\n\nconst std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {\n  std::vector<std::string> res;\n  auto sink_muted = backend->getSinkMuted();\n  if (sink_muted) {\n    res.emplace_back(backend->getCurrentSinkName() + \"-muted\");\n  }\n  res.push_back(backend->getCurrentSinkName());\n  res.push_back(backend->getDefaultSourceName());\n  std::string nameLC = backend->getSinkPortName() + backend->getFormFactor();\n  std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);\n  for (auto const& port : ports) {\n    if (nameLC.find(port) != std::string::npos) {\n      if (sink_muted) {\n        res.emplace_back(port + \"-muted\");\n      }\n      res.push_back(port);\n      break;\n    }\n  }\n  if (sink_muted) {\n    res.emplace_back(\"default-muted\");\n  }\n  return res;\n}\n\nauto waybar::modules::Pulseaudio::update() -> void {\n  auto format = format_;\n  std::string tooltip_format;\n  auto sink_volume = backend->getSinkVolume();\n  if (!alt_) {\n    std::string format_name = \"format\";\n    if (backend->isBluetooth()) {\n      format_name = format_name + \"-bluetooth\";\n      label_.get_style_context()->add_class(\"bluetooth\");\n    } else {\n      label_.get_style_context()->remove_class(\"bluetooth\");\n    }\n    if (backend->getSinkMuted()) {\n      // Check muted bluetooth format exist, otherwise fallback to default muted format\n      if (format_name != \"format\" && !config_[format_name + \"-muted\"].isString()) {\n        format_name = \"format\";\n      }\n      format_name = format_name + \"-muted\";\n      label_.get_style_context()->add_class(\"muted\");\n      label_.get_style_context()->add_class(\"sink-muted\");\n    } else {\n      label_.get_style_context()->remove_class(\"muted\");\n      label_.get_style_context()->remove_class(\"sink-muted\");\n    }\n    auto state = getState(sink_volume, true);\n    if (!state.empty() && config_[format_name + \"-\" + state].isString()) {\n      format = config_[format_name + \"-\" + state].asString();\n    } else if (config_[format_name].isString()) {\n      format = config_[format_name].asString();\n    }\n  }\n  // TODO: find a better way to split source/sink\n  std::string format_source = \"{volume}%\";\n  if (backend->getSourceMuted()) {\n    label_.get_style_context()->add_class(\"source-muted\");\n    if (config_[\"format-source-muted\"].isString()) {\n      format_source = config_[\"format-source-muted\"].asString();\n    }\n  } else {\n    label_.get_style_context()->remove_class(\"source-muted\");\n    if (config_[\"format-source\"].isString()) {\n      format_source = config_[\"format-source\"].asString();\n    }\n  }\n\n  auto source_volume = backend->getSourceVolume();\n  auto sink_desc = backend->getSinkDesc();\n  auto source_desc = backend->getSourceDesc();\n\n  format_source = fmt::format(fmt::runtime(format_source), fmt::arg(\"volume\", source_volume));\n  auto text = fmt::format(\n      fmt::runtime(format), fmt::arg(\"desc\", sink_desc), fmt::arg(\"volume\", sink_volume),\n      fmt::arg(\"format_source\", format_source), fmt::arg(\"source_volume\", source_volume),\n      fmt::arg(\"source_desc\", source_desc), fmt::arg(\"icon\", getIcon(sink_volume, getPulseIcon())));\n  if (text.empty()) {\n    label_.hide();\n  } else {\n    label_.set_markup(text);\n    label_.show();\n  }\n\n  if (tooltipEnabled()) {\n    if (tooltip_format.empty() && config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    if (!tooltip_format.empty()) {\n      label_.set_tooltip_markup(fmt::format(\n          fmt::runtime(tooltip_format), fmt::arg(\"desc\", sink_desc),\n          fmt::arg(\"volume\", sink_volume), fmt::arg(\"format_source\", format_source),\n          fmt::arg(\"source_volume\", source_volume), fmt::arg(\"source_desc\", source_desc),\n          fmt::arg(\"icon\", getIcon(sink_volume, getPulseIcon()))));\n    } else {\n      label_.set_tooltip_markup(sink_desc);\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n"
  },
  {
    "path": "src/modules/pulseaudio_slider.cpp",
    "content": "#include \"modules/pulseaudio_slider.hpp\"\n\nnamespace waybar::modules {\n\nPulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& config)\n    : ASlider(config, \"pulseaudio-slider\", id) {\n  backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });\n  backend->setIgnoredSinks(config_[\"ignored-sinks\"]);\n\n  if (config_[\"target\"].isString()) {\n    std::string target = config_[\"target\"].asString();\n    if (target == \"sink\") {\n      this->target = PulseaudioSliderTarget::Sink;\n    } else if (target == \"source\") {\n      this->target = PulseaudioSliderTarget::Source;\n    }\n  }\n}\n\nvoid PulseaudioSlider::update() {\n  switch (target) {\n    case PulseaudioSliderTarget::Sink:\n      if (backend->getSinkMuted()) {\n        scale_.set_value(min_);\n      } else {\n        scale_.set_value(backend->getSinkVolume());\n      }\n      break;\n\n    case PulseaudioSliderTarget::Source:\n      if (backend->getSourceMuted()) {\n        scale_.set_value(min_);\n      } else {\n        scale_.set_value(backend->getSourceVolume());\n      }\n      break;\n  }\n}\n\nvoid PulseaudioSlider::onValueChanged() {\n  bool is_mute = false;\n\n  switch (target) {\n    case PulseaudioSliderTarget::Sink:\n      if (backend->getSinkMuted()) {\n        is_mute = true;\n      }\n      break;\n\n    case PulseaudioSliderTarget::Source:\n      if (backend->getSourceMuted()) {\n        is_mute = true;\n      }\n      break;\n  }\n\n  uint16_t volume = scale_.get_value();\n\n  if (is_mute) {\n    // Avoid setting sink/source to volume 0 if the user muted if via another mean.\n    if (volume == 0) {\n      return;\n    }\n\n    // If the sink/source is mute, but the user clicked the slider, unmute it!\n    else {\n      switch (target) {\n        case PulseaudioSliderTarget::Sink:\n          backend->toggleSinkMute(false);\n          break;\n\n        case PulseaudioSliderTarget::Source:\n          backend->toggleSourceMute(false);\n          break;\n      }\n    }\n  }\n\n  backend->changeVolume(volume, min_, max_);\n}\n\n}  // namespace waybar::modules"
  },
  {
    "path": "src/modules/river/layout.cpp",
    "content": "#include \"modules/river/layout.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <wayland-client.h>\n\n#include \"client.hpp\"\n\nnamespace waybar::modules::river {\n\nstatic void listen_focused_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                                uint32_t tags) {\n  // Intentionally empty\n}\n\nstatic void listen_view_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                             struct wl_array* tags) {\n  // Intentionally empty\n}\n\nstatic void listen_urgent_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                               uint32_t tags) {\n  // Intentionally empty\n}\n\nstatic void listen_layout_name(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                               const char* layout) {\n  static_cast<Layout*>(data)->handle_name(layout);\n}\n\nstatic void listen_layout_name_clear(void* data,\n                                     struct zriver_output_status_v1* zriver_output_status_v1) {\n  static_cast<Layout*>(data)->handle_clear();\n}\n\nstatic void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                  struct wl_output* output) {\n  static_cast<Layout*>(data)->handle_focused_output(output);\n}\n\nstatic void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                    struct wl_output* output) {\n  static_cast<Layout*>(data)->handle_unfocused_output(output);\n}\n\nstatic void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                const char* title) {\n  // Intentionally empty\n}\n\nstatic void listen_mode(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                        const char* mode) {\n  // Intentionally empty\n}\n\nstatic const zriver_output_status_v1_listener output_status_listener_impl{\n    .focused_tags = listen_focused_tags,\n    .view_tags = listen_view_tags,\n    .urgent_tags = listen_urgent_tags,\n    .layout_name = listen_layout_name,\n    .layout_name_clear = listen_layout_name_clear,\n};\n\nstatic const zriver_seat_status_v1_listener seat_status_listener_impl{\n    .focused_output = listen_focused_output,\n    .unfocused_output = listen_unfocused_output,\n    .focused_view = listen_focused_view,\n    .mode = listen_mode,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 4);\n\n    // implies ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION\n    if (version < ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) {\n      spdlog::error(\n          \"river server does not support the \\\"layout_name\\\" and \\\"layout_clear\\\" events; the \"\n          \"module will be disabled\" +\n          std::to_string(version));\n      return;\n    }\n    static_cast<Layout*>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1*>(\n        wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));\n  }\n\n  if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 1);\n    static_cast<Layout*>(data)->seat_ =\n        static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  // Nobody cares\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nLayout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::ALabel(config, \"layout\", id, \"{}\"),\n      status_manager_{nullptr},\n      seat_{nullptr},\n      bar_(bar),\n      output_status_{nullptr} {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n\n  if (!status_manager_) {\n    spdlog::error(\"river_status_manager_v1 not advertised\");\n    return;\n  }\n\n  if (!seat_) {\n    spdlog::error(\"wl_seat not advertised\");\n    return;\n  }\n\n  label_.hide();\n  ALabel::update();\n\n  seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);\n  zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);\n\n  output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_);\n  zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);\n\n  zriver_status_manager_v1_destroy(status_manager_);\n}\n\nLayout::~Layout() {\n  if (output_status_) {\n    zriver_output_status_v1_destroy(output_status_);\n  }\n  if (seat_status_) {\n    zriver_seat_status_v1_destroy(seat_status_);\n  }\n}\n\nvoid Layout::handle_name(const char* name) {\n  if (std::strcmp(name, \"\") == 0 || format_.empty()) {\n    label_.hide();  // hide empty labels or labels with empty format\n  } else {\n    if (!name_.empty()) {\n      label_.get_style_context()->remove_class(name_);\n    }\n\n    label_.get_style_context()->add_class(name);\n    label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw()));\n    label_.show();\n  }\n  name_ = name;\n  ALabel::update();\n}\n\nvoid Layout::handle_clear() {\n  label_.hide();\n  ALabel::update();\n}\n\nvoid Layout::handle_focused_output(struct wl_output* output) {\n  if (output_ == output) {  // if we focused the output this bar belongs to\n    label_.get_style_context()->add_class(\"focused\");\n    ALabel::update();\n  }\n  focused_output_ = output;\n}\n\nvoid Layout::handle_unfocused_output(struct wl_output* output) {\n  if (output_ == output) {  // if we unfocused the output this bar belongs to\n    label_.get_style_context()->remove_class(\"focused\");\n    ALabel::update();\n  }\n}\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "src/modules/river/mode.cpp",
    "content": "#include \"modules/river/mode.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <wayland-client.h>\n\n#include \"client.hpp\"\n\nnamespace waybar::modules::river {\n\nstatic void listen_focused_output(void* data, struct zriver_seat_status_v1* seat_status,\n                                  struct wl_output* output) {\n  // Intentionally empty\n}\n\nstatic void listen_unfocused_output(void* data, struct zriver_seat_status_v1* seat_status,\n                                    struct wl_output* output) {\n  // Intentionally empty\n}\n\nstatic void listen_focused_view(void* data, struct zriver_seat_status_v1* seat_status,\n                                const char* title) {\n  // Intentionally empty\n}\n\nstatic void listen_mode(void* data, struct zriver_seat_status_v1* seat_status, const char* mode) {\n  static_cast<Mode*>(data)->handle_mode(mode);\n}\n\nstatic const zriver_seat_status_v1_listener seat_status_listener_impl = {\n    .focused_output = listen_focused_output,\n    .unfocused_output = listen_unfocused_output,\n    .focused_view = listen_focused_view,\n    .mode = listen_mode,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 3);\n    if (version < ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) {\n      spdlog::error(\n          \"river server does not support the \\\"mode\\\" event; the module will be disabled\");\n      return;\n    }\n    static_cast<Mode*>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1*>(\n        wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));\n  } else if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 1);\n    static_cast<Mode*>(data)->seat_ =\n        static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  // Nobody cares\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nMode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::ALabel(config, \"mode\", id, \"{}\"),\n      status_manager_{nullptr},\n      seat_{nullptr},\n      bar_(bar),\n      mode_{\"\"},\n      seat_status_{nullptr} {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  if (!status_manager_) {\n    spdlog::error(\"river_status_manager_v1 not advertised\");\n    return;\n  }\n\n  if (!seat_) {\n    spdlog::error(\"wl_seat not advertised\");\n    return;\n  }\n\n  label_.hide();\n  ALabel::update();\n\n  seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);\n  zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);\n\n  zriver_status_manager_v1_destroy(status_manager_);\n}\n\nMode::~Mode() {\n  if (seat_status_) {\n    zriver_seat_status_v1_destroy(seat_status_);\n  }\n}\n\nvoid Mode::handle_mode(const char* mode) {\n  if (format_.empty()) {\n    label_.hide();\n  } else {\n    if (!mode_.empty()) {\n      label_.get_style_context()->remove_class(mode_);\n    }\n\n    label_.get_style_context()->add_class(mode);\n    label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw()));\n    label_.show();\n  }\n\n  mode_ = mode;\n  ALabel::update();\n}\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "src/modules/river/tags.cpp",
    "content": "#include \"modules/river/tags.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n#include <wayland-client.h>\n\n#include <algorithm>\n\n#include \"client.hpp\"\n#include \"xdg-output-unstable-v1-client-protocol.h\"\n\nnamespace waybar::modules::river {\n\nstatic void listen_focused_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                                uint32_t tags) {\n  static_cast<Tags*>(data)->handle_focused_tags(tags);\n}\n\nstatic void listen_view_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                             struct wl_array* tags) {\n  static_cast<Tags*>(data)->handle_view_tags(tags);\n}\n\nstatic void listen_urgent_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1,\n                               uint32_t tags) {\n  static_cast<Tags*>(data)->handle_urgent_tags(tags);\n}\n\nstatic const zriver_output_status_v1_listener output_status_listener_impl{\n    .focused_tags = listen_focused_tags,\n    .view_tags = listen_view_tags,\n    .urgent_tags = listen_urgent_tags,\n};\n\nstatic void listen_command_success(void* data,\n                                   struct zriver_command_callback_v1* zriver_command_callback_v1,\n                                   const char* output) {\n  // Do nothing but keep listener to avoid crashing when command was successful\n}\n\nstatic void listen_command_failure(void* data,\n                                   struct zriver_command_callback_v1* zriver_command_callback_v1,\n                                   const char* output) {\n  spdlog::error(\"failure when selecting/toggling tags {}\", output);\n}\n\nstatic const zriver_command_callback_v1_listener command_callback_listener_impl{\n    .success = listen_command_success,\n    .failure = listen_command_failure,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {\n    version = std::min(version, 2u);\n    if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {\n      spdlog::warn(\"river server does not support urgent tags\");\n    }\n    static_cast<Tags*>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1*>(\n        wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));\n  }\n\n  if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {\n    version = std::min(version, 1u);\n    static_cast<Tags*>(data)->control_ = static_cast<struct zriver_control_v1*>(\n        wl_registry_bind(registry, name, &zriver_control_v1_interface, version));\n  }\n\n  if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    version = std::min(version, 1u);\n    static_cast<Tags*>(data)->seat_ =\n        static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  /* Ignore event */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nTags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::AModule(config, \"tags\", id, false, false),\n      status_manager_{nullptr},\n      control_{nullptr},\n      seat_{nullptr},\n      bar_(bar),\n      box_{bar.orientation, 0},\n      output_status_{nullptr} {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  if (!status_manager_) {\n    spdlog::error(\"river_status_manager_v1 not advertised\");\n    return;\n  }\n\n  if (!control_) {\n    spdlog::error(\"river_control_v1 not advertised\");\n    return;\n  }\n\n  if (!seat_) {\n    spdlog::error(\"wl_seat not advertised\");\n    return;\n  }\n\n  box_.set_name(\"tags\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  // Default to 9 tags, cap at 32\n  const int num_tags =\n      config[\"num-tags\"].isUInt() ? std::min<int>(32, config_[\"num-tags\"].asUInt()) : 9;\n\n  const auto tag_labels = config[\"tag-labels\"];\n  const auto set_tags = config[\"set-tags\"];\n  const auto toggle_tags = config[\"toggle-tags\"];\n  for (int tag = 0; tag < num_tags; ++tag) {\n    if (tag_labels.isArray() && !tag_labels.empty()) {\n      buttons_.emplace_back(tag_labels[tag].asString());\n    } else {\n      // default name is the tag value\n      buttons_.emplace_back(std::to_string(tag + 1));\n    }\n\n    auto& button = buttons_[tag];\n    button.set_relief(Gtk::RELIEF_NONE);\n    box_.pack_start(button, false, false, 0);\n\n    if (!config_[\"disable-click\"].asBool()) {\n      if (set_tags.isArray() && !set_tags.empty())\n        button.signal_clicked().connect(sigc::bind(\n            sigc::mem_fun(*this, &Tags::handle_primary_clicked), set_tags[tag].asUInt()));\n      else\n        button.signal_clicked().connect(\n            sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), (1 << tag)));\n      if (toggle_tags.isArray() && !toggle_tags.empty())\n        button.signal_button_press_event().connect(sigc::bind(\n            sigc::mem_fun(*this, &Tags::handle_button_press), toggle_tags[tag].asUInt()));\n      else\n        button.signal_button_press_event().connect(\n            sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag)));\n    }\n    button.show();\n  }\n\n  box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show));\n}\n\nTags::~Tags() {\n  if (output_status_) {\n    zriver_output_status_v1_destroy(output_status_);\n  }\n\n  if (control_) {\n    zriver_control_v1_destroy(control_);\n  }\n\n  if (status_manager_) {\n    zriver_status_manager_v1_destroy(status_manager_);\n  }\n}\n\nvoid Tags::handle_show() {\n  if (!status_manager_) return;\n  struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n  output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output);\n  zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this);\n\n  zriver_status_manager_v1_destroy(status_manager_);\n  status_manager_ = nullptr;\n}\n\nvoid Tags::handle_primary_clicked(uint32_t tag) {\n  // Send river command to select tag on left mouse click\n  zriver_command_callback_v1* callback;\n  zriver_control_v1_add_argument(control_, \"set-focused-tags\");\n  zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());\n  callback = zriver_control_v1_run_command(control_, seat_);\n  zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);\n}\n\nbool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) {\n  if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {\n    // Send river command to toggle tag on right mouse click\n    zriver_command_callback_v1* callback;\n    zriver_control_v1_add_argument(control_, \"toggle-focused-tags\");\n    zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());\n    callback = zriver_control_v1_run_command(control_, seat_);\n    zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);\n  }\n  return true;\n}\n\nvoid Tags::handle_focused_tags(uint32_t tags) {\n  auto hide_vacant = config_[\"hide-vacant\"].asBool();\n  for (size_t i = 0; i < buttons_.size(); ++i) {\n    bool visible = buttons_[i].is_visible();\n    bool occupied = buttons_[i].get_style_context()->has_class(\"occupied\");\n    bool urgent = buttons_[i].get_style_context()->has_class(\"urgent\");\n    if ((1 << i) & tags) {\n      if (hide_vacant && !visible) {\n        buttons_[i].set_visible(true);\n      }\n      buttons_[i].get_style_context()->add_class(\"focused\");\n    } else {\n      if (hide_vacant && !(occupied || urgent)) {\n        buttons_[i].set_visible(false);\n      }\n      buttons_[i].get_style_context()->remove_class(\"focused\");\n    }\n  }\n}\n\nvoid Tags::handle_view_tags(struct wl_array* view_tags) {\n  uint32_t tags = 0;\n  auto view_tag = reinterpret_cast<uint32_t*>(view_tags->data);\n  auto end = view_tag + (view_tags->size / sizeof(uint32_t));\n  for (; view_tag < end; ++view_tag) {\n    tags |= *view_tag;\n  }\n  auto hide_vacant = config_[\"hide-vacant\"].asBool();\n  for (size_t i = 0; i < buttons_.size(); ++i) {\n    bool visible = buttons_[i].is_visible();\n    bool focused = buttons_[i].get_style_context()->has_class(\"focused\");\n    bool urgent = buttons_[i].get_style_context()->has_class(\"urgent\");\n    if ((1 << i) & tags) {\n      if (hide_vacant && !visible) {\n        buttons_[i].set_visible(true);\n      }\n      buttons_[i].get_style_context()->add_class(\"occupied\");\n    } else {\n      if (hide_vacant && !(focused || urgent)) {\n        buttons_[i].set_visible(false);\n      }\n      buttons_[i].get_style_context()->remove_class(\"occupied\");\n    }\n  }\n}\n\nvoid Tags::handle_urgent_tags(uint32_t tags) {\n  auto hide_vacant = config_[\"hide-vacant\"].asBool();\n  for (size_t i = 0; i < buttons_.size(); ++i) {\n    bool visible = buttons_[i].is_visible();\n    bool occupied = buttons_[i].get_style_context()->has_class(\"occupied\");\n    bool focused = buttons_[i].get_style_context()->has_class(\"focused\");\n    if ((1 << i) & tags) {\n      if (hide_vacant && !visible) {\n        buttons_[i].set_visible(true);\n      }\n      buttons_[i].get_style_context()->add_class(\"urgent\");\n    } else {\n      if (hide_vacant && !(occupied || focused)) {\n        buttons_[i].set_visible(false);\n      }\n      buttons_[i].get_style_context()->remove_class(\"urgent\");\n    }\n  }\n}\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "src/modules/river/window.cpp",
    "content": "#include \"modules/river/window.hpp\"\n\n#include <spdlog/spdlog.h>\n#include <wayland-client.h>\n\n#include <algorithm>\n\n#include \"client.hpp\"\n\nnamespace waybar::modules::river {\n\nstatic void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                const char* title) {\n  static_cast<Window*>(data)->handle_focused_view(title);\n}\n\nstatic void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                  struct wl_output* output) {\n  static_cast<Window*>(data)->handle_focused_output(output);\n}\n\nstatic void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                                    struct wl_output* output) {\n  static_cast<Window*>(data)->handle_unfocused_output(output);\n}\n\nstatic void listen_mode(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1,\n                        const char* mode) {\n  // This module doesn't care\n}\n\nstatic const zriver_seat_status_v1_listener seat_status_listener_impl{\n    .focused_output = listen_focused_output,\n    .unfocused_output = listen_unfocused_output,\n    .focused_view = listen_focused_view,\n    .mode = listen_mode,\n};\n\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 2);\n    static_cast<Window*>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1*>(\n        wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));\n  }\n\n  if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    version = std::min<uint32_t>(version, 1);\n    static_cast<Window*>(data)->seat_ =\n        static_cast<struct wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  /* Ignore event */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nWindow::Window(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::ALabel(config, \"window\", id, \"{}\", 30),\n      status_manager_{nullptr},\n      seat_{nullptr},\n      bar_(bar),\n      seat_status_{nullptr} {\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n\n  if (!status_manager_) {\n    spdlog::error(\"river_status_manager_v1 not advertised\");\n    return;\n  }\n\n  if (!seat_) {\n    spdlog::error(\"wl_seat not advertised\");\n    return;\n  }\n\n  label_.hide();  // hide the label until populated\n  ALabel::update();\n\n  seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);\n  zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);\n\n  zriver_status_manager_v1_destroy(status_manager_);\n}\n\nWindow::~Window() {\n  if (seat_status_) {\n    zriver_seat_status_v1_destroy(seat_status_);\n  }\n}\n\nvoid Window::handle_focused_view(const char* title) {\n  // don't change the label on unfocused outputs.\n  // this makes the current output report its currently focused view, and unfocused outputs will\n  // report their last focused views. when freshly starting the bar, unfocused outputs don't have a\n  // last focused view, and will get blank labels until they are brought into focus at least once.\n  if (focused_output_ != output_) return;\n\n  if (std::strcmp(title, \"\") == 0 || format_.empty()) {\n    label_.hide();  // hide empty labels or labels with empty format\n  } else {\n    label_.show();\n    auto text = fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw());\n    label_.set_markup(text);\n    if (tooltipEnabled()) {\n      label_.set_tooltip_markup(text);\n    }\n  }\n\n  ALabel::update();\n}\n\nvoid Window::handle_focused_output(struct wl_output* output) {\n  if (output_ == output) {  // if we focused the output this bar belongs to\n    label_.get_style_context()->add_class(\"focused\");\n    ALabel::update();\n  }\n  focused_output_ = output;\n}\n\nvoid Window::handle_unfocused_output(struct wl_output* output) {\n  if (output_ == output) {  // if we unfocused the output this bar belongs to\n    label_.get_style_context()->remove_class(\"focused\");\n    ALabel::update();\n  }\n}\n\n} /* namespace waybar::modules::river */\n"
  },
  {
    "path": "src/modules/simpleclock.cpp",
    "content": "#include \"modules/simpleclock.hpp\"\n\n#include <time.h>\n\nwaybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"clock\", id, \"{:%H:%M}\", 60) {\n  thread_ = [this] {\n    dp.emit();\n    auto now = std::chrono::system_clock::now();\n    /* difference with projected wakeup time */\n    auto diff = now.time_since_epoch() % interval_;\n    /* sleep until the next projected time */\n    thread_.sleep_for(interval_ - diff);\n  };\n}\n\nauto waybar::modules::Clock::update() -> void {\n  tzset();  // Update timezone information\n  auto now = std::chrono::system_clock::now();\n  auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));\n  auto text = fmt::format(fmt::runtime(format_), localtime);\n  label_.set_markup(text);\n\n  if (tooltipEnabled()) {\n    if (config_[\"tooltip-format\"].isString()) {\n      auto tooltip_format = config_[\"tooltip-format\"].asString();\n      auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime);\n      label_.set_tooltip_markup(tooltip_text);\n    } else {\n      label_.set_tooltip_markup(text);\n    }\n  }\n  // Call parent update\n  ALabel::update();\n}\n"
  },
  {
    "path": "src/modules/sndio.cpp",
    "content": "#include \"modules/sndio.hpp\"\n\n#include <fmt/format.h>\n#include <poll.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <cstdlib>\n\nnamespace waybar::modules {\n\nvoid ondesc(void* arg, struct sioctl_desc* d, int curval) {\n  auto self = static_cast<Sndio*>(arg);\n  if (d == NULL) {\n    // d is NULL when the list is done\n    return;\n  }\n  self->set_desc(d, curval);\n}\n\nvoid onval(void* arg, unsigned int addr, unsigned int val) {\n  auto self = static_cast<Sndio*>(arg);\n  self->put_val(addr, val);\n}\n\nauto Sndio::connect_to_sndio() -> void {\n  hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);\n  if (hdl_ == nullptr) {\n    throw std::runtime_error(\"sioctl_open() failed.\");\n  }\n\n  if (sioctl_ondesc(hdl_, ondesc, this) == 0) {\n    throw std::runtime_error(\"sioctl_ondesc() failed.\");\n  }\n\n  if (sioctl_onval(hdl_, onval, this) == 0) {\n    throw std::runtime_error(\"sioctl_onval() failed.\");\n  }\n\n  pfds_.reserve(sioctl_nfds(hdl_));\n}\n\nSndio::Sndio(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"sndio\", id, \"{volume}%\", 1, false, true),\n      hdl_(nullptr),\n      pfds_(0),\n      addr_(0),\n      volume_(0),\n      old_volume_(0),\n      maxval_(0),\n      muted_(false) {\n  connect_to_sndio();\n\n  event_box_.show();\n\n  event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK);\n  event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Sndio::handleScroll));\n  event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Sndio::handleToggle));\n\n  thread_ = [this] {\n    dp.emit();\n\n    int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN);\n    if (nfds == 0) {\n      throw std::runtime_error(\"sioctl_pollfd() failed.\");\n    }\n    while (poll(pfds_.data(), nfds, -1) < 0) {\n      if (errno != EINTR) {\n        throw std::runtime_error(\"poll() failed.\");\n      }\n    }\n\n    int revents = sioctl_revents(hdl_, pfds_.data());\n    if (revents & POLLHUP) {\n      spdlog::warn(\"sndio disconnected!\");\n      sioctl_close(hdl_);\n      hdl_ = nullptr;\n\n      // reconnection loop\n      while (thread_.isRunning()) {\n        try {\n          connect_to_sndio();\n        } catch (std::runtime_error const& e) {\n          // avoid leaking hdl_\n          if (hdl_) {\n            sioctl_close(hdl_);\n            hdl_ = nullptr;\n          }\n          // rate limiting for the retries\n          thread_.sleep_for(interval_);\n          continue;\n        }\n\n        spdlog::warn(\"sndio reconnected!\");\n        break;\n      }\n    }\n  };\n}\n\nSndio::~Sndio() { sioctl_close(hdl_); }\n\nauto Sndio::update() -> void {\n  auto format = format_;\n  unsigned int vol = (maxval_ > 0) ? static_cast<unsigned int>(100. * static_cast<double>(volume_) /\n                                                               static_cast<double>(maxval_))\n                                   : 0;\n\n  if (volume_ == 0) {\n    label_.get_style_context()->add_class(\"muted\");\n  } else {\n    label_.get_style_context()->remove_class(\"muted\");\n  }\n\n  auto text =\n      fmt::format(fmt::runtime(format), fmt::arg(\"volume\", vol), fmt::arg(\"raw_value\", volume_));\n  if (text.empty()) {\n    label_.hide();\n  } else {\n    label_.set_markup(text);\n    label_.show();\n  }\n\n  ALabel::update();\n}\n\nauto Sndio::set_desc(struct sioctl_desc* d, unsigned int val) -> void {\n  std::string name{d->func};\n  std::string node_name{d->node0.name};\n\n  if (name == \"level\" && node_name == \"output\" && d->type == SIOCTL_NUM) {\n    // store addr for output.level value, used in put_val\n    addr_ = d->addr;\n    maxval_ = d->maxval;\n    volume_ = val;\n  }\n}\n\nauto Sndio::put_val(unsigned int addr, unsigned int val) -> void {\n  if (addr == addr_) {\n    volume_ = val;\n  }\n}\n\nbool Sndio::handleScroll(GdkEventScroll* e) {\n  // change the volume only when no user provided\n  // events are configured\n  if (config_[\"on-scroll-up\"].isString() || config_[\"on-scroll-down\"].isString()) {\n    return AModule::handleScroll(e);\n  }\n\n  // only try to talk to sndio if connected\n  if (hdl_ == nullptr) return true;\n\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) {\n    return true;\n  }\n\n  int step = 5;\n  if (config_[\"scroll-step\"].isInt()) {\n    step = config_[\"scroll-step\"].asInt();\n  }\n\n  int new_volume = volume_;\n  if (muted_) {\n    new_volume = old_volume_;\n  }\n\n  if (dir == SCROLL_DIR::UP) {\n    new_volume += step;\n  } else if (dir == SCROLL_DIR::DOWN) {\n    new_volume -= step;\n  }\n  new_volume = std::clamp(new_volume, 0, static_cast<int>(maxval_));\n\n  // quits muted mode if volume changes\n  muted_ = false;\n\n  sioctl_setval(hdl_, addr_, new_volume);\n\n  return true;\n}\n\nbool Sndio::handleToggle(GdkEventButton* const& e) {\n  // toggle mute only when no user provided events are configured\n  if (config_[\"on-click\"].isString()) {\n    return AModule::handleToggle(e);\n  }\n\n  // only try to talk to sndio if connected\n  if (hdl_ == nullptr) return true;\n\n  muted_ = !muted_;\n  if (muted_) {\n    // store old volume to be able to restore it later\n    old_volume_ = volume_;\n    sioctl_setval(hdl_, addr_, 0);\n  } else {\n    sioctl_setval(hdl_, addr_, old_volume_);\n  }\n\n  return true;\n}\n\n} /* namespace waybar::modules */\n"
  },
  {
    "path": "src/modules/sni/host.cpp",
    "content": "#include \"modules/sni/host.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include \"util/scope_guard.hpp\"\n\nnamespace waybar::modules::SNI {\n\nHost::Host(const std::size_t id, const Json::Value& config, const Bar& bar,\n           const std::function<void(std::unique_ptr<Item>&)>& on_add,\n           const std::function<void(std::unique_ptr<Item>&)>& on_remove,\n           const std::function<void()>& on_update)\n    : bus_name_(\"org.kde.StatusNotifierHost-\" + std::to_string(getpid()) + \"-\" +\n                std::to_string(id)),\n      object_path_(\"/StatusNotifierHost/\" + std::to_string(id)),\n      bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_,\n                                       sigc::mem_fun(*this, &Host::busAcquired))),\n      config_(config),\n      bar_(bar),\n      on_add_(on_add),\n      on_remove_(on_remove),\n      on_update_(on_update) {}\n\nHost::~Host() {\n  if (bus_name_id_ > 0) {\n    Gio::DBus::unown_name(bus_name_id_);\n    bus_name_id_ = 0;\n  }\n  if (watcher_id_ > 0) {\n    Gio::DBus::unwatch_name(watcher_id_);\n    watcher_id_ = 0;\n  }\n  g_cancellable_cancel(cancellable_);\n  g_clear_object(&cancellable_);\n  g_clear_object(&watcher_);\n}\n\nvoid Host::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {\n  watcher_id_ = Gio::DBus::watch_name(conn, \"org.kde.StatusNotifierWatcher\",\n                                      sigc::mem_fun(*this, &Host::nameAppeared),\n                                      sigc::mem_fun(*this, &Host::nameVanished));\n}\n\nvoid Host::nameAppeared(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name,\n                        const Glib::ustring& name_owner) {\n  if (cancellable_ != nullptr) {\n    // TODO\n    return;\n  }\n  cancellable_ = g_cancellable_new();\n  sn_watcher_proxy_new(conn->gobj(), G_DBUS_PROXY_FLAGS_NONE, \"org.kde.StatusNotifierWatcher\",\n                       \"/StatusNotifierWatcher\", cancellable_, &Host::proxyReady, this);\n}\n\nvoid Host::nameVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring name) {\n  g_cancellable_cancel(cancellable_);\n  g_clear_object(&cancellable_);\n  g_clear_object(&watcher_);\n  clearItems();\n}\n\nvoid Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) {\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error != nullptr) {\n      g_error_free(error);\n    }\n  });\n  SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error);\n  if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {\n    spdlog::error(\"Host: {}\", error->message);\n    return;\n  }\n  auto host = static_cast<SNI::Host*>(data);\n  host->watcher_ = watcher;\n  if (error != nullptr) {\n    spdlog::error(\"Host: {}\", error->message);\n    return;\n  }\n  sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_,\n                                &Host::registerHost, data);\n}\n\nvoid Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) {\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error != nullptr) {\n      g_error_free(error);\n    }\n  });\n  sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error);\n  if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {\n    spdlog::error(\"Host: {}\", error->message);\n    return;\n  }\n  auto host = static_cast<SNI::Host*>(data);\n  if (error != nullptr) {\n    spdlog::error(\"Host: {}\", error->message);\n    return;\n  }\n  g_signal_connect(host->watcher_, \"item-registered\", G_CALLBACK(&Host::itemRegistered), data);\n  g_signal_connect(host->watcher_, \"item-unregistered\", G_CALLBACK(&Host::itemUnregistered), data);\n  auto items = sn_watcher_dup_registered_items(host->watcher_);\n  if (items != nullptr) {\n    for (uint32_t i = 0; items[i] != nullptr; i += 1) {\n      host->addRegisteredItem(items[i]);\n    }\n  }\n  g_strfreev(items);\n}\n\nvoid Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) {\n  auto host = static_cast<SNI::Host*>(data);\n  host->addRegisteredItem(service);\n}\n\nvoid Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) {\n  auto host = static_cast<SNI::Host*>(data);\n  auto [bus_name, object_path] = host->getBusNameAndObjectPath(service);\n  for (auto it = host->items_.begin(); it != host->items_.end(); ++it) {\n    if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) {\n      host->removeItem(it);\n      break;\n    }\n  }\n}\n\nvoid Host::itemReady(Item& item) {\n  auto it = std::find_if(items_.begin(), items_.end(),\n                         [&item](const auto& candidate) { return candidate.get() == &item; });\n  if (it != items_.end() && (*it)->isReady()) {\n    on_add_(*it);\n  }\n}\n\nvoid Host::itemInvalidated(Item& item) {\n  auto it = std::find_if(items_.begin(), items_.end(),\n                         [&item](const auto& candidate) { return candidate.get() == &item; });\n  if (it != items_.end()) {\n    removeItem(it);\n  }\n}\n\nvoid Host::removeItem(std::vector<std::unique_ptr<Item>>::iterator it) {\n  if ((*it)->isReady()) {\n    on_remove_(*it);\n  }\n  items_.erase(it);\n}\n\nvoid Host::clearItems() {\n  bool removed_ready_item = false;\n  for (auto& item : items_) {\n    if (item->isReady()) {\n      on_remove_(item);\n      removed_ready_item = true;\n    }\n  }\n  bool had_items = !items_.empty();\n  items_.clear();\n  if (had_items && !removed_ready_item) {\n    on_update_();\n  }\n}\n\nstd::tuple<std::string, std::string> Host::getBusNameAndObjectPath(const std::string service) {\n  auto it = service.find('/');\n  if (it != std::string::npos) {\n    return {service.substr(0, it), service.substr(it)};\n  }\n  return {service, \"/StatusNotifierItem\"};\n}\n\nvoid Host::addRegisteredItem(const std::string& service) {\n  std::string bus_name, object_path;\n  std::tie(bus_name, object_path) = getBusNameAndObjectPath(service);\n  auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) {\n    return bus_name == item->bus_name && object_path == item->object_path;\n  });\n  if (it == items_.end()) {\n    items_.emplace_back(new Item(\n        bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); },\n        [this](Item& item) { itemInvalidated(item); }, on_update_));\n  }\n}\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "src/modules/sni/item.cpp",
    "content": "#include \"modules/sni/item.hpp\"\n\n#include <gdkmm/general.h>\n#include <glibmm/main.h>\n#include <gtkmm/tooltip.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <filesystem>\n#include <fstream>\n#include <map>\n\n#include \"gdk/gdk.h\"\n#include \"modules/sni/icon_manager.hpp\"\n#include \"util/format.hpp\"\n#include \"util/gtk_icon.hpp\"\n\ntemplate <>\nstruct fmt::formatter<Glib::VariantBase> : formatter<std::string> {\n  bool is_printable(const Glib::VariantBase& value) const {\n    auto type = value.get_type_string();\n    /* Print only primitive (single character excluding 'v') and short complex types */\n    return (type.length() == 1 && islower(type[0]) && type[0] != 'v') || value.get_size() <= 32;\n  }\n\n  template <typename FormatContext>\n  auto format(const Glib::VariantBase& value, FormatContext& ctx) const {\n    if (is_printable(value)) {\n      return formatter<std::string>::format(static_cast<std::string>(value.print()), ctx);\n    } else {\n      return formatter<std::string>::format(value.get_type_string(), ctx);\n    }\n  }\n};\n\nnamespace waybar::modules::SNI {\n\nstatic const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;\nstatic const unsigned UPDATE_DEBOUNCE_TIME = 10;\n\nItem::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar,\n           const std::function<void(Item&)>& on_ready,\n           const std::function<void(Item&)>& on_invalidate, const std::function<void()>& on_updated)\n    : bus_name(bn),\n      object_path(op),\n      icon_size(16),\n      effective_icon_size(0),\n      icon_theme(Gtk::IconTheme::create()),\n      bar_(bar),\n      on_ready_(on_ready),\n      on_invalidate_(on_invalidate),\n      on_updated_(on_updated) {\n  if (config[\"icon-size\"].isUInt()) {\n    icon_size = config[\"icon-size\"].asUInt();\n  }\n  if (config[\"smooth-scrolling-threshold\"].isNumeric()) {\n    scroll_threshold_ = config[\"smooth-scrolling-threshold\"].asDouble();\n  }\n  if (config[\"show-passive-items\"].isBool()) {\n    show_passive_ = config[\"show-passive-items\"].asBool();\n  }\n\n  auto& window = const_cast<Bar&>(bar).window;\n  window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Item::onConfigure));\n  event_box.add(image);\n  event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n  event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));\n  event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));\n  event_box.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Item::handleMouseEnter));\n  event_box.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Item::handleMouseLeave));\n  // initial visibility\n  event_box.show_all();\n  event_box.set_visible(show_passive_);\n\n  cancellable_ = Gio::Cancellable::create();\n\n  auto interface = Glib::wrap(sn_item_interface_info(), true);\n  Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name, object_path,\n                                   SNI_INTERFACE_NAME, sigc::mem_fun(*this, &Item::proxyReady),\n                                   cancellable_, interface);\n}\n\nItem::~Item() {\n  if (this->gtk_menu != nullptr) {\n    this->gtk_menu->popdown();\n    this->gtk_menu->detach();\n  }\n  if (this->dbus_menu != nullptr) {\n    g_object_weak_unref(G_OBJECT(this->dbus_menu), (GWeakNotify)onMenuDestroyed, this);\n    this->dbus_menu = nullptr;\n  }\n}\n\nbool Item::isReady() const { return ready_; }\n\nbool Item::handleMouseEnter(GdkEventCrossing* const& e) {\n  event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  return false;\n}\n\nbool Item::handleMouseLeave(GdkEventCrossing* const& e) {\n  event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n  return false;\n}\n\nvoid Item::onConfigure(GdkEventConfigure* ev) { this->updateImage(); }\n\nvoid Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {\n  try {\n    this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);\n    /* Properties are already cached during object creation */\n    auto cached_properties = this->proxy_->get_cached_property_names();\n    for (const auto& name : cached_properties) {\n      Glib::VariantBase value;\n      this->proxy_->get_cached_property(value, name);\n      setProperty(name, value);\n    }\n\n    this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));\n\n    if (this->id.empty() || this->category.empty()) {\n      spdlog::error(\"Invalid Status Notifier Item: {}, {}\", bus_name, object_path);\n      invalidate();\n      return;\n    }\n    this->updateImage();\n    setReady();\n\n  } catch (const Glib::Error& err) {\n    spdlog::error(\"Failed to create DBus Proxy for {} {}: {}\", bus_name, object_path, err.what());\n    invalidate();\n  } catch (const std::exception& err) {\n    spdlog::error(\"Failed to create DBus Proxy for {} {}: {}\", bus_name, object_path, err.what());\n    invalidate();\n  }\n}\n\ntemplate <typename T>\nT get_variant(const Glib::VariantBase& value) {\n  return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();\n}\n\ntemplate <>\nToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {\n  ToolTip result;\n  // Unwrap (sa(iiay)ss)\n  auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);\n  result.icon_name = get_variant<Glib::ustring>(container.get_child(0));\n  result.text = get_variant<Glib::ustring>(container.get_child(2));\n  auto description = get_variant<Glib::ustring>(container.get_child(3));\n  if (!description.empty()) {\n    auto escapedDescription = Glib::Markup::escape_text(description);\n    result.text = fmt::format(\"<b>{}</b>\\n{}\", result.text, escapedDescription);\n  }\n  return result;\n}\n\nvoid Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {\n  try {\n    spdlog::trace(\"Set tray item property: {}.{} = {}\", id.empty() ? bus_name : id, name, value);\n\n    if (name == \"Category\") {\n      category = get_variant<std::string>(value);\n    } else if (name == \"Id\") {\n      id = get_variant<std::string>(value);\n\n      /*\n       * HACK: Electron apps seem to have the same ID, but tooltip seems correct, so use that as ID\n       * to pass as the custom icon option. I'm avoiding being disruptive and setting that to the ID\n       * itself as I've no idea what this would affect.\n       * The tooltip text is converted to lowercase since that's what (most?) themes expect?\n       * I still haven't found a way for it to pick from theme automatically, although\n       * it might be my theme.\n       */\n      if (id == \"chrome_status_icon_1\") {\n        Glib::VariantBase value;\n        this->proxy_->get_cached_property(value, \"ToolTip\");\n        tooltip = get_variant<ToolTip>(value);\n        if (!tooltip.text.empty()) {\n          setCustomIcon(tooltip.text.lowercase());\n        }\n      } else {\n        setCustomIcon(id);\n      }\n    } else if (name == \"Title\") {\n      title = get_variant<std::string>(value);\n      if (tooltip.text.empty()) {\n        event_box.set_tooltip_markup(title);\n      }\n    } else if (name == \"Status\") {\n      setStatus(get_variant<Glib::ustring>(value));\n    } else if (name == \"IconName\") {\n      icon_name = get_variant<std::string>(value);\n    } else if (name == \"IconPixmap\") {\n      icon_pixmap = this->extractPixBuf(value.gobj());\n    } else if (name == \"OverlayIconName\") {\n      overlay_icon_name = get_variant<std::string>(value);\n    } else if (name == \"OverlayIconPixmap\") {\n      overlay_icon_pixmap = extractPixBuf(value.gobj());\n    } else if (name == \"AttentionIconName\") {\n      attention_icon_name = get_variant<std::string>(value);\n    } else if (name == \"AttentionIconPixmap\") {\n      attention_icon_pixmap = extractPixBuf(value.gobj());\n    } else if (name == \"AttentionMovieName\") {\n      attention_movie_name = get_variant<std::string>(value);\n    } else if (name == \"ToolTip\") {\n      tooltip = get_variant<ToolTip>(value);\n      if (!tooltip.text.empty()) {\n        event_box.set_tooltip_markup(tooltip.text);\n      }\n    } else if (name == \"IconThemePath\") {\n      icon_theme_path = get_variant<std::string>(value);\n      if (!icon_theme_path.empty()) {\n        icon_theme->set_search_path({icon_theme_path});\n      }\n    } else if (name == \"Menu\") {\n      menu = get_variant<std::string>(value);\n      makeMenu();\n    } else if (name == \"ItemIsMenu\") {\n      item_is_menu = get_variant<bool>(value);\n    }\n  } catch (const Glib::Error& err) {\n    spdlog::warn(\"Failed to set tray item property: {}.{}, value = {}, err = {}\",\n                 id.empty() ? bus_name : id, name, value, err.what());\n  } catch (const std::exception& err) {\n    spdlog::warn(\"Failed to set tray item property: {}.{}, value = {}, err = {}\",\n                 id.empty() ? bus_name : id, name, value, err.what());\n  }\n}\n\nvoid Item::setStatus(const Glib::ustring& value) {\n  status_ = value.lowercase();\n  event_box.set_visible(show_passive_ || status_.compare(\"passive\") != 0);\n\n  auto style = event_box.get_style_context();\n  for (const auto& class_name : style->list_classes()) {\n    style->remove_class(class_name);\n  }\n  auto css_class = status_;\n  if (css_class.compare(\"needsattention\") == 0) {\n    // convert status to dash-case for CSS\n    css_class = \"needs-attention\";\n  }\n  style->add_class(css_class);\n  on_updated_();\n}\n\nvoid Item::setReady() {\n  if (ready_) {\n    return;\n  }\n  ready_ = true;\n  on_ready_(*this);\n}\n\nvoid Item::invalidate() {\n  if (ready_) {\n    ready_ = false;\n  }\n  on_invalidate_(*this);\n}\n\nvoid Item::setCustomIcon(const std::string& id) {\n  spdlog::debug(\"SNI tray id: {}\", id);\n\n  std::string custom_icon = IconManager::instance().getIconForApp(id);\n  if (!custom_icon.empty()) {\n    if (std::filesystem::exists(custom_icon)) {\n      try {\n        Glib::RefPtr<Gdk::Pixbuf> custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon);\n        icon_name = \"\";  // icon_name has priority over pixmap\n        icon_pixmap = custom_pixbuf;\n      } catch (const Glib::Error& e) {\n        spdlog::error(\"Failed to load custom icon {}: {}\", custom_icon, e.what());\n      }\n    } else {  // if file doesn't exist it's most likely an icon_name\n      icon_name = custom_icon;\n    }\n  }\n}\n\nvoid Item::getUpdatedProperties() {\n  auto params = Glib::VariantContainerBase::create_tuple(\n      {Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});\n  proxy_->call(\"org.freedesktop.DBus.Properties.GetAll\",\n               sigc::mem_fun(*this, &Item::processUpdatedProperties), params);\n};\n\nvoid Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {\n  try {\n    auto result = proxy_->call_finish(_result);\n    // extract \"a{sv}\" from VariantContainerBase\n    Glib::Variant<std::map<Glib::ustring, Glib::VariantBase>> properties_variant;\n    result.get_child(properties_variant);\n    auto properties = properties_variant.get();\n\n    for (const auto& [name, value] : properties) {\n      if (update_pending_.count(name.raw())) {\n        setProperty(name, const_cast<Glib::VariantBase&>(value));\n      }\n    }\n\n    this->updateImage();\n  } catch (const Glib::Error& err) {\n    spdlog::warn(\"Failed to update properties: {}\", err.what());\n  } catch (const std::exception& err) {\n    spdlog::warn(\"Failed to update properties: {}\", err.what());\n  }\n  update_pending_.clear();\n}\n\n/**\n * Mapping from a signal name to a set of possibly changed properties.\n * Commented signals are not handled by the tray module at the moment.\n */\nstatic const std::map<std::string_view, std::set<std::string_view>> signal2props = {\n    {\"NewTitle\", {\"Title\"}},\n    {\"NewIcon\", {\"IconName\", \"IconPixmap\"}},\n    {\"NewAttentionIcon\", {\"AttentionIconName\", \"AttentionIconPixmap\", \"AttentionMovieName\"}},\n    {\"NewOverlayIcon\", {\"OverlayIconName\", \"OverlayIconPixmap\"}},\n    {\"NewIconThemePath\", {\"IconThemePath\"}},\n    {\"NewToolTip\", {\"ToolTip\"}},\n    {\"NewStatus\", {\"Status\"}},\n    // {\"XAyatanaNewLabel\", {\"XAyatanaLabel\"}},\n};\n\nvoid Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                    const Glib::VariantContainerBase& arguments) {\n  spdlog::trace(\"Tray item '{}' got signal {}\", id, signal_name);\n  auto changed = signal2props.find(signal_name.raw());\n  if (changed != signal2props.end()) {\n    if (update_pending_.empty()) {\n      /* Debounce signals and schedule update of all properties.\n       * Based on behavior of Plasma dataengine for StatusNotifierItem.\n       */\n      Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),\n                                          UPDATE_DEBOUNCE_TIME);\n    }\n    update_pending_.insert(changed->second.begin(), changed->second.end());\n  }\n}\n\nstatic void pixbuf_data_deleter(const guint8* data) { g_free((void*)data); }\n\nGlib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {\n  GVariantIter* it;\n  g_variant_get(variant, \"a(iiay)\", &it);\n  if (it == nullptr) {\n    return Glib::RefPtr<Gdk::Pixbuf>{};\n  }\n  GVariant* val;\n  gint lwidth = 0;\n  gint lheight = 0;\n  gint width;\n  gint height;\n  guchar* array = nullptr;\n  while (g_variant_iter_loop(it, \"(ii@ay)\", &width, &height, &val)) {\n    if (width > 0 && height > 0 && val != nullptr && width * height > lwidth * lheight) {\n      auto size = g_variant_get_size(val);\n      /* Sanity check */\n      if (size == 4U * width * height) {\n        /* Find the largest image */\n        gconstpointer data = g_variant_get_data(val);\n        if (data != nullptr) {\n          if (array != nullptr) {\n            g_free(array);\n          }\n          // We must allocate our own array because the data from GVariant is read-only\n          // and we need to modify it to convert ARGB to RGBA.\n          array = static_cast<guchar*>(g_malloc(size));\n\n          // Copy and convert ARGB to RGBA in one pass to avoid g_memdup2 overhead\n          const guchar* src = static_cast<const guchar*>(data);\n          for (gsize i = 0; i < size; i += 4) {\n            guchar alpha = src[i];\n            array[i] = src[i + 1];\n            array[i + 1] = src[i + 2];\n            array[i + 2] = src[i + 3];\n            array[i + 3] = alpha;\n          }\n\n          lwidth = width;\n          lheight = height;\n        }\n      }\n    }\n  }\n  g_variant_iter_free(it);\n  if (array != nullptr) {\n    return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, true, 8, lwidth,\n                                         lheight, 4 * lwidth, &pixbuf_data_deleter);\n  }\n  return Glib::RefPtr<Gdk::Pixbuf>{};\n}\n\nvoid Item::updateImage() {\n  auto pixbuf = getIconPixbuf();\n  if (!pixbuf) return;\n  auto scaled_icon_size = getScaledIconSize();\n\n  // If the loaded icon is not square, assume that the icon height should match the\n  // requested icon size, but the width is allowed to be different. As such, if the\n  // height of the image does not match the requested icon size, resize the icon such that\n  // the aspect ratio is maintained, but the height matches the requested icon size.\n  if (pixbuf->get_height() > 0 && pixbuf->get_height() != scaled_icon_size) {\n    int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();\n    pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);\n  }\n\n  pixbuf = overlayPixbufs(pixbuf, getOverlayIconPixbuf());\n\n  auto surface =\n      Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window());\n  image.set(surface);\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {\n  if (status_ == \"needsattention\") {\n    if (auto attention_pixbuf = getAttentionIconPixbuf()) {\n      return attention_pixbuf;\n    }\n  }\n\n  if (auto pixbuf = loadIconFromNameOrFile(icon_name, true)) {\n    return pixbuf;\n  }\n\n  if (icon_pixmap) {\n    return icon_pixmap;\n  }\n\n  if (icon_name.empty()) {\n    spdlog::error(\"Item '{}': No icon name or pixmap given.\", id);\n  } else {\n    spdlog::error(\"Item '{}': Could not find an icon named '{}' and no pixmap given.\", id,\n                  icon_name);\n  }\n\n  return getIconByName(\"image-missing\", getScaledIconSize());\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::getAttentionIconPixbuf() {\n  if (auto pixbuf = loadIconFromNameOrFile(attention_icon_name, false)) {\n    return pixbuf;\n  }\n  if (auto pixbuf = loadIconFromNameOrFile(attention_movie_name, false)) {\n    return pixbuf;\n  }\n  return attention_icon_pixmap;\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::getOverlayIconPixbuf() {\n  if (auto pixbuf = loadIconFromNameOrFile(overlay_icon_name, false)) {\n    return pixbuf;\n  }\n  return overlay_icon_pixmap;\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::loadIconFromNameOrFile(const std::string& name, bool log_failure) {\n  if (name.empty()) {\n    return {};\n  }\n\n  try {\n    std::ifstream temp(name);\n    if (temp.is_open()) {\n      return Gdk::Pixbuf::create_from_file(name);\n    }\n  } catch (const Glib::Error& e) {\n    if (log_failure) {\n      spdlog::warn(\"Item '{}': {}\", id, static_cast<std::string>(e.what()));\n    }\n  }\n\n  try {\n    return getIconByName(name, getScaledIconSize());\n  } catch (const Glib::Error& e) {\n    if (log_failure) {\n      spdlog::trace(\"Item '{}': {}\", id, static_cast<std::string>(e.what()));\n    }\n  }\n\n  return {};\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::overlayPixbufs(const Glib::RefPtr<Gdk::Pixbuf>& base,\n                                               const Glib::RefPtr<Gdk::Pixbuf>& overlay) {\n  if (!base || !overlay) {\n    return base;\n  }\n\n  auto composed = base->copy();\n  if (!composed) {\n    return base;\n  }\n\n  int overlay_target_size =\n      std::max(1, std::min(composed->get_width(), composed->get_height()) / 2);\n  auto scaled_overlay = overlay->scale_simple(overlay_target_size, overlay_target_size,\n                                              Gdk::InterpType::INTERP_BILINEAR);\n  if (!scaled_overlay) {\n    return composed;\n  }\n\n  int dest_x = std::max(0, composed->get_width() - scaled_overlay->get_width());\n  int dest_y = std::max(0, composed->get_height() - scaled_overlay->get_height());\n  scaled_overlay->composite(composed, dest_x, dest_y, scaled_overlay->get_width(),\n                            scaled_overlay->get_height(), dest_x, dest_y, 1.0, 1.0,\n                            Gdk::InterpType::INTERP_BILINEAR, 255);\n  return composed;\n}\n\nGlib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {\n  if (!icon_theme_path.empty()) {\n    auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size,\n                                             Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);\n    if (icon_info) {\n      bool is_sym = false;\n      return icon_info.load_symbolic(event_box.get_style_context(), is_sym);\n    }\n  }\n  return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size,\n                                               Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE,\n                                               event_box.get_style_context());\n}\n\ndouble Item::getScaledIconSize() {\n  // apply the scale factor from the Gtk window to the requested icon size\n  return icon_size * image.get_scale_factor();\n}\n\nvoid Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {\n  if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {\n    self->gtk_menu = nullptr;\n    self->dbus_menu = nullptr;\n  }\n}\n\nvoid Item::makeMenu() {\n  if (gtk_menu == nullptr && !menu.empty()) {\n    dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data());\n    if (dbus_menu != nullptr) {\n      g_object_ref_sink(G_OBJECT(dbus_menu));\n      g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this);\n      gtk_menu = Glib::wrap(GTK_MENU(dbus_menu));\n      gtk_menu->attach_to_widget(event_box);\n    }\n  }\n  // Manually reset prelight to make sure the tray item doesn't stay in a hover state even though\n  // the menu is focused\n  event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);\n}\n\nbool Item::handleClick(GdkEventButton* const& ev) {\n  if (!proxy_) {\n    return false;\n  }\n  auto parameters = Glib::VariantContainerBase::create_tuple(\n      {Glib::Variant<int>::create(ev->x_root + bar_.x_global),\n       Glib::Variant<int>::create(ev->y_root + bar_.y_global)});\n  if ((ev->button == 1 && item_is_menu) || ev->button == 3) {\n    makeMenu();\n    if (gtk_menu != nullptr) {\n#if GTK_CHECK_VERSION(3, 22, 0)\n      gtk_menu->popup_at_pointer(reinterpret_cast<GdkEvent*>(ev));\n#else\n      gtk_menu->popup(ev->button, ev->time);\n#endif\n      return true;\n    } else {\n      proxy_->call(\"ContextMenu\", parameters);\n      return true;\n    }\n  } else if (ev->button == 1) {\n    proxy_->call(\"Activate\", parameters);\n    return true;\n  } else if (ev->button == 2) {\n    proxy_->call(\"SecondaryActivate\", parameters);\n    return true;\n  }\n  return false;\n}\n\nbool Item::handleScroll(GdkEventScroll* const& ev) {\n  if (!proxy_) {\n    return false;\n  }\n  int dx = 0, dy = 0;\n  switch (ev->direction) {\n    case GDK_SCROLL_UP:\n      dy = -1;\n      break;\n    case GDK_SCROLL_DOWN:\n      dy = 1;\n      break;\n    case GDK_SCROLL_LEFT:\n      dx = -1;\n      break;\n    case GDK_SCROLL_RIGHT:\n      dx = 1;\n      break;\n    case GDK_SCROLL_SMOOTH:\n      distance_scrolled_x_ += ev->delta_x;\n      distance_scrolled_y_ += ev->delta_y;\n      // check against the configured threshold and ensure that the absolute value >= 1\n      if (distance_scrolled_x_ > scroll_threshold_) {\n        dx = (int)lround(std::max(distance_scrolled_x_, 1.0));\n        distance_scrolled_x_ = 0;\n      } else if (distance_scrolled_x_ < -scroll_threshold_) {\n        dx = (int)lround(std::min(distance_scrolled_x_, -1.0));\n        distance_scrolled_x_ = 0;\n      }\n      if (distance_scrolled_y_ > scroll_threshold_) {\n        dy = (int)lround(std::max(distance_scrolled_y_, 1.0));\n        distance_scrolled_y_ = 0;\n      } else if (distance_scrolled_y_ < -scroll_threshold_) {\n        dy = (int)lround(std::min(distance_scrolled_y_, -1.0));\n        distance_scrolled_y_ = 0;\n      }\n      break;\n  }\n  if (dx != 0) {\n    auto parameters = Glib::VariantContainerBase::create_tuple(\n        {Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create(\"horizontal\")});\n    proxy_->call(\"Scroll\", parameters);\n  }\n  if (dy != 0) {\n    auto parameters = Glib::VariantContainerBase::create_tuple(\n        {Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create(\"vertical\")});\n    proxy_->call(\"Scroll\", parameters);\n  }\n  return true;\n}\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "src/modules/sni/tray.cpp",
    "content": "#include \"modules/sni/tray.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n\n#include \"modules/sni/icon_manager.hpp\"\n\nnamespace waybar::modules::SNI {\n\nTray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AModule(config, \"tray\", id),\n      box_(bar.orientation, 0),\n      watcher_(SNI::Watcher::getInstance()),\n      host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),\n            std::bind(&Tray::onRemove, this, std::placeholders::_1),\n            std::bind(&Tray::queueUpdate, this)) {\n  box_.set_name(\"tray\");\n  event_box_.add(box_);\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  if (config_[\"spacing\"].isUInt()) {\n    box_.set_spacing(config_[\"spacing\"].asUInt());\n  }\n  if (config[\"show-passive-items\"].isBool()) {\n    show_passive_ = config[\"show-passive-items\"].asBool();\n  }\n  nb_hosts_ += 1;\n  if (config_[\"icons\"].isObject()) {\n    IconManager::instance().setIconsConfig(config_[\"icons\"]);\n  }\n  dp.emit();\n}\n\nvoid Tray::queueUpdate() { dp.emit(); }\n\nvoid Tray::onAdd(std::unique_ptr<Item>& item) {\n  if (config_[\"reverse-direction\"].isBool() && config_[\"reverse-direction\"].asBool()) {\n    box_.pack_end(item->event_box);\n  } else {\n    box_.pack_start(item->event_box);\n  }\n  dp.emit();\n}\n\nvoid Tray::onRemove(std::unique_ptr<Item>& item) {\n  box_.remove(item->event_box);\n  dp.emit();\n}\n\nauto Tray::update() -> void {\n  // Show tray only when items are available\n  std::vector<Gtk::Widget*> children = box_.get_children();\n  if (show_passive_) {\n    event_box_.set_visible(!children.empty());\n  } else {\n    event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) {\n      return child->get_style_context()->has_class(\"passive\");\n    }));\n  }\n\n  // Call parent update\n  AModule::update();\n}\n\n}  // namespace waybar::modules::SNI\n"
  },
  {
    "path": "src/modules/sni/watcher.cpp",
    "content": "#include \"modules/sni/watcher.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include \"util/scope_guard.hpp\"\n\nusing namespace waybar::modules::SNI;\n\nWatcher::Watcher()\n    : bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION,\n                                       \"org.kde.StatusNotifierWatcher\",\n                                       sigc::mem_fun(*this, &Watcher::busAcquired),\n                                       Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(),\n                                       Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |\n                                           Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)),\n      watcher_(sn_watcher_skeleton_new()) {}\n\nWatcher::~Watcher() {\n  if (hosts_ != nullptr) {\n    g_slist_free_full(hosts_, gfWatchFree);\n    hosts_ = nullptr;\n  }\n  if (items_ != nullptr) {\n    g_slist_free_full(items_, gfWatchFree);\n    items_ = nullptr;\n  }\n  Gio::DBus::unown_name(bus_name_id_);\n  auto iface = G_DBUS_INTERFACE_SKELETON(watcher_);\n  g_dbus_interface_skeleton_unexport(iface);\n}\n\nvoid Watcher::busAcquired(const Glib::RefPtr<Gio::DBus::Connection>& conn, Glib::ustring name) {\n  GError* error = nullptr;\n  waybar::util::ScopeGuard error_deleter([&error]() {\n    if (error) {\n      g_error_free(error);\n    }\n  });\n  g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(watcher_), conn->gobj(),\n                                   \"/StatusNotifierWatcher\", &error);\n  if (error != nullptr) {\n    // Don't print an error when a watcher is already present\n    if (error->code != 2) {\n      spdlog::error(\"Watcher: {}\", error->message);\n    }\n    return;\n  }\n  g_signal_connect_swapped(watcher_, \"handle-register-item\",\n                           G_CALLBACK(&Watcher::handleRegisterItem), this);\n  g_signal_connect_swapped(watcher_, \"handle-register-host\",\n                           G_CALLBACK(&Watcher::handleRegisterHost), this);\n}\n\ngboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invocation,\n                                     const gchar* service) {\n  const gchar* bus_name = service;\n  const gchar* object_path = \"/StatusNotifierHost\";\n\n  if (*service == '/') {\n    bus_name = g_dbus_method_invocation_get_sender(invocation);\n    object_path = service;\n  }\n  if (g_dbus_is_name(bus_name) == FALSE) {\n    g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,\n                                          \"D-Bus bus name '%s' is not valid\", bus_name);\n    return TRUE;\n  }\n  auto watch = gfWatchFind(obj->hosts_, bus_name, object_path);\n  if (watch != nullptr) {\n    g_warning(\"Status Notifier Host with bus name '%s' and object path '%s' is already registered\",\n              bus_name, object_path);\n    sn_watcher_complete_register_host(obj->watcher_, invocation);\n    return TRUE;\n  }\n  watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj);\n  obj->hosts_ = g_slist_prepend(obj->hosts_, watch);\n  if (!sn_watcher_get_is_host_registered(obj->watcher_)) {\n    sn_watcher_set_is_host_registered(obj->watcher_, TRUE);\n    sn_watcher_emit_host_registered(obj->watcher_);\n  }\n  sn_watcher_complete_register_host(obj->watcher_, invocation);\n  return TRUE;\n}\n\ngboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invocation,\n                                     const gchar* service) {\n  const gchar* bus_name = service;\n  const gchar* object_path = \"/StatusNotifierItem\";\n\n  if (*service == '/') {\n    bus_name = g_dbus_method_invocation_get_sender(invocation);\n    object_path = service;\n  }\n  if (g_dbus_is_name(bus_name) == FALSE) {\n    g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,\n                                          \"D-Bus bus name '%s' is not valid\", bus_name);\n    return TRUE;\n  }\n  auto watch = gfWatchFind(obj->items_, bus_name, object_path);\n  if (watch != nullptr) {\n    spdlog::debug(\"Ignoring duplicate Status Notifier Item registration for '{}' at '{}'\", bus_name,\n                  object_path);\n    sn_watcher_complete_register_item(obj->watcher_, invocation);\n    return TRUE;\n  }\n  watch = gfWatchNew(GF_WATCH_TYPE_ITEM, service, bus_name, object_path, obj);\n  obj->items_ = g_slist_prepend(obj->items_, watch);\n  obj->updateRegisteredItems(obj->watcher_);\n  gchar* tmp = g_strdup_printf(\"%s%s\", bus_name, object_path);\n  sn_watcher_emit_item_registered(obj->watcher_, tmp);\n  g_free(tmp);\n  sn_watcher_complete_register_item(obj->watcher_, invocation);\n  return TRUE;\n}\n\nWatcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name,\n                                       const gchar* object_path) {\n  for (GSList* l = list; l != nullptr; l = g_slist_next(l)) {\n    auto watch = static_cast<GfWatch*>(l->data);\n    if (g_strcmp0(watch->bus_name, bus_name) == 0 &&\n        g_strcmp0(watch->object_path, object_path) == 0) {\n      return watch;\n    }\n  }\n  return nullptr;\n}\n\nvoid Watcher::gfWatchFree(gpointer data) {\n  auto watch = static_cast<GfWatch*>(data);\n\n  if (watch->watch_id > 0) {\n    g_bus_unwatch_name(watch->watch_id);\n  }\n\n  g_free(watch->service);\n  g_free(watch->bus_name);\n  g_free(watch->object_path);\n\n  g_free(watch);\n}\n\nWatcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, const gchar* bus_name,\n                                      const gchar* object_path, Watcher* watcher) {\n  GfWatch* watch = g_new0(GfWatch, 1);\n  watch->type = type;\n  watch->watcher = watcher;\n  watch->service = g_strdup(service);\n  watch->bus_name = g_strdup(bus_name);\n  watch->object_path = g_strdup(object_path);\n  watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, bus_name, G_BUS_NAME_WATCHER_FLAGS_NONE,\n                                     nullptr, &Watcher::nameVanished, watch, nullptr);\n  return watch;\n}\n\nvoid Watcher::nameVanished(GDBusConnection* connection, const char* name, gpointer data) {\n  auto watch = static_cast<GfWatch*>(data);\n  if (watch->type == GF_WATCH_TYPE_HOST) {\n    watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch);\n    if (watch->watcher->hosts_ == nullptr) {\n      sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE);\n      sn_watcher_emit_host_unregistered(watch->watcher->watcher_);\n    }\n  } else if (watch->type == GF_WATCH_TYPE_ITEM) {\n    watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch);\n    watch->watcher->updateRegisteredItems(watch->watcher->watcher_);\n    gchar* tmp = g_strdup_printf(\"%s%s\", watch->bus_name, watch->object_path);\n    sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp);\n    g_free(tmp);\n  }\n  gfWatchFree(watch);\n}\n\nvoid Watcher::updateRegisteredItems(SnWatcher* obj) {\n  GVariantBuilder builder;\n  g_variant_builder_init(&builder, G_VARIANT_TYPE(\"as\"));\n  for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) {\n    auto watch = static_cast<GfWatch*>(l->data);\n    gchar* item = g_strdup_printf(\"%s%s\", watch->bus_name, watch->object_path);\n    g_variant_builder_add(&builder, \"s\", item);\n    g_free(item);\n  }\n  GVariant* variant = g_variant_builder_end(&builder);\n  const gchar** items = g_variant_get_strv(variant, nullptr);\n  sn_watcher_set_registered_items(obj, items);\n  g_variant_unref(variant);\n  g_free(items);\n}\n"
  },
  {
    "path": "src/modules/sway/bar.cpp",
    "content": "#include \"modules/sway/bar.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <sstream>\n#include <stdexcept>\n\n#include \"bar.hpp\"\n#include \"modules/sway/ipc/ipc.hpp\"\n\nnamespace waybar::modules::sway {\n\nBarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {\n  {\n    sigc::connection handle =\n        ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));\n    ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);\n\n    handle.disconnect();\n  }\n\n  Json::Value subscribe_events{Json::arrayValue};\n  subscribe_events.append(\"bar_state_update\");\n  subscribe_events.append(\"barconfig_update\");\n\n  bool has_mode = isModuleEnabled(\"sway/mode\");\n  bool has_workspaces = isModuleEnabled(\"sway/workspaces\");\n\n  if (has_mode) {\n    subscribe_events.append(\"mode\");\n  }\n  if (has_workspaces) {\n    subscribe_events.append(\"workspace\");\n  }\n  if (has_mode || has_workspaces) {\n    subscribe_events.append(\"binding\");\n  }\n\n  modifier_reset_ = bar.config.get(\"modifier-reset\", \"press\").asString();\n\n  signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));\n  signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));\n  signal_urgency_.connect(sigc::mem_fun(*this, &BarIpcClient::onUrgencyUpdate));\n  signal_mode_.connect(sigc::mem_fun(*this, &BarIpcClient::onModeUpdate));\n\n  // Subscribe to non bar events to determine if the modifier key press is followed by another\n  // action.\n  std::ostringstream oss_events;\n  oss_events << subscribe_events;\n  ipc_.subscribe(oss_events.str());\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));\n  ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onCmd));\n  // Launch worker\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"BarIpcClient::handleEvent {}\", e.what());\n    }\n  });\n}\n\nbool BarIpcClient::isModuleEnabled(const std::string& name) {\n  for (const auto& section : {\"modules-left\", \"modules-center\", \"modules-right\"}) {\n    if (const auto& modules = bar_.config.get(section, {}); modules.isArray()) {\n      for (const auto& module : modules) {\n        if (module.asString().rfind(name, 0) == 0) {\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nstruct swaybar_config parseConfig(const Json::Value& payload) {\n  swaybar_config conf;\n  if (auto id = payload[\"id\"]; id.isString()) {\n    conf.id = id.asString();\n  }\n  if (auto mode = payload[\"mode\"]; mode.isString()) {\n    conf.mode = mode.asString();\n  }\n  if (auto hs = payload[\"hidden_state\"]; hs.isString()) {\n    conf.hidden_state = hs.asString();\n  }\n  return conf;\n}\n\nvoid BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {\n  auto payload = parser_.parse(res.payload);\n  if (auto success = payload.get(\"success\", true); !success.asBool()) {\n    auto err = payload.get(\"error\", \"Unknown error\");\n    throw std::runtime_error(err.asString());\n  }\n  auto config = parseConfig(payload);\n  onConfigUpdate(config);\n}\n\nvoid BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {\n  try {\n    auto payload = parser_.parse(res.payload);\n    switch (res.type) {\n      case IPC_EVENT_WORKSPACE:\n        if (payload.isMember(\"change\")) {\n          // only check and send signal if the workspace update reason was because of a urgent\n          // change\n          if (payload[\"change\"] == \"urgent\") {\n            auto urgent = payload[\"current\"][\"urgent\"];\n            if (urgent.asBool()) {\n              // Event for a new urgency, update the visibly\n              signal_urgency_(true);\n            } else if (!urgent.asBool() && visible_by_urgency_) {\n              // Event clearing an urgency, bar is visible, check if another workspace still has\n              // the urgency hint set\n              ipc_.sendCmd(IPC_GET_WORKSPACES);\n            }\n          }\n          modifier_no_action_ = false;\n        }\n        break;\n      case IPC_EVENT_MODE:\n        if (payload.isMember(\"change\")) {\n          signal_mode_(payload[\"change\"] != \"default\");\n          modifier_no_action_ = false;\n        }\n        break;\n      case IPC_EVENT_BINDING:\n        modifier_no_action_ = false;\n        break;\n      case IPC_EVENT_BAR_STATE_UPDATE:\n      case IPC_EVENT_BARCONFIG_UPDATE:\n        if (auto id = payload[\"id\"]; id.isString() && id.asString() != bar_.bar_id) {\n          spdlog::trace(\"swaybar ipc: ignore event for {}\", id.asString());\n          return;\n        }\n        if (payload.isMember(\"visible_by_modifier\")) {\n          // visibility change for hidden bar\n          signal_visible_(payload[\"visible_by_modifier\"].asBool());\n        } else {\n          // configuration update\n          auto config = parseConfig(payload);\n          signal_config_(std::move(config));\n        }\n        break;\n    }\n  } catch (const std::exception& e) {\n    spdlog::error(\"BarIpcClient::onEvent {}\", e.what());\n  }\n}\n\nvoid BarIpcClient::onCmd(const struct Ipc::ipc_response& res) {\n  if (res.type == IPC_GET_WORKSPACES) {\n    try {\n      auto payload = parser_.parse(res.payload);\n      for (auto& ws : payload) {\n        if (ws[\"urgent\"].asBool()) {\n          spdlog::debug(\"Found workspace {} with urgency set. Stopping search.\", ws[\"name\"]);\n          // Found one workspace with urgency set, signal bar visibility\n          signal_urgency_(true);\n          return;\n        }\n      }\n      // Command to get workspaces was sent after a change in workspaces was based on \"urgent\",\n      // if no workspace has this flag set to true, all flags must be cleared.\n      signal_urgency_(false);\n    } catch (const std::exception& e) {\n      spdlog::error(\"Bar: {}\", e.what());\n    }\n  }\n}\n\nvoid BarIpcClient::onConfigUpdate(const swaybar_config& config) {\n  spdlog::info(\"config update for {}: id {}, mode {}, hidden_state {}\", bar_.bar_id, config.id,\n               config.mode, config.hidden_state);\n  bar_config_ = config;\n  update();\n}\n\nvoid BarIpcClient::onModeUpdate(bool visible_by_mode) {\n  spdlog::debug(\"mode update for {}: {}\", bar_.bar_id, visible_by_mode);\n  visible_by_mode_ = visible_by_mode;\n  update();\n}\n\nvoid BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {\n  spdlog::debug(\"visibility update for {}: {}\", bar_.bar_id, visible_by_modifier);\n  visible_by_modifier_ = visible_by_modifier;\n  if (visible_by_modifier) {\n    modifier_no_action_ = true;\n  }\n\n  // Clear on either press or release depending on bar_.bar_config_.action value.\n  // For the check on release, make sure that the modifier key was not used for another action.\n  if (((modifier_reset_ == \"press\" && visible_by_modifier_) ||\n       (modifier_reset_ == \"release\" && !visible_by_modifier_ && modifier_no_action_))) {\n    // Clear the flags to hide the bar.\n    visible_by_urgency_ = false;\n    visible_by_mode_ = false;\n  }\n\n  update();\n}\n\nvoid BarIpcClient::onUrgencyUpdate(bool visible_by_urgency) {\n  spdlog::debug(\"urgency update for {}: {}\", bar_.bar_id, visible_by_urgency);\n  visible_by_urgency_ = visible_by_urgency;\n  update();\n}\n\nvoid BarIpcClient::update() {\n  bool visible = visible_by_modifier_ || visible_by_mode_ || visible_by_urgency_;\n  if (bar_config_.mode == \"invisible\") {\n    visible = false;\n  } else if (bar_config_.mode != \"hide\" || bar_config_.hidden_state != \"hide\") {\n    visible = true;\n  }\n  bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);\n}\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/sway/ipc/client.cpp",
    "content": "#include \"modules/sway/ipc/client.hpp\"\n\n#include <fcntl.h>\n#include <spdlog/spdlog.h>\n\n#include <stdexcept>\n\nnamespace waybar::modules::sway {\n\nIpc::Ipc() {\n  const std::string& socketPath = getSocketPath();\n  fd_ = util::ScopedFd(open(socketPath));\n  fd_event_ = util::ScopedFd(open(socketPath));\n}\n\nIpc::~Ipc() {\n  thread_.stop();\n\n  if (fd_ > 0) {\n    // To fail the IPC header\n    if (write(fd_, \"close-sway-ipc\", 14) == -1) {\n      spdlog::error(\"Failed to close sway IPC\");\n    }\n  }\n  if (fd_event_ > 0) {\n    if (write(fd_event_, \"close-sway-ipc\", 14) == -1) {\n      spdlog::error(\"Failed to close sway IPC event handler\");\n    }\n  }\n}\n\nvoid Ipc::setWorker(std::function<void()>&& func) { thread_ = func; }\n\nconst std::string Ipc::getSocketPath() const {\n  const char* env = getenv(\"SWAYSOCK\");\n  if (env != nullptr) {\n    return std::string(env);\n  }\n  std::string str;\n  {\n    std::string str_buf;\n    FILE* in;\n    char buf[512] = {0};\n    if ((in = popen(\"sway --get-socketpath 2>/dev/null\", \"r\")) == nullptr) {\n      throw std::runtime_error(\"Failed to get socket path\");\n    }\n    while (fgets(buf, sizeof(buf), in) != nullptr) {\n      str_buf.append(buf, sizeof(buf));\n    }\n    pclose(in);\n    str = str_buf;\n    if (str.empty()) {\n      throw std::runtime_error(\"Socket path is empty\");\n    }\n  }\n  if (str.back() == '\\n') {\n    str.pop_back();\n  }\n  return str;\n}\n\nint Ipc::open(const std::string& socketPath) const {\n  util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0));\n  if (fd == -1) {\n    throw std::runtime_error(\"Unable to open Unix socket\");\n  }\n  (void)fcntl(fd, F_SETFD, FD_CLOEXEC);\n  struct sockaddr_un addr;\n  memset(&addr, 0, sizeof(struct sockaddr_un));\n  addr.sun_family = AF_UNIX;\n  strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);\n  addr.sun_path[sizeof(addr.sun_path) - 1] = 0;\n  int l = sizeof(struct sockaddr_un);\n  if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), l) == -1) {\n    throw std::runtime_error(\"Unable to connect to Sway\");\n  }\n  return fd.release();\n}\n\nstruct Ipc::ipc_response Ipc::recv(int fd) {\n  std::string header;\n  header.resize(ipc_header_size_);\n  auto data32 = reinterpret_cast<uint32_t*>(header.data() + ipc_magic_.size());\n  size_t total = 0;\n\n  while (total < ipc_header_size_) {\n    auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0);\n    if (fd_event_ == -1 || fd_ == -1) {\n      // IPC is closed so just return an empty response\n      return {0, 0, \"\"};\n    }\n    if (res <= 0) {\n      throw std::runtime_error(\"Unable to receive IPC header\");\n    }\n    total += res;\n  }\n  auto magic = std::string(header.data(), header.data() + ipc_magic_.size());\n  if (magic != ipc_magic_) {\n    throw std::runtime_error(\"Invalid IPC magic\");\n  }\n\n  total = 0;\n  std::string payload;\n  payload.resize(data32[0]);\n  while (total < data32[0]) {\n    auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0);\n    if (res < 0) {\n      if (errno == EINTR || errno == EAGAIN) {\n        continue;\n      }\n      throw std::runtime_error(\"Unable to receive IPC payload\");\n    }\n    total += res;\n  }\n  return {data32[0], data32[1], &payload.front()};\n}\n\nstruct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) {\n  std::string header;\n  header.resize(ipc_header_size_);\n  auto data32 = reinterpret_cast<uint32_t*>(header.data() + ipc_magic_.size());\n  memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size());\n  data32[0] = payload.size();\n  data32[1] = type;\n\n  if (::send(fd, header.data(), ipc_header_size_, 0) == -1) {\n    throw std::runtime_error(\"Unable to send IPC header\");\n  }\n  if (::send(fd, payload.c_str(), payload.size(), 0) == -1) {\n    throw std::runtime_error(\"Unable to send IPC payload\");\n  }\n  return Ipc::recv(fd);\n}\n\nvoid Ipc::sendCmd(uint32_t type, const std::string& payload) {\n  std::lock_guard<std::mutex> lock(mutex_);\n  const auto res = Ipc::send(fd_, type, payload);\n  signal_cmd.emit(res);\n}\n\nvoid Ipc::subscribe(const std::string& payload) {\n  auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload);\n  if (res.payload != \"{\\\"success\\\": true}\") {\n    throw std::runtime_error(\"Unable to subscribe ipc event\");\n  }\n}\n\nvoid Ipc::handleEvent() {\n  const auto res = Ipc::recv(fd_event_);\n  signal_event.emit(res);\n}\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/sway/language.cpp",
    "content": "#include \"modules/sway/language.hpp\"\n\n#include <fmt/core.h>\n#include <json/json.h>\n#include <spdlog/spdlog.h>\n#include <xkbcommon/xkbregistry.h>\n\n#include <cstring>\n#include <string>\n#include <vector>\n\n#include \"modules/sway/ipc/ipc.hpp\"\n#include \"util/string.hpp\"\n\nnamespace waybar::modules::sway {\n\nconst std::string Language::XKB_LAYOUT_NAMES_KEY = \"xkb_layout_names\";\nconst std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = \"xkb_active_layout_name\";\n\nLanguage::Language(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"language\", id, \"{}\", 0, true) {\n  hide_single_ = config[\"hide-single-layout\"].isBool() && config[\"hide-single-layout\"].asBool();\n  is_variant_displayed = format_.find(\"{variant}\") != std::string::npos;\n  if (format_.find(\"{}\") != std::string::npos || format_.find(\"{short}\") != std::string::npos) {\n    displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortName);\n  }\n  if (format_.find(\"{shortDescription}\") != std::string::npos) {\n    displayed_short_flag |= static_cast<std::byte>(DisplayedShortFlag::ShortDescription);\n  }\n  if (config.isMember(\"tooltip-format\")) {\n    tooltip_format_ = config[\"tooltip-format\"].asString();\n  }\n  ipc_.subscribe(R\"([\"input\"])\");\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));\n  ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));\n  ipc_.sendCmd(IPC_GET_INPUTS);\n  // Launch worker\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Language: {}\", e.what());\n    }\n  });\n  dp.emit();\n}\n\nvoid Language::onCmd(const struct Ipc::ipc_response& res) {\n  if (res.type != IPC_GET_INPUTS) {\n    return;\n  }\n\n  try {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto payload = parser_.parse(res.payload);\n    std::vector<std::string> used_layouts;\n    // Display current layout of a device with a maximum count of layouts, expecting that all will\n    // be OK\n    Json::ArrayIndex max_id = 0, max = 0;\n    for (Json::ArrayIndex i = 0; i < payload.size(); i++) {\n      auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();\n      if (size > max) {\n        max = size;\n        max_id = i;\n      }\n    }\n\n    for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {\n      used_layouts.push_back(layout.asString());\n    }\n\n    init_layouts_map(used_layouts);\n    set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Language: {}\", e.what());\n  }\n}\n\nvoid Language::onEvent(const struct Ipc::ipc_response& res) {\n  if (res.type != IPC_EVENT_INPUT) {\n    return;\n  }\n\n  try {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto payload = parser_.parse(res.payload)[\"input\"];\n    if (payload[\"type\"].asString() == \"keyboard\") {\n      set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());\n    }\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Language: {}\", e.what());\n  }\n}\n\nauto Language::update() -> void {\n  std::lock_guard<std::mutex> lock(mutex_);\n  if (hide_single_ && layouts_map_.size() <= 1) {\n    event_box_.hide();\n    return;\n  }\n  auto display_layout = trim(fmt::format(\n      fmt::runtime(format_), fmt::arg(\"short\", layout_.short_name),\n      fmt::arg(\"shortDescription\", layout_.short_description), fmt::arg(\"long\", layout_.full_name),\n      fmt::arg(\"variant\", layout_.variant), fmt::arg(\"flag\", layout_.country_flag())));\n  label_.set_markup(display_layout);\n  if (tooltipEnabled()) {\n    if (tooltip_format_ != \"\") {\n      auto tooltip_display_layout = trim(\n          fmt::format(fmt::runtime(tooltip_format_), fmt::arg(\"short\", layout_.short_name),\n                      fmt::arg(\"shortDescription\", layout_.short_description),\n                      fmt::arg(\"long\", layout_.full_name), fmt::arg(\"variant\", layout_.variant),\n                      fmt::arg(\"flag\", layout_.country_flag())));\n      label_.set_tooltip_markup(tooltip_display_layout);\n    } else {\n      label_.set_tooltip_markup(display_layout);\n    }\n  }\n\n  event_box_.show();\n\n  // Call parent update\n  ALabel::update();\n}\n\nauto Language::set_current_layout(const std::string& current_layout) -> void {\n  label_.get_style_context()->remove_class(layout_.short_name);\n  layout_ = layouts_map_[current_layout];\n  label_.get_style_context()->add_class(layout_.short_name);\n}\n\nauto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {\n  std::map<std::string, std::vector<Layout*>> found_by_short_names;\n  XKBContext xkb_context;\n  auto layout = xkb_context.next_layout();\n  for (; layout != nullptr; layout = xkb_context.next_layout()) {\n    if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==\n        used_layouts.end()) {\n      continue;\n    }\n\n    if (!is_variant_displayed) {\n      auto short_name = layout->short_name;\n      if (found_by_short_names.count(short_name) > 0) {\n        found_by_short_names[short_name].push_back(layout);\n      } else {\n        found_by_short_names[short_name] = {layout};\n      }\n    }\n\n    layouts_map_.emplace(layout->full_name, *layout);\n  }\n\n  if (is_variant_displayed || found_by_short_names.size() == 0) {\n    return;\n  }\n\n  std::map<std::string, int> short_name_to_number_map;\n  for (const auto& used_layout_name : used_layouts) {\n    auto found = layouts_map_.find(used_layout_name);\n    if (found == layouts_map_.end()) continue;\n    auto used_layout = &found->second;\n    auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];\n    if (layouts_with_same_name_list.size() < 2) {\n      continue;\n    }\n\n    if (short_name_to_number_map.count(used_layout->short_name) == 0) {\n      short_name_to_number_map[used_layout->short_name] = 1;\n    }\n\n    if (displayed_short_flag != static_cast<std::byte>(0)) {\n      int& number = short_name_to_number_map[used_layout->short_name];\n      used_layout->short_name = used_layout->short_name + std::to_string(number);\n      used_layout->short_description = used_layout->short_description + std::to_string(number);\n      ++number;\n    }\n  }\n}\n\nLanguage::XKBContext::XKBContext() {\n  context_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);\n  rxkb_context_parse_default_ruleset(context_);\n}\n\nauto Language::XKBContext::next_layout() -> Layout* {\n  if (xkb_layout_ == nullptr) {\n    xkb_layout_ = rxkb_layout_first(context_);\n  } else {\n    xkb_layout_ = rxkb_layout_next(xkb_layout_);\n  }\n\n  if (xkb_layout_ == nullptr) {\n    return nullptr;\n  }\n\n  auto description = std::string(rxkb_layout_get_description(xkb_layout_));\n  auto name = std::string(rxkb_layout_get_name(xkb_layout_));\n  auto variant_ = rxkb_layout_get_variant(xkb_layout_);\n  std::string variant = variant_ == nullptr ? \"\" : std::string(variant_);\n  auto short_description_ = rxkb_layout_get_brief(xkb_layout_);\n  std::string short_description;\n  if (short_description_ != nullptr) {\n    short_description = std::string(short_description_);\n    base_layouts_by_name_.emplace(name, xkb_layout_);\n  } else {\n    auto base_layout = base_layouts_by_name_[name];\n    short_description =\n        base_layout == nullptr ? \"\" : std::string(rxkb_layout_get_brief(base_layout));\n  }\n  delete layout_;\n  layout_ = new Layout{description, name, variant, short_description};\n  return layout_;\n}\n\nLanguage::XKBContext::~XKBContext() {\n  rxkb_context_unref(context_);\n  delete layout_;\n}\n\nstd::string Language::Layout::country_flag() const {\n  if (short_name.size() != 2) return \"\";\n  unsigned char result[] = \"\\xf0\\x9f\\x87\\x00\\xf0\\x9f\\x87\\x00\";\n  result[3] = short_name[0] + 0x45;\n  result[7] = short_name[1] + 0x45;\n  // Check if both emojis are in A-Z symbol bounds\n  if (result[3] < 0xa6 || result[3] > 0xbf) return \"\";\n  if (result[7] < 0xa6 || result[7] > 0xbf) return \"\";\n  return std::string{reinterpret_cast<char*>(result)};\n}\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/sway/mode.cpp",
    "content": "#include \"modules/sway/mode.hpp\"\n\n#include <spdlog/spdlog.h>\n\nnamespace waybar::modules::sway {\n\nMode::Mode(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"mode\", id, \"{}\", 0, true) {\n  ipc_.subscribe(R\"([\"mode\"])\");\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));\n  // Launch worker\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Mode: {}\", e.what());\n    }\n  });\n  dp.emit();\n}\n\nvoid Mode::onEvent(const struct Ipc::ipc_response& res) {\n  try {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto payload = parser_.parse(res.payload);\n    if (payload[\"change\"] != \"default\") {\n      if (payload[\"pango_markup\"].asBool()) {\n        mode_ = payload[\"change\"].asString();\n      } else {\n        mode_ = Glib::Markup::escape_text(payload[\"change\"].asString());\n      }\n    } else {\n      mode_.clear();\n    }\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Mode: {}\", e.what());\n  }\n}\n\nauto Mode::update() -> void {\n  if (mode_.empty()) {\n    event_box_.hide();\n  } else {\n    label_.set_markup(fmt::format(fmt::runtime(format_), mode_));\n    if (tooltipEnabled()) {\n      label_.set_tooltip_markup(mode_);\n    }\n    event_box_.show();\n  }\n  // Call parent update\n  ALabel::update();\n}\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/sway/scratchpad.cpp",
    "content": "#include \"modules/sway/scratchpad.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <string>\n\nnamespace waybar::modules::sway {\nScratchpad::Scratchpad(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"scratchpad\", id,\n             config[\"format\"].isString() ? config[\"format\"].asString() : \"{icon} {count}\"),\n      tooltip_format_(config_[\"tooltip-format\"].isString() ? config_[\"tooltip-format\"].asString()\n                                                           : \"{app}: {title}\"),\n      show_empty_(config_[\"show-empty\"].isBool() ? config_[\"show-empty\"].asBool() : false),\n      tooltip_enabled_(config_[\"tooltip\"].isBool() ? config_[\"tooltip\"].asBool() : true),\n      tooltip_text_(\"\"),\n      count_(0) {\n  ipc_.subscribe(R\"([\"window\"])\");\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &Scratchpad::onEvent));\n  ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Scratchpad::onCmd));\n\n  getTree();\n\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Scratchpad: {}\", e.what());\n    }\n  });\n}\nauto Scratchpad::update() -> void {\n  if (count_ || show_empty_) {\n    event_box_.show();\n    label_.set_markup(\n        fmt::format(fmt::runtime(format_),\n                    fmt::arg(\"icon\", getIcon(count_, \"\", config_[\"format-icons\"].size())),\n                    fmt::arg(\"count\", count_)));\n    if (tooltip_enabled_) {\n      label_.set_tooltip_markup(tooltip_text_);\n    }\n  } else {\n    event_box_.hide();\n  }\n  if (count_) {\n    label_.get_style_context()->remove_class(\"empty\");\n  } else {\n    label_.get_style_context()->add_class(\"empty\");\n  }\n  ALabel::update();\n}\n\nauto Scratchpad::getTree() -> void {\n  try {\n    ipc_.sendCmd(IPC_GET_TREE);\n  } catch (const std::exception& e) {\n    spdlog::error(\"Scratchpad: {}\", e.what());\n  }\n}\n\nauto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {\n  try {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto tree = parser_.parse(res.payload);\n    count_ = tree[\"nodes\"][0][\"nodes\"][0][\"floating_nodes\"].size();\n    if (tooltip_enabled_) {\n      tooltip_text_.clear();\n      for (const auto& window : tree[\"nodes\"][0][\"nodes\"][0][\"floating_nodes\"]) {\n        tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\\n'),\n                                         fmt::arg(\"app\", window[\"app_id\"].asString()),\n                                         fmt::arg(\"title\", window[\"name\"].asString())));\n      }\n      if (!tooltip_text_.empty()) {\n        tooltip_text_.pop_back();\n      }\n    }\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Scratchpad: {}\", e.what());\n  }\n}\n\nauto Scratchpad::onEvent(const struct Ipc::ipc_response& res) -> void { getTree(); }\n}  // namespace waybar::modules::sway"
  },
  {
    "path": "src/modules/sway/window.cpp",
    "content": "#include \"modules/sway/window.hpp\"\n\n#include <gdkmm/pixbuf.h>\n#include <glibmm/fileutils.h>\n#include <glibmm/keyfile.h>\n#include <glibmm/miscutils.h>\n#include <gtkmm/enums.h>\n#include <spdlog/spdlog.h>\n\n#include <filesystem>\n#include <regex>\n#include <string>\n\n#include \"util/gtk_icon.hpp\"\n#include \"util/rewrite_string.hpp\"\n\nnamespace waybar::modules::sway {\n\nWindow::Window(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"window\", id, \"{}\", 0, true), bar_(bar), windowId_(-1) {\n  ipc_.subscribe(R\"([\"window\",\"workspace\"])\");\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));\n  ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));\n  // Get Initial focused window\n  getTree();\n  // Launch worker\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Window: {}\", e.what());\n      spdlog::trace(\"Window::Window exception\");\n    }\n  });\n}\n\nvoid Window::onEvent(const struct Ipc::ipc_response& res) { getTree(); }\n\nvoid Window::onCmd(const struct Ipc::ipc_response& res) {\n  try {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto payload = parser_.parse(res.payload);\n    auto output = payload[\"output\"].isString() ? payload[\"output\"].asString() : \"\";\n    std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_,\n             marks_) = getFocusedNode(payload[\"nodes\"], output);\n    updateAppIconName(app_id_, app_class_);\n    dp.emit();\n  } catch (const std::exception& e) {\n    spdlog::error(\"Window: {}\", e.what());\n    spdlog::trace(\"Window::onCmd exception\");\n  }\n}\n\nauto Window::update() -> void {\n  spdlog::trace(\"workspace layout {}, tiled count {}, floating count {}\", layout_, app_nb_,\n                floating_count_);\n\n  int mode = 0;\n  if (app_nb_ == 0) {\n    if (floating_count_ == 0) {\n      mode += 1;\n    } else {\n      mode += 4;\n    }\n  } else if (app_nb_ == 1) {\n    mode += 2;\n  } else {\n    if (layout_ == \"tabbed\") {\n      mode += 8;\n    } else if (layout_ == \"stacked\") {\n      mode += 16;\n    } else {\n      mode += 32;\n    }\n  }\n\n  if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&\n      bar_.window.get_style_context()->has_class(old_app_id_)) {\n    spdlog::trace(\"Removing app_id class: {}\", old_app_id_);\n    bar_.window.get_style_context()->remove_class(old_app_id_);\n    old_app_id_ = \"\";\n  }\n\n  setClass(\"empty\", ((mode & 1) > 0));\n  setClass(\"solo\", ((mode & 2) > 0));\n  setClass(\"floating\", ((mode & 4) > 0));\n  setClass(\"tabbed\", ((mode & 8) > 0));\n  setClass(\"stacked\", ((mode & 16) > 0));\n  setClass(\"tiled\", ((mode & 32) > 0));\n\n  if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {\n    spdlog::trace(\"Adding app_id class: {}\", app_id_);\n    bar_.window.get_style_context()->add_class(app_id_);\n    old_app_id_ = app_id_;\n  }\n\n  label_.set_markup(waybar::util::rewriteString(\n      fmt::format(fmt::runtime(format_), fmt::arg(\"title\", window_), fmt::arg(\"app_id\", app_id_),\n                  fmt::arg(\"shell\", shell_), fmt::arg(\"marks\", marks_)),\n      config_[\"rewrite\"]));\n  if (tooltipEnabled()) {\n    label_.set_tooltip_markup(window_);\n  }\n\n  updateAppIcon();\n\n  // Call parent update\n  AAppIconLabel::update();\n}\n\nvoid Window::setClass(const std::string& classname, bool enable) {\n  if (enable) {\n    if (!bar_.window.get_style_context()->has_class(classname)) {\n      bar_.window.get_style_context()->add_class(classname);\n    }\n  } else {\n    bar_.window.get_style_context()->remove_class(classname);\n  }\n}\n\nstd::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {\n  auto const& nodes = node[\"nodes\"];\n  auto const& floating_nodes = node[\"floating_nodes\"];\n  if (nodes.empty() && floating_nodes.empty()) {\n    if (node[\"type\"].asString() == \"workspace\")\n      return {0, 0};\n    else if (node[\"type\"].asString() == \"floating_con\") {\n      return {0, 1};\n    } else {\n      return {1, 0};\n    }\n  }\n  int sum = 0;\n  int floating_sum = 0;\n  for (auto const& node : nodes) {\n    std::pair all_leaf_nodes = leafNodesInWorkspace(node);\n    sum += all_leaf_nodes.first;\n    floating_sum += all_leaf_nodes.second;\n  }\n  for (auto const& node : floating_nodes) {\n    std::pair all_leaf_nodes = leafNodesInWorkspace(node);\n    sum += all_leaf_nodes.first;\n    floating_sum += all_leaf_nodes.second;\n  }\n  return {sum, floating_sum};\n}\n\nstd::optional<std::reference_wrapper<const Json::Value>> getSingleChildNode(\n    const Json::Value& node) {\n  auto const& nodes = node[\"nodes\"];\n  if (nodes.empty()) {\n    if (node[\"type\"].asString() == \"workspace\")\n      return {};\n    else if (node[\"type\"].asString() == \"floating_con\") {\n      return {};\n    } else {\n      return {std::cref(node)};\n    }\n  }\n  auto it = std::cbegin(nodes);\n  if (it == std::cend(nodes)) {\n    return {};\n  }\n  auto const& child = *it;\n  ++it;\n  if (it != std::cend(nodes)) {\n    return {};\n  }\n  return {getSingleChildNode(child)};\n}\n\nstd::tuple<std::string, std::string, std::string, std::string> getWindowInfo(\n    const Json::Value& node, bool showHidden) {\n  const auto app_id = node[\"app_id\"].isString() ? node[\"app_id\"].asString()\n                                                : node[\"window_properties\"][\"instance\"].asString();\n  const auto app_class = node[\"window_properties\"][\"class\"].isString()\n                             ? node[\"window_properties\"][\"class\"].asString()\n                             : \"\";\n  const auto shell = node[\"shell\"].isString() ? node[\"shell\"].asString() : \"\";\n  std::string marks = \"\";\n  if (node[\"marks\"].isArray()) {\n    for (const auto& m : node[\"marks\"]) {\n      if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) {\n        continue;\n      }\n      if (!marks.empty()) {\n        marks.append(\",\");\n      }\n      marks.append(m.asString());\n    }\n  }\n  return {app_id, app_class, shell, marks};\n}\n\nstd::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,\n           std::string>\ngfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,\n                 const Bar& bar_, Json::Value& parentWorkspace,\n                 const Json::Value& immediateParent) {\n  for (auto const& node : nodes) {\n    if (node[\"type\"].asString() == \"output\") {\n      if ((!config_[\"all-outputs\"].asBool() || config_[\"offscreen-css\"].asBool()) &&\n          (node[\"name\"].asString() != bar_.output->name)) {\n        continue;\n      }\n      output = node[\"name\"].asString();\n    } else if (node[\"type\"].asString() == \"workspace\") {\n      // needs to be a string comparison, because filterWorkspace is the current_workspace\n      if (node[\"name\"].asString() != immediateParent[\"current_workspace\"].asString()) {\n        continue;\n      }\n      if (node[\"focused\"].asBool()) {\n        std::pair all_leaf_nodes = leafNodesInWorkspace(node);\n        return {all_leaf_nodes.first,\n                all_leaf_nodes.second,\n                node[\"id\"].asInt(),\n                (((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&\n                 (config_[\"show-focused-workspace-name\"].asBool()))\n                    ? node[\"name\"].asString()\n                    : \"\",\n                \"\",\n                \"\",\n                \"\",\n                node[\"layout\"].asString(),\n                \"\"};\n      }\n      parentWorkspace = node;\n    } else if ((node[\"type\"].asString() == \"con\" || node[\"type\"].asString() == \"floating_con\") &&\n               (node[\"focused\"].asBool())) {\n      // found node\n      spdlog::trace(\"actual output {}, output found {}, node (focused) found {}\", bar_.output->name,\n                    output, node[\"name\"].asString());\n      const auto [app_id, app_class, shell, marks] =\n          getWindowInfo(node, config_[\"show-hidden-marks\"].asBool());\n      int nb = node.size();\n      int floating_count = 0;\n      std::string workspace_layout = \"\";\n      if (!parentWorkspace.isNull()) {\n        std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);\n        nb = all_leaf_nodes.first;\n        floating_count = all_leaf_nodes.second;\n        workspace_layout = parentWorkspace[\"layout\"].asString();\n      }\n      return {nb,\n              floating_count,\n              node[\"id\"].asInt(),\n              Glib::Markup::escape_text(node[\"name\"].asString()),\n              app_id,\n              app_class,\n              shell,\n              workspace_layout,\n              marks};\n    }\n\n    // iterate\n    auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] =\n        gfnWithWorkspace(node[\"nodes\"], output, config_, bar_, parentWorkspace, node);\n    auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] =\n        gfnWithWorkspace(node[\"floating_nodes\"], output, config_, bar_, parentWorkspace, node);\n\n    //    if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {\n    if ((id > 0) || (id2 < 0 && id > -1)) {\n      return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks};\n    } else if (id2 > 0 && !name2.empty()) {\n      return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2};\n    }\n  }\n\n  // this only comes into effect when no focused children are present\n  if (config_[\"all-outputs\"].asBool() && config_[\"offscreen-css\"].asBool() &&\n      immediateParent[\"type\"].asString() == \"workspace\") {\n    std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);\n    // using an empty string as default ensures that no window depending styles are set due to the\n    // checks above for !name.empty()\n    std::string app_id = \"\";\n    std::string app_class = \"\";\n    std::string workspace_layout = \"\";\n    std::string marks = \"\";\n    if (all_leaf_nodes.first == 1) {\n      const auto single_child = getSingleChildNode(immediateParent);\n      if (single_child.has_value()) {\n        std::tie(app_id, app_class, workspace_layout, marks) =\n            getWindowInfo(single_child.value(), config_[\"show-hidden-marks\"].asBool());\n      }\n    }\n    return {all_leaf_nodes.first,\n            all_leaf_nodes.second,\n            0,\n            (all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)\n                ? config_[\"offscreen-css-text\"].asString()\n                : \"\",\n            app_id,\n            app_class,\n            workspace_layout,\n            immediateParent[\"layout\"].asString(),\n            marks};\n  }\n\n  return {0, 0, -1, \"\", \"\", \"\", \"\", \"\", \"\"};\n}\n\nstd::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string,\n           std::string>\nWindow::getFocusedNode(const Json::Value& nodes, std::string& output) {\n  Json::Value placeholder = Json::Value::null;\n  return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);\n}\n\nvoid Window::getTree() {\n  try {\n    ipc_.sendCmd(IPC_GET_TREE);\n  } catch (const std::exception& e) {\n    spdlog::error(\"Window: {}\", e.what());\n    spdlog::trace(\"Window::getTree exception\");\n  }\n}\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/sway/workspaces.cpp",
    "content": "#include \"modules/sway/workspaces.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <cctype>\n#include <string>\n\nnamespace waybar::modules::sway {\n\n// Helper function to assign a number to a workspace, just like sway. In fact\n// this is taken quite verbatim from `sway/ipc-json.c`.\nint Workspaces::convertWorkspaceNameToNum(const std::string& name) {\n  if (isdigit(name[0]) != 0) {\n    errno = 0;\n    char* endptr = nullptr;\n    long long parsed_num = strtoll(name.c_str(), &endptr, 10);\n    if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) {\n      return -1;\n    }\n    return (int)parsed_num;\n  }\n  return -1;\n}\n\nint Workspaces::windowRewritePriorityFunction(std::string const& window_rule) {\n  // Rules that match against title are prioritized\n  // Rules that don't specify if they're matching against either title or class are deprioritized\n  bool const hasTitle = window_rule.find(\"title\") != std::string::npos;\n  bool const hasClass = window_rule.find(\"class\") != std::string::npos;\n\n  if (hasTitle && hasClass) {\n    return 3;\n  }\n  if (hasTitle) {\n    return 2;\n  }\n  if (hasClass) {\n    return 1;\n  }\n  return 0;\n}\n\nWorkspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AModule(config, \"workspaces\", id, false, !config[\"disable-scroll\"].asBool()),\n      bar_(bar),\n      box_(bar.orientation, 0) {\n  if (config[\"format-icons\"][\"high-priority-named\"].isArray()) {\n    for (const auto& it : config[\"format-icons\"][\"high-priority-named\"]) {\n      high_priority_named_.push_back(it.asString());\n    }\n  }\n  box_.set_name(\"workspaces\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n  if (config_[\"format-window-separator\"].isString()) {\n    m_formatWindowSeparator = config_[\"format-window-separator\"].asString();\n  } else {\n    m_formatWindowSeparator = \" \";\n  }\n  const Json::Value& windowRewrite = config[\"window-rewrite\"];\n  if (windowRewrite.isObject()) {\n    const Json::Value& windowRewriteDefaultConfig = config[\"window-rewrite-default\"];\n    std::string windowRewriteDefault =\n        windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : \"?\";\n    m_windowRewriteRules = waybar::util::RegexCollection(\n        windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction);\n  }\n  ipc_.subscribe(R\"([\"workspace\"])\");\n  ipc_.subscribe(R\"([\"window\"])\");\n  ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));\n  ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd));\n  ipc_.sendCmd(IPC_GET_TREE);\n  if (config[\"enable-bar-scroll\"].asBool()) {\n    auto& window = const_cast<Bar&>(bar_).window;\n    window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n    window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));\n  }\n  // Launch worker\n  ipc_.setWorker([this] {\n    try {\n      ipc_.handleEvent();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Workspaces: {}\", e.what());\n    }\n  });\n}\n\nvoid Workspaces::onEvent(const struct Ipc::ipc_response& res) {\n  try {\n    ipc_.sendCmd(IPC_GET_TREE);\n  } catch (const std::exception& e) {\n    spdlog::error(\"Workspaces: {}\", e.what());\n  }\n}\n\nvoid Workspaces::onCmd(const struct Ipc::ipc_response& res) {\n  if (res.type == IPC_GET_TREE) {\n    try {\n      {\n        std::lock_guard<std::mutex> lock(mutex_);\n        auto payload = parser_.parse(res.payload);\n        workspaces_.clear();\n        std::vector<Json::Value> outputs;\n        bool alloutputs = config_[\"all-outputs\"].asBool();\n        std::copy_if(payload[\"nodes\"].begin(), payload[\"nodes\"].end(), std::back_inserter(outputs),\n                     [&](const auto& output) {\n                       if (alloutputs && output[\"name\"].asString() != \"__i3\") {\n                         return true;\n                       }\n                       if (output[\"name\"].asString() == bar_.output->name) {\n                         return true;\n                       }\n                       return false;\n                     });\n\n        for (auto& output : outputs) {\n          std::copy(output[\"nodes\"].begin(), output[\"nodes\"].end(),\n                    std::back_inserter(workspaces_));\n          std::copy(output[\"floating_nodes\"].begin(), output[\"floating_nodes\"].end(),\n                    std::back_inserter(workspaces_));\n        }\n\n        // adding persistent workspaces (as per the config file)\n        if (config_[\"persistent-workspaces\"].isObject()) {\n          const Json::Value& p_workspaces = config_[\"persistent-workspaces\"];\n          const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();\n\n          for (const std::string& p_w_name : p_workspaces_names) {\n            const Json::Value& p_w = p_workspaces[p_w_name];\n            auto it = std::find_if(workspaces_.begin(), workspaces_.end(),\n                                   [&p_w_name](const Json::Value& node) {\n                                     return node[\"name\"].asString() == p_w_name;\n                                   });\n\n            if (it != workspaces_.end()) {\n              continue;  // already displayed by some bar\n            }\n\n            if (p_w.isArray() && !p_w.empty()) {\n              // Adding to target outputs\n              for (const Json::Value& output : p_w) {\n                if (output.asString() == bar_.output->name) {\n                  Json::Value v;\n                  v[\"name\"] = p_w_name;\n                  v[\"target_output\"] = bar_.output->name;\n                  v[\"num\"] = convertWorkspaceNameToNum(p_w_name);\n                  workspaces_.emplace_back(std::move(v));\n                  break;\n                }\n              }\n            } else {\n              // Adding to all outputs\n              Json::Value v;\n              v[\"name\"] = p_w_name;\n              v[\"target_output\"] = \"\";\n              v[\"num\"] = convertWorkspaceNameToNum(p_w_name);\n              workspaces_.emplace_back(std::move(v));\n            }\n          }\n        }\n\n        // sway has a defined ordering of workspaces that should be preserved in\n        // the representation displayed by waybar to ensure that commands such\n        // as \"workspace prev\" or \"workspace next\" make sense when looking at\n        // the workspace representation in the bar.\n        // Due to waybar's own feature of persistent workspaces unknown to sway,\n        // custom sorting logic is necessary to make these workspaces appear\n        // naturally in the list of workspaces without messing up sway's\n        // sorting. For this purpose, a custom numbering property is created\n        // that preserves the order provided by sway while inserting numbered\n        // persistent workspaces at their natural positions.\n        //\n        // All of this code assumes that sway provides numbered workspaces first\n        // and other workspaces are sorted by their creation time.\n        //\n        // In a first pass, the maximum \"num\" value is computed to enqueue\n        // unnumbered workspaces behind numbered ones when computing the sort\n        // attribute.\n        //\n        // Note: if the 'alphabetical_sort' option is true, the user is in\n        // agreement that the \"workspace prev/next\" commands may not follow\n        // the order displayed in Waybar.\n        int max_num = -1;\n        for (auto& workspace : workspaces_) {\n          max_num = std::max(workspace[\"num\"].asInt(), max_num);\n        }\n        for (auto& workspace : workspaces_) {\n          auto workspace_num = workspace[\"num\"].asInt();\n          if (workspace_num > -1) {\n            workspace[\"sort\"] = workspace_num;\n          } else {\n            workspace[\"sort\"] = ++max_num;\n          }\n        }\n        std::sort(workspaces_.begin(), workspaces_.end(),\n                  [this](const Json::Value& lhs, const Json::Value& rhs) {\n                    auto lname = lhs[\"name\"].asString();\n                    auto rname = rhs[\"name\"].asString();\n                    int l = lhs[\"sort\"].asInt();\n                    int r = rhs[\"sort\"].asInt();\n\n                    if (l == r || config_[\"alphabetical_sort\"].asBool()) {\n                      // In case both integers are the same, lexicographical\n                      // sort. The code above already ensure that this will only\n                      // happened in case of explicitly numbered workspaces.\n                      //\n                      // Additionally, if the config specifies to sort workspaces\n                      // alphabetically do this here.\n                      return lname < rname;\n                    }\n\n                    return l < r;\n                  });\n      }\n      dp.emit();\n    } catch (const std::exception& e) {\n      spdlog::error(\"Workspaces: {}\", e.what());\n    }\n  }\n}\n\nbool Workspaces::filterButtons() {\n  bool needReorder = false;\n  for (auto it = buttons_.begin(); it != buttons_.end();) {\n    auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),\n                           [it](const auto& node) { return node[\"name\"].asString() == it->first; });\n    if (ws == workspaces_.end() ||\n        (!config_[\"all-outputs\"].asBool() && (*ws)[\"output\"].asString() != bar_.output->name)) {\n      it = buttons_.erase(it);\n      needReorder = true;\n    } else {\n      ++it;\n    }\n  }\n  return needReorder;\n}\n\nbool Workspaces::hasFlag(const Json::Value& node, const std::string& flag) {\n  if (node[flag].asBool()) {\n    return true;\n  }\n\n  if (std::any_of(node[\"nodes\"].begin(), node[\"nodes\"].end(),\n                  [&](auto const& e) { return hasFlag(e, flag); })) {\n    return true;\n  }\n  if (std::any_of(node[\"floating_nodes\"].begin(), node[\"floating_nodes\"].end(),\n                  [&](auto const& e) { return hasFlag(e, flag); })) {\n    return true;\n  }\n  return false;\n}\n\nvoid Workspaces::updateWindows(const Json::Value& node, std::string& windows) {\n  if ((node[\"type\"].asString() == \"con\" || node[\"type\"].asString() == \"floating_con\") &&\n      node[\"name\"].isString()) {\n    std::string title = g_markup_escape_text(node[\"name\"].asString().c_str(), -1);\n    std::string windowClass = node[\"app_id\"].isString()\n                                  ? node[\"app_id\"].asString()\n                                  : node[\"window_properties\"][\"class\"].asString();\n\n    // Only add window rewrites that can be looked up\n    if (!windowClass.empty()) {\n      std::string windowReprKey = fmt::format(\"class<{}> title<{}>\", windowClass, title);\n      std::string window = m_windowRewriteRules.get(windowReprKey);\n      // allow result to have formatting\n      window = fmt::format(fmt::runtime(window), fmt::arg(\"name\", title),\n                           fmt::arg(\"class\", windowClass));\n      windows.append(window);\n      windows.append(m_formatWindowSeparator);\n    }\n  }\n  for (const Json::Value& child : node[\"nodes\"]) {\n    updateWindows(child, windows);\n  }\n  for (const Json::Value& child : node[\"floating_nodes\"]) {\n    updateWindows(child, windows);\n  }\n}\n\nauto Workspaces::update() -> void {\n  std::lock_guard<std::mutex> lock(mutex_);\n  bool needReorder = filterButtons();\n  for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) {\n    auto bit = buttons_.find((*it)[\"name\"].asString());\n    if (bit == buttons_.end()) {\n      needReorder = true;\n    }\n    auto& button = bit == buttons_.end() ? addButton(*it) : bit->second;\n    if (needReorder) {\n      box_.reorder_child(button, it - workspaces_.begin());\n    }\n    bool noNodes = (*it)[\"nodes\"].empty() && (*it)[\"floating_nodes\"].empty();\n    if (hasFlag((*it), \"focused\")) {\n      button.get_style_context()->add_class(\"focused\");\n    } else {\n      button.get_style_context()->remove_class(\"focused\");\n    }\n    if (hasFlag((*it), \"visible\") || ((*it)[\"output\"].isString() && noNodes)) {\n      button.get_style_context()->add_class(\"visible\");\n    } else {\n      button.get_style_context()->remove_class(\"visible\");\n    }\n    if (hasFlag((*it), \"urgent\")) {\n      button.get_style_context()->add_class(\"urgent\");\n    } else {\n      button.get_style_context()->remove_class(\"urgent\");\n    }\n    if ((*it)[\"target_output\"].isString()) {\n      button.get_style_context()->add_class(\"persistent\");\n    } else {\n      button.get_style_context()->remove_class(\"persistent\");\n    }\n    if (noNodes) {\n      button.get_style_context()->add_class(\"empty\");\n    } else {\n      button.get_style_context()->remove_class(\"empty\");\n    }\n    if ((*it)[\"output\"].isString()) {\n      if (((*it)[\"output\"].asString()) == bar_.output->name) {\n        button.get_style_context()->add_class(\"current_output\");\n      } else {\n        button.get_style_context()->remove_class(\"current_output\");\n      }\n    } else {\n      button.get_style_context()->remove_class(\"current_output\");\n    }\n    std::string output = (*it)[\"name\"].asString();\n    std::string windows = \"\";\n    if (config_[\"window-rewrite\"].isObject()) {\n      updateWindows((*it), windows);\n    }\n    if (config_[\"format\"].isString()) {\n      auto format = config_[\"format\"].asString();\n      output = fmt::format(\n          fmt::runtime(format), fmt::arg(\"icon\", getIcon(output, *it)), fmt::arg(\"value\", output),\n          fmt::arg(\"name\", trimWorkspaceName(output)), fmt::arg(\"index\", (*it)[\"num\"].asString()),\n          fmt::arg(\"windows\",\n                   windows.substr(0, windows.length() - m_formatWindowSeparator.length())),\n          fmt::arg(\"output\", (*it)[\"output\"].asString()));\n    }\n    if (!config_[\"disable-markup\"].asBool()) {\n      static_cast<Gtk::Label*>(button.get_children()[0])->set_markup(output);\n    } else {\n      button.set_label(output);\n    }\n    onButtonReady(*it, button);\n  }\n  // Call parent update\n  AModule::update();\n}\n\nGtk::Button& Workspaces::addButton(const Json::Value& node) {\n  auto pair = buttons_.emplace(node[\"name\"].asString(), node[\"name\"].asString());\n  auto&& button = pair.first->second;\n  box_.pack_start(button, false, false, 0);\n  button.set_name(\"sway-workspace-\" + node[\"name\"].asString());\n  button.set_relief(Gtk::RELIEF_NONE);\n  if (!config_[\"disable-click\"].asBool()) {\n    button.signal_pressed().connect([this, node] {\n      try {\n        if (node[\"target_output\"].isString()) {\n          ipc_.sendCmd(IPC_COMMAND,\n                       fmt::format(persistent_workspace_switch_cmd_, \"--no-auto-back-and-forth\",\n                                   node[\"name\"].asString(), node[\"target_output\"].asString(),\n                                   \"--no-auto-back-and-forth\", node[\"name\"].asString()));\n        } else {\n          ipc_.sendCmd(IPC_COMMAND, fmt::format(\"workspace {} \\\"{}\\\"\",\n                                                config_[\"disable-auto-back-and-forth\"].asBool()\n                                                    ? \"--no-auto-back-and-forth\"\n                                                    : \"\",\n                                                node[\"name\"].asString()));\n        }\n      } catch (const std::exception& e) {\n        spdlog::error(\"Workspaces: {}\", e.what());\n      }\n    });\n  }\n  return button;\n}\n\nstd::string Workspaces::getIcon(const std::string& name, const Json::Value& node) {\n  std::vector<std::string> keys = {\"high-priority-named\", \"urgent\", \"focused\", name, \"default\"};\n  for (auto const& key : keys) {\n    if (key == \"high-priority-named\") {\n      auto it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(),\n                             [&](const std::string& member) { return member == name; });\n      if (it != high_priority_named_.end()) {\n        return config_[\"format-icons\"][name].asString();\n      }\n\n      it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(),\n                        [&](const std::string& member) {\n                          return trimWorkspaceName(member) == trimWorkspaceName(name);\n                        });\n      if (it != high_priority_named_.end()) {\n        return config_[\"format-icons\"][trimWorkspaceName(name)].asString();\n      }\n    }\n    if (key == \"focused\" || key == \"urgent\") {\n      if (config_[\"format-icons\"][key].isString() && hasFlag(node, key)) {\n        return config_[\"format-icons\"][key].asString();\n      }\n    } else if (config_[\"format-icons\"][\"persistent\"].isString() &&\n               node[\"target_output\"].isString()) {\n      return config_[\"format-icons\"][\"persistent\"].asString();\n    } else if (config_[\"format-icons\"][key].isString()) {\n      return config_[\"format-icons\"][key].asString();\n    } else if (config_[\"format-icons\"][trimWorkspaceName(key)].isString()) {\n      return config_[\"format-icons\"][trimWorkspaceName(key)].asString();\n    }\n  }\n  return name;\n}\n\nbool Workspaces::handleScroll(GdkEventScroll* e) {\n  if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) {\n    /**\n     * Ignore emulated scroll events on window\n     */\n    return false;\n  }\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) {\n    return true;\n  }\n  std::string name;\n  {\n    bool alloutputs = config_[\"all-outputs\"].asBool();\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto it =\n        std::find_if(workspaces_.begin(), workspaces_.end(), [alloutputs](const auto& workspace) {\n          if (alloutputs) {\n            return hasFlag(workspace, \"focused\");\n          }\n          bool noNodes = workspace[\"nodes\"].empty() && workspace[\"floating_nodes\"].empty();\n          return hasFlag(workspace, \"visible\") || (workspace[\"output\"].isString() && noNodes);\n        });\n    if (it == workspaces_.end()) {\n      return true;\n    }\n    bool reverse_scroll = config_[\"reverse-scroll\"].isBool() && config_[\"reverse-scroll\"].asBool();\n    if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {\n      name = getCycleWorkspace(it, reverse_scroll ? true : false);\n    } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {\n      name = getCycleWorkspace(it, reverse_scroll ? false : true);\n    } else {\n      return true;\n    }\n    if (name == (*it)[\"name\"].asString()) {\n      return true;\n    }\n  }\n  if (!config_[\"warp-on-scroll\"].isNull() && !config_[\"warp-on-scroll\"].asBool()) {\n    ipc_.sendCmd(IPC_COMMAND, fmt::format(\"mouse_warping none\"));\n  }\n  try {\n    ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, \"--no-auto-back-and-forth\", name));\n  } catch (const std::exception& e) {\n    spdlog::error(\"Workspaces: {}\", e.what());\n  }\n  if (!config_[\"warp-on-scroll\"].isNull() && !config_[\"warp-on-scroll\"].asBool()) {\n    ipc_.sendCmd(IPC_COMMAND, fmt::format(\"mouse_warping container\"));\n  }\n  return true;\n}\n\nstd::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it, bool prev) const {\n  if (prev && it == workspaces_.begin() && !config_[\"disable-scroll-wraparound\"].asBool()) {\n    return (*(--workspaces_.end()))[\"name\"].asString();\n  }\n  if (prev && it != workspaces_.begin())\n    --it;\n  else if (!prev && it != workspaces_.end())\n    ++it;\n  if (!prev && it == workspaces_.end()) {\n    if (config_[\"disable-scroll-wraparound\"].asBool()) {\n      --it;\n    } else {\n      return (*(workspaces_.begin()))[\"name\"].asString();\n    }\n  }\n  return (*it)[\"name\"].asString();\n}\n\nstd::string Workspaces::trimWorkspaceName(const std::string& name) {\n  std::size_t found = name.find(':');\n  if (found != std::string::npos) {\n    return name.substr(found + 1);\n  }\n  return name;\n}\n\nbool is_focused_recursive(const Json::Value& node) {\n  // If a workspace has a focused container then get_tree will say\n  // that the workspace itself isn't focused.  Therefore we need to\n  // check if any of its nodes are focused as well.\n  // some layouts like tabbed have many nested nodes\n  // all nested nodes must be checked for focused flag\n  if (node[\"focused\"].asBool()) {\n    return true;\n  }\n\n  for (const auto& child : node[\"nodes\"]) {\n    if (is_focused_recursive(child)) {\n      return true;\n    }\n  }\n\n  for (const auto& child : node[\"floating_nodes\"]) {\n    if (is_focused_recursive(child)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid Workspaces::onButtonReady(const Json::Value& node, Gtk::Button& button) {\n  if (config_[\"current-only\"].asBool()) {\n    if (is_focused_recursive(node)) {\n      button.show();\n    } else {\n      button.hide();\n    }\n  } else {\n    button.show();\n  }\n}\n\n}  // namespace waybar::modules::sway\n"
  },
  {
    "path": "src/modules/systemd_failed_units.cpp",
    "content": "#include \"modules/systemd_failed_units.hpp\"\n\n#include <fmt/format.h>\n#include <giomm/dbusproxy.h>\n#include <glibmm/markup.h>\n#include <glibmm/variant.h>\n#include <spdlog/spdlog.h>\n\n#include <cstdint>\n#include <exception>\n#include <stdexcept>\n#include <tuple>\n\nstatic const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000;\n\nnamespace waybar::modules {\n\nSystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"systemd-failed-units\", id, \"{nr_failed} failed\", 1),\n      hide_on_ok_(true),\n      tooltip_format_(\n          \"System: {system_state}\\nUser: {user_state}\\nFailed units ({nr_failed}):\\n\"\n          \"{failed_units_list}\"),\n      tooltip_format_ok_(\"System: {system_state}\\nUser: {user_state}\"),\n      tooltip_unit_format_(\"{name}: {description}\"),\n      update_pending_(false),\n      nr_failed_system_(0),\n      nr_failed_user_(0),\n      nr_failed_(0),\n      last_status_() {\n  if (config[\"hide-on-ok\"].isBool()) {\n    hide_on_ok_ = config[\"hide-on-ok\"].asBool();\n  }\n  if (config[\"format-ok\"].isString()) {\n    format_ok_ = config[\"format-ok\"].asString();\n  } else {\n    format_ok_ = format_;\n  }\n\n  if (config[\"tooltip-format\"].isString()) {\n    tooltip_format_ = config[\"tooltip-format\"].asString();\n  }\n  if (config[\"tooltip-format-ok\"].isString()) {\n    tooltip_format_ok_ = config[\"tooltip-format-ok\"].asString();\n  }\n  if (config[\"tooltip-unit-format\"].isString()) {\n    tooltip_unit_format_ = config[\"tooltip-unit-format\"].asString();\n  }\n\n  /* Default to enable both \"system\" and \"user\". */\n  if (!config[\"system\"].isBool() || config[\"system\"].asBool()) {\n    system_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n        Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"org.freedesktop.systemd1\",\n        \"/org/freedesktop/systemd1\", \"org.freedesktop.DBus.Properties\");\n    if (!system_props_proxy_) {\n      throw std::runtime_error(\"Unable to connect to systemwide systemd DBus!\");\n    }\n    system_props_proxy_->signal_signal().connect(\n        sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));\n    try {\n      system_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n          Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"org.freedesktop.systemd1\",\n          \"/org/freedesktop/systemd1\", \"org.freedesktop.systemd1.Manager\");\n    } catch (const Glib::Error& e) {\n      spdlog::warn(\"Unable to connect to systemwide systemd Manager interface: {}\",\n                   e.what().c_str());\n    }\n  }\n  if (!config[\"user\"].isBool() || config[\"user\"].asBool()) {\n    user_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n        Gio::DBus::BusType::BUS_TYPE_SESSION, \"org.freedesktop.systemd1\",\n        \"/org/freedesktop/systemd1\", \"org.freedesktop.DBus.Properties\");\n    if (!user_props_proxy_) {\n      throw std::runtime_error(\"Unable to connect to user systemd DBus!\");\n    }\n    user_props_proxy_->signal_signal().connect(\n        sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb));\n    try {\n      user_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n          Gio::DBus::BusType::BUS_TYPE_SESSION, \"org.freedesktop.systemd1\",\n          \"/org/freedesktop/systemd1\", \"org.freedesktop.systemd1.Manager\");\n    } catch (const Glib::Error& e) {\n      spdlog::warn(\"Unable to connect to user systemd Manager interface: {}\", e.what().c_str());\n    }\n  }\n\n  updateData();\n  /* Always update for the first time. */\n  dp.emit();\n}\n\nauto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name,\n                                   const Glib::ustring& signal_name,\n                                   const Glib::VariantContainerBase& arguments) -> void {\n  if (signal_name == \"PropertiesChanged\" && !update_pending_) {\n    update_pending_ = true;\n    /* The fail count may fluctuate due to restarting. */\n    Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData),\n                                        UPDATE_DEBOUNCE_TIME_MS);\n  }\n}\n\nvoid SystemdFailedUnits::RequestSystemState() {\n  auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> std::string {\n    try {\n      if (!proxy) return \"unknown\";\n      auto parameters = Glib::VariantContainerBase(\n          g_variant_new(\"(ss)\", \"org.freedesktop.systemd1.Manager\", \"SystemState\"));\n      Glib::VariantContainerBase data = proxy->call_sync(\"Get\", parameters);\n      if (data && data.is_of_type(Glib::VariantType(\"(v)\"))) {\n        Glib::VariantBase variant;\n        g_variant_get(const_cast<GVariant*>(data.gobj()), \"(v)\", &variant);\n        if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) {\n          return g_variant_get_string(const_cast<GVariant*>(variant.gobj()), NULL);\n        }\n      }\n    } catch (Glib::Error& e) {\n      spdlog::error(\"Failed to get {} state: {}\", kind, e.what().c_str());\n    }\n    return \"unknown\";\n  };\n\n  system_state_ = load(\"systemwide\", system_props_proxy_);\n  user_state_ = load(\"user\", user_props_proxy_);\n  if (system_state_ == \"running\" && user_state_ == \"running\")\n    overall_state_ = \"ok\";\n  else\n    overall_state_ = \"degraded\";\n}\n\nvoid SystemdFailedUnits::RequestFailedUnits() {\n  auto load = [](const char* kind, Glib::RefPtr<Gio::DBus::Proxy>& proxy) -> uint32_t {\n    try {\n      if (!proxy) return 0;\n      auto parameters = Glib::VariantContainerBase(\n          g_variant_new(\"(ss)\", \"org.freedesktop.systemd1.Manager\", \"NFailedUnits\"));\n      Glib::VariantContainerBase data = proxy->call_sync(\"Get\", parameters);\n      if (data && data.is_of_type(Glib::VariantType(\"(v)\"))) {\n        Glib::VariantBase variant;\n        g_variant_get(const_cast<GVariant*>(data.gobj()), \"(v)\", &variant);\n        if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) {\n          return g_variant_get_uint32(const_cast<GVariant*>(variant.gobj()));\n        }\n      }\n    } catch (Glib::Error& e) {\n      spdlog::error(\"Failed to get {} failed units: {}\", kind, e.what().c_str());\n    }\n    return 0;\n  };\n\n  nr_failed_system_ = load(\"systemwide\", system_props_proxy_);\n  nr_failed_user_ = load(\"user\", user_props_proxy_);\n  nr_failed_ = nr_failed_system_ + nr_failed_user_;\n}\n\nvoid SystemdFailedUnits::RequestFailedUnitsList() {\n  failed_units_.clear();\n  if (!tooltipEnabled() || nr_failed_ == 0) {\n    return;\n  }\n\n  if (system_manager_proxy_) {\n    auto units = LoadFailedUnitsList(\"systemwide\", system_manager_proxy_, \"system\");\n    failed_units_.insert(failed_units_.end(), units.begin(), units.end());\n  }\n  if (user_manager_proxy_) {\n    auto units = LoadFailedUnitsList(\"user\", user_manager_proxy_, \"user\");\n    failed_units_.insert(failed_units_.end(), units.begin(), units.end());\n  }\n}\n\nauto SystemdFailedUnits::LoadFailedUnitsList(const char* kind,\n                                             Glib::RefPtr<Gio::DBus::Proxy>& proxy,\n                                             const std::string& scope) -> std::vector<FailedUnit> {\n  // org.freedesktop.systemd1.Manager.ListUnits returns\n  // (name, description, load_state, active_state, sub_state, followed, unit_path, job_id,\n  //  job_type, job_path).\n  using UnitRow = std::tuple<Glib::ustring, Glib::ustring, Glib::ustring, Glib::ustring,\n                             Glib::ustring, Glib::ustring, Glib::DBusObjectPathString, guint32,\n                             Glib::ustring, Glib::DBusObjectPathString>;\n  using ListUnitsReply = Glib::Variant<std::tuple<std::vector<UnitRow>>>;\n\n  std::vector<FailedUnit> units;\n  if (!proxy) {\n    return units;\n  }\n\n  try {\n    auto data = proxy->call_sync(\"ListUnits\");\n    if (!data) return units;\n    if (!data.is_of_type(ListUnitsReply::variant_type())) {\n      spdlog::warn(\"Unexpected DBus signature for ListUnits: {}\", data.get_type_string());\n      return units;\n    }\n\n    auto [rows] = Glib::VariantBase::cast_dynamic<ListUnitsReply>(data).get();\n    for (const auto& row : rows) {\n      const auto& name = std::get<0>(row);\n      const auto& description = std::get<1>(row);\n      const auto& load_state = std::get<2>(row);\n      const auto& active_state = std::get<3>(row);\n      const auto& sub_state = std::get<4>(row);\n      if (active_state == \"failed\" || sub_state == \"failed\") {\n        units.push_back({name, description, load_state, active_state, sub_state, scope});\n      }\n    }\n  } catch (const Glib::Error& e) {\n    spdlog::error(\"Failed to list {} units: {}\", kind, e.what().c_str());\n  }\n\n  return units;\n}\n\nstd::string SystemdFailedUnits::BuildTooltipFailedList() const {\n  if (failed_units_.empty()) {\n    return \"\";\n  }\n\n  std::string list;\n  list.reserve(failed_units_.size() * 16);\n  bool first = true;\n  for (const auto& unit : failed_units_) {\n    try {\n      auto line = fmt::format(\n          fmt::runtime(tooltip_unit_format_),\n          fmt::arg(\"name\", Glib::Markup::escape_text(unit.name).raw()),\n          fmt::arg(\"description\", Glib::Markup::escape_text(unit.description).raw()),\n          fmt::arg(\"load_state\", unit.load_state), fmt::arg(\"active_state\", unit.active_state),\n          fmt::arg(\"sub_state\", unit.sub_state), fmt::arg(\"scope\", unit.scope));\n      if (!first) {\n        list += \"\\n\";\n      }\n      first = false;\n      list += \"- \";\n      list += line;\n    } catch (const std::exception& e) {\n      spdlog::warn(\"Failed to format tooltip for unit {}: {}\", unit.name, e.what());\n    }\n  }\n\n  return list;\n}\n\nvoid SystemdFailedUnits::updateData() {\n  update_pending_ = false;\n\n  RequestSystemState();\n  if (overall_state_ == \"degraded\") {\n    RequestFailedUnits();\n    RequestFailedUnitsList();\n  } else {\n    nr_failed_system_ = nr_failed_user_ = nr_failed_ = 0;\n    failed_units_.clear();\n  }\n\n  dp.emit();\n}\n\nauto SystemdFailedUnits::update() -> void {\n  // Hide if needed.\n  if (overall_state_ == \"ok\" && hide_on_ok_) {\n    event_box_.set_visible(false);\n    last_status_ = overall_state_;\n    return;\n  }\n\n  event_box_.set_visible(true);\n\n  // Set state class.\n  if (!last_status_.empty() && label_.get_style_context()->has_class(last_status_)) {\n    label_.get_style_context()->remove_class(last_status_);\n  }\n  if (!label_.get_style_context()->has_class(overall_state_)) {\n    label_.get_style_context()->add_class(overall_state_);\n  }\n\n  last_status_ = overall_state_;\n\n  label_.set_markup(fmt::format(\n      fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg(\"nr_failed\", nr_failed_),\n      fmt::arg(\"nr_failed_system\", nr_failed_system_), fmt::arg(\"nr_failed_user\", nr_failed_user_),\n      fmt::arg(\"system_state\", system_state_), fmt::arg(\"user_state\", user_state_),\n      fmt::arg(\"overall_state\", overall_state_)));\n  if (tooltipEnabled()) {\n    std::string failed_list = BuildTooltipFailedList();\n    auto tooltip_template = overall_state_ == \"ok\" ? tooltip_format_ok_ : tooltip_format_;\n    if (!tooltip_template.empty()) {\n      label_.set_tooltip_markup(fmt::format(\n          fmt::runtime(tooltip_template), fmt::arg(\"nr_failed\", nr_failed_),\n          fmt::arg(\"nr_failed_system\", nr_failed_system_),\n          fmt::arg(\"nr_failed_user\", nr_failed_user_), fmt::arg(\"system_state\", system_state_),\n          fmt::arg(\"user_state\", user_state_), fmt::arg(\"overall_state\", overall_state_),\n          fmt::arg(\"failed_units_list\", failed_list)));\n    } else {\n      label_.set_tooltip_text(\"\");\n    }\n  }\n  ALabel::update();\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/temperature.cpp",
    "content": "#include \"modules/temperature.hpp\"\n\n#include <filesystem>\n#include <string>\n\n#if defined(__FreeBSD__)\n#include <sys/sysctl.h>\n#endif\n\nwaybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"temperature\", id, \"{temperatureC}°C\", 10) {\n#if defined(__FreeBSD__)\n// FreeBSD uses sysctlbyname instead of read from a file\n#else\n  auto traverseAsArray = [](const Json::Value& value, auto&& check_set_path) {\n    if (value.isString())\n      check_set_path(value.asString());\n    else if (value.isArray())\n      for (const auto& item : value)\n        if (check_set_path(item.asString())) break;\n  };\n\n  // if hwmon_path is an array, loop to find first valid item\n  traverseAsArray(config_[\"hwmon-path\"], [this](const std::string& path) {\n    if (!std::filesystem::exists(path)) return false;\n    file_path_ = path;\n    return true;\n  });\n\n  if (file_path_.empty() && config_[\"input-filename\"].isString()) {\n    // fallback to hwmon_paths-abs\n    traverseAsArray(config_[\"hwmon-path-abs\"], [this](const std::string& path) {\n      if (!std::filesystem::is_directory(path)) return false;\n      return std::ranges::any_of(\n          std::filesystem::directory_iterator(path), [this](const auto& hwmon) {\n            if (!hwmon.path().filename().string().starts_with(\"hwmon\")) return false;\n            file_path_ = hwmon.path().string() + \"/\" + config_[\"input-filename\"].asString();\n            return true;\n          });\n    });\n  }\n\n  if (file_path_.empty()) {\n    auto zone = config_[\"thermal-zone\"].isInt() ? config_[\"thermal-zone\"].asInt() : 0;\n    file_path_ = fmt::format(\"/sys/class/thermal/thermal_zone{}/temp\", zone);\n  }\n\n  // check if file_path_ can be used to retrieve the temperature\n  std::ifstream temp(file_path_);\n  if (!temp.is_open()) {\n    throw std::runtime_error(\"Can't open \" + file_path_);\n  }\n  if (!temp.good()) {\n    temp.close();\n    throw std::runtime_error(\"Can't read from \" + file_path_);\n  }\n  temp.close();\n#endif\n\n  thread_ = [this] {\n    dp.emit();\n    thread_.sleep_for(interval_);\n  };\n}\n\nauto waybar::modules::Temperature::update() -> void {\n  auto temperature = getTemperature();\n  uint16_t temperature_c = std::round(temperature);\n  uint16_t temperature_f = std::round(temperature * 1.8 + 32);\n  uint16_t temperature_k = std::round(temperature + 273.15);\n  auto critical = isCritical(temperature_c);\n  auto warning = isWarning(temperature_c);\n  auto format = format_;\n  if (critical) {\n    format = config_[\"format-critical\"].isString() ? config_[\"format-critical\"].asString() : format;\n    label_.get_style_context()->add_class(\"critical\");\n  } else {\n    label_.get_style_context()->remove_class(\"critical\");\n    if (warning) {\n      format = config_[\"format-warning\"].isString() ? config_[\"format-warning\"].asString() : format;\n      label_.get_style_context()->add_class(\"warning\");\n    } else {\n      label_.get_style_context()->remove_class(\"warning\");\n    }\n  }\n\n  if (format.empty()) {\n    event_box_.hide();\n    return;\n  }\n\n  event_box_.show();\n\n  auto max_temp = config_[\"critical-threshold\"].isInt() ? config_[\"critical-threshold\"].asInt() : 0;\n  label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg(\"temperatureC\", temperature_c),\n                                fmt::arg(\"temperatureF\", temperature_f),\n                                fmt::arg(\"temperatureK\", temperature_k),\n                                fmt::arg(\"icon\", getIcon(temperature_c, \"\", max_temp))));\n  if (tooltipEnabled()) {\n    std::string tooltip_format = \"{temperatureC}°C\";\n    if (config_[\"tooltip-format\"].isString()) {\n      tooltip_format = config_[\"tooltip-format\"].asString();\n    }\n    label_.set_tooltip_markup(fmt::format(\n        fmt::runtime(tooltip_format), fmt::arg(\"temperatureC\", temperature_c),\n        fmt::arg(\"temperatureF\", temperature_f), fmt::arg(\"temperatureK\", temperature_k)));\n  }\n  // Call parent update\n  ALabel::update();\n}\n\nfloat waybar::modules::Temperature::getTemperature() {\n#if defined(__FreeBSD__)\n  int temp;\n  size_t size = sizeof temp;\n\n  auto zone = config_[\"thermal-zone\"].isInt() ? config_[\"thermal-zone\"].asInt() : 0;\n\n  // First, try with dev.cpu\n  if ((sysctlbyname(fmt::format(\"dev.cpu.{}.temperature\", zone).c_str(), &temp, &size, NULL, 0) ==\n       0) ||\n      (sysctlbyname(fmt::format(\"hw.acpi.thermal.tz{}.temperature\", zone).c_str(), &temp, &size,\n                    NULL, 0) == 0)) {\n    auto temperature_c = ((float)temp - 2732) / 10;\n    return temperature_c;\n  }\n\n  throw std::runtime_error(fmt::format(\n      \"sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed\", zone, zone));\n\n#else  // Linux\n  std::ifstream temp(file_path_);\n  if (!temp.is_open()) {\n    throw std::runtime_error(\"Can't open \" + file_path_);\n  }\n  std::string line;\n  if (temp.good()) {\n    getline(temp, line);\n  } else {\n    temp.close();\n    throw std::runtime_error(\"Can't read from \" + file_path_);\n  }\n  temp.close();\n  auto temperature_c = std::strtol(line.c_str(), nullptr, 10) / 1000.0;\n  return temperature_c;\n#endif\n}\n\nbool waybar::modules::Temperature::isWarning(uint16_t temperature_c) {\n  return config_[\"warning-threshold\"].isInt() &&\n         temperature_c >= config_[\"warning-threshold\"].asInt();\n}\n\nbool waybar::modules::Temperature::isCritical(uint16_t temperature_c) {\n  return config_[\"critical-threshold\"].isInt() &&\n         temperature_c >= config_[\"critical-threshold\"].asInt();\n}\n"
  },
  {
    "path": "src/modules/upower.cpp",
    "content": "#include \"modules/upower.hpp\"\n\n#include <giomm/dbuswatchname.h>\n#include <gtkmm/tooltip.h>\n#include <spdlog/spdlog.h>\n\nnamespace waybar::modules {\n\nUPower::UPower(const std::string& id, const Json::Value& config)\n    : AIconLabel(config, \"upower\", id, \"{percentage}\", 0, true, true, true), sleeping_{false} {\n  box_.set_name(name_);\n  box_.set_spacing(0);\n  // Tooltip box\n  contentBox_.set_orientation((box_.get_orientation() == Gtk::ORIENTATION_HORIZONTAL)\n                                  ? Gtk::ORIENTATION_VERTICAL\n                                  : Gtk::ORIENTATION_HORIZONTAL);\n  // Get current theme\n  gtkTheme_ = Gtk::IconTheme::get_default();\n\n  // Icon Size\n  if (config_[\"icon-size\"].isInt()) {\n    iconSize_ = config_[\"icon-size\"].asInt();\n  }\n  image_.set_pixel_size(iconSize_);\n\n  // Show icon only when \"show-icon\" isn't set to false\n  if (config_[\"show-icon\"].isBool()) showIcon_ = config_[\"show-icon\"].asBool();\n  if (!showIcon_) box_.remove(image_);\n  // Device user wants\n  if (config_[\"native-path\"].isString()) nativePath_ = config_[\"native-path\"].asString();\n  // Device model user wants\n  if (config_[\"model\"].isString()) model_ = config_[\"model\"].asString();\n\n  // Hide If Empty\n  if (config_[\"hide-if-empty\"].isBool()) hideIfEmpty_ = config_[\"hide-if-empty\"].asBool();\n\n  // Tooltip Spacing\n  if (config_[\"tooltip-spacing\"].isInt()) tooltip_spacing_ = config_[\"tooltip-spacing\"].asInt();\n\n  // Tooltip Padding\n  if (config_[\"tooltip-padding\"].isInt()) {\n    tooltip_padding_ = config_[\"tooltip-padding\"].asInt();\n    contentBox_.set_margin_top(tooltip_padding_);\n    contentBox_.set_margin_bottom(tooltip_padding_);\n    contentBox_.set_margin_left(tooltip_padding_);\n    contentBox_.set_margin_right(tooltip_padding_);\n  }\n\n  // Tooltip Format\n  if (config_[\"tooltip-format\"].isString()) tooltipFormat_ = config_[\"tooltip-format\"].asString();\n\n  // Start watching DBUS\n  watcherID_ = Gio::DBus::watch_name(\n      Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"org.freedesktop.UPower\",\n      sigc::mem_fun(*this, &UPower::onAppear), sigc::mem_fun(*this, &UPower::onVanished),\n      Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START);\n  // Get DBus async connect\n  Gio::DBus::Connection::get(Gio::DBus::BusType::BUS_TYPE_SYSTEM,\n                             sigc::mem_fun(*this, &UPower::getConn_cb));\n\n  // Make UPower client\n  GError* gErr = NULL;\n  upClient_ = up_client_new_full(NULL, &gErr);\n  if (upClient_ == NULL) {\n    spdlog::error(\"Upower. UPower client connection error. {}\",\n                  gErr ? gErr->message : \"unknown error\");\n    if (gErr) g_error_free(gErr);\n  }\n\n  // Subscribe UPower events\n  g_signal_connect(upClient_, \"device-added\", G_CALLBACK(deviceAdded_cb), this);\n  g_signal_connect(upClient_, \"device-removed\", G_CALLBACK(deviceRemoved_cb), this);\n\n  // Subscribe tooltip query events\n  box_.set_has_tooltip(AModule::tooltipEnabled());\n  if (AModule::tooltipEnabled()) {\n    box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);\n  }\n\n  resetDevices();\n  setDisplayDevice();\n  // Update the widget\n  dp.emit();\n}\n\nUPower::~UPower() {\n  if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);\n  if (upClient_ != NULL) g_object_unref(upClient_);\n  if (subscrID_ > 0u) {\n    conn_->signal_unsubscribe(subscrID_);\n    subscrID_ = 0u;\n  }\n  Gio::DBus::unwatch_name(watcherID_);\n  watcherID_ = 0u;\n  removeDevices();\n}\n\nstatic std::string_view getDeviceStatus(UpDeviceState& state) {\n  switch (state) {\n    case UP_DEVICE_STATE_CHARGING:\n    case UP_DEVICE_STATE_PENDING_CHARGE:\n      return \"charging\";\n    case UP_DEVICE_STATE_DISCHARGING:\n    case UP_DEVICE_STATE_PENDING_DISCHARGE:\n      return \"discharging\";\n    case UP_DEVICE_STATE_FULLY_CHARGED:\n      return \"full\";\n    case UP_DEVICE_STATE_EMPTY:\n      return \"empty\";\n    default:\n      return \"unknown-status\";\n  }\n}\n\nstatic std::string_view getDeviceIcon(UpDeviceKind& kind) {\n  switch (kind) {\n    case UP_DEVICE_KIND_LINE_POWER:\n      return \"ac-adapter-symbolic\";\n    case UP_DEVICE_KIND_BATTERY:\n      return \"battery-symbolic\";\n    case UP_DEVICE_KIND_UPS:\n      return \"uninterruptible-power-supply-symbolic\";\n    case UP_DEVICE_KIND_MONITOR:\n      return \"video-display-symbolic\";\n    case UP_DEVICE_KIND_MOUSE:\n      return \"input-mouse-symbolic\";\n    case UP_DEVICE_KIND_KEYBOARD:\n      return \"input-keyboard-symbolic\";\n    case UP_DEVICE_KIND_PDA:\n      return \"pda-symbolic\";\n    case UP_DEVICE_KIND_PHONE:\n      return \"phone-symbolic\";\n    case UP_DEVICE_KIND_MEDIA_PLAYER:\n      return \"multimedia-player-symbolic\";\n    case UP_DEVICE_KIND_TABLET:\n      return \"computer-apple-ipad-symbolic\";\n    case UP_DEVICE_KIND_COMPUTER:\n      return \"computer-symbolic\";\n    case UP_DEVICE_KIND_GAMING_INPUT:\n      return \"input-gaming-symbolic\";\n    case UP_DEVICE_KIND_PEN:\n      return \"input-tablet-symbolic\";\n    case UP_DEVICE_KIND_TOUCHPAD:\n      return \"input-touchpad-symbolic\";\n    case UP_DEVICE_KIND_MODEM:\n      return \"modem-symbolic\";\n    case UP_DEVICE_KIND_NETWORK:\n      return \"network-wired-symbolic\";\n    case UP_DEVICE_KIND_HEADSET:\n      return \"audio-headset-symbolic\";\n    case UP_DEVICE_KIND_HEADPHONES:\n      return \"audio-headphones-symbolic\";\n    case UP_DEVICE_KIND_OTHER_AUDIO:\n    case UP_DEVICE_KIND_SPEAKERS:\n      return \"audio-speakers-symbolic\";\n    case UP_DEVICE_KIND_VIDEO:\n      return \"camera-web-symbolic\";\n    case UP_DEVICE_KIND_PRINTER:\n      return \"printer-symbolic\";\n    case UP_DEVICE_KIND_SCANNER:\n      return \"scanner-symbolic\";\n    case UP_DEVICE_KIND_CAMERA:\n      return \"camera-photo-symbolic\";\n    case UP_DEVICE_KIND_BLUETOOTH_GENERIC:\n      return \"bluetooth-active-symbolic\";\n    case UP_DEVICE_KIND_TOY:\n    case UP_DEVICE_KIND_REMOTE_CONTROL:\n    case UP_DEVICE_KIND_WEARABLE:\n    case UP_DEVICE_KIND_LAST:\n    default:\n      return \"battery-symbolic\";\n  }\n}\n\nstatic std::string secondsToString(const std::chrono::seconds sec) {\n  const auto ds{std::chrono::duration_cast<std::chrono::days>(sec)};\n  const auto hrs{std::chrono::duration_cast<std::chrono::hours>(sec - ds)};\n  const auto min{std::chrono::duration_cast<std::chrono::minutes>(sec - ds - hrs)};\n  std::string_view strRet{(ds.count() > 0)    ? \"{D}d {H}h {M}min\"\n                          : (hrs.count() > 0) ? \"{H}h {M}min\"\n                          : (min.count() > 0) ? \"{M}min\"\n                                              : \"\"};\n  spdlog::debug(\n      \"UPower::secondsToString(). seconds: \\\"{0}\\\", minutes: \\\"{1}\\\", hours: \\\"{2}\\\", \\\ndays: \\\"{3}\\\", strRet: \\\"{4}\\\"\",\n      sec.count(), min.count(), hrs.count(), ds.count(), strRet);\n  return fmt::format(fmt::runtime(strRet), fmt::arg(\"D\", ds.count()), fmt::arg(\"H\", hrs.count()),\n                     fmt::arg(\"M\", min.count()));\n}\n\nauto UPower::update() -> void {\n  std::lock_guard<std::mutex> guard{mutex_};\n  // Don't update widget if the UPower service isn't running\n  if (!upRunning_ || sleeping_) {\n    if (hideIfEmpty_) box_.hide();\n    return;\n  }\n\n  getUpDeviceInfo(upDevice_);\n\n  if (upDevice_.upDevice == NULL && hideIfEmpty_) {\n    box_.hide();\n    return;\n  }\n  /* Every Device which is handled by Upower and which is not\n   * UP_DEVICE_KIND_UNKNOWN (0) or UP_DEVICE_KIND_LINE_POWER (1) is a Battery\n   */\n  const bool upDeviceValid{upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&\n                           upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER};\n  // Get CSS status\n  const auto status{getDeviceStatus(upDevice_.state)};\n  // Remove last status if it exists\n  if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_))\n    box_.get_style_context()->remove_class(lastStatus_);\n  if (!box_.get_style_context()->has_class(std::string(status)))\n    box_.get_style_context()->add_class(std::string(status));\n  lastStatus_ = status;\n\n  if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) {\n    box_.hide();\n    // Call parent update\n    AModule::update();\n    return;\n  }\n\n  label_.set_markup(getText(upDevice_, format_));\n  // Set icon\n  if (upDevice_.icon_name == NULL || !gtkTheme_->has_icon(upDevice_.icon_name))\n    upDevice_.icon_name = (char*)NO_BATTERY.c_str();\n  image_.set_from_icon_name(upDevice_.icon_name, Gtk::ICON_SIZE_INVALID);\n\n  box_.show();\n\n  // Call parent update\n  ALabel::update();\n}\n\nvoid UPower::getConn_cb(Glib::RefPtr<Gio::AsyncResult>& result) {\n  try {\n    conn_ = Gio::DBus::Connection::get_finish(result);\n    // Subscribe DBUs events\n    subscrID_ = conn_->signal_subscribe(sigc::mem_fun(*this, &UPower::prepareForSleep_cb),\n                                        \"org.freedesktop.login1\", \"org.freedesktop.login1.Manager\",\n                                        \"PrepareForSleep\", \"/org/freedesktop/login1\");\n\n  } catch (const Glib::Error& e) {\n    spdlog::error(\"Upower. DBus connection error. {}\", e.what().c_str());\n  }\n}\n\nvoid UPower::onAppear(const Glib::RefPtr<Gio::DBus::Connection>& conn, const Glib::ustring& name,\n                      const Glib::ustring& name_owner) {\n  upRunning_ = true;\n}\n\nvoid UPower::onVanished(const Glib::RefPtr<Gio::DBus::Connection>& conn,\n                        const Glib::ustring& name) {\n  upRunning_ = false;\n}\n\nvoid UPower::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection>& connection,\n                                const Glib::ustring& sender_name, const Glib::ustring& object_path,\n                                const Glib::ustring& interface_name,\n                                const Glib::ustring& signal_name,\n                                const Glib::VariantContainerBase& parameters) {\n  if (parameters.is_of_type(Glib::VariantType(\"(b)\"))) {\n    Glib::Variant<bool> sleeping;\n    parameters.get_child(sleeping, 0);\n    if (!sleeping.get()) {\n      resetDevices();\n      setDisplayDevice();\n      sleeping_ = false;\n      // Update the widget\n      dp.emit();\n    } else\n      sleeping_ = true;\n  }\n}\n\nvoid UPower::deviceAdded_cb(UpClient* client, UpDevice* device, gpointer data) {\n  UPower* up{static_cast<UPower*>(data)};\n  up->addDevice(device);\n  up->setDisplayDevice();\n  // Update the widget\n  up->dp.emit();\n}\n\nvoid UPower::deviceRemoved_cb(UpClient* client, const gchar* objectPath, gpointer data) {\n  UPower* up{static_cast<UPower*>(data)};\n  up->removeDevice(objectPath);\n  up->setDisplayDevice();\n  // Update the widget\n  up->dp.emit();\n}\n\nvoid UPower::deviceNotify_cb(UpDevice* device, GParamSpec* pspec, gpointer data) {\n  UPower* up{static_cast<UPower*>(data)};\n  // Update the widget\n  up->dp.emit();\n}\n\nvoid UPower::addDevice(UpDevice* device) {\n  std::lock_guard<std::mutex> guard{mutex_};\n\n  if (G_IS_OBJECT(device)) {\n    const gchar* objectPath{up_device_get_object_path(device)};\n\n    // Due to the device getting cleared after this event is fired, we\n    // create a new object pointing to its objectPath\n    device = up_device_new();\n    upDevice_output upDevice{.upDevice = device};\n    gboolean ret{up_device_set_object_path_sync(device, objectPath, NULL, NULL)};\n    if (!ret) {\n      g_object_unref(G_OBJECT(device));\n      return;\n    }\n\n    if (devices_.find(objectPath) != devices_.cend()) {\n      auto upDevice{devices_[objectPath]};\n      if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);\n      devices_.erase(objectPath);\n    }\n\n    g_signal_connect(device, \"notify\", G_CALLBACK(deviceNotify_cb), this);\n    devices_.emplace(Devices::value_type(objectPath, upDevice));\n  }\n}\n\nvoid UPower::removeDevice(const gchar* objectPath) {\n  std::lock_guard<std::mutex> guard{mutex_};\n  if (devices_.find(objectPath) != devices_.cend()) {\n    auto upDevice{devices_[objectPath]};\n    if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);\n    devices_.erase(objectPath);\n  }\n}\n\nvoid UPower::removeDevices() {\n  std::lock_guard<std::mutex> guard{mutex_};\n  if (!devices_.empty()) {\n    auto it{devices_.cbegin()};\n    while (it != devices_.cend()) {\n      if (G_IS_OBJECT(it->second.upDevice)) g_object_unref(it->second.upDevice);\n      devices_.erase(it++);\n    }\n  }\n}\n\n// Removes all devices and adds the current devices\nvoid UPower::resetDevices() {\n  // Remove all devices\n  removeDevices();\n\n  // Adds all devices\n  GPtrArray* newDevices = up_client_get_devices2(upClient_);\n  if (newDevices != NULL)\n    for (guint i{0}; i < newDevices->len; ++i) {\n      UpDevice* device{(UpDevice*)g_ptr_array_index(newDevices, i)};\n      if (device && G_IS_OBJECT(device)) addDevice(device);\n    }\n}\n\nvoid UPower::setDisplayDevice() {\n  std::lock_guard<std::mutex> guard{mutex_};\n\n  if (upDevice_.upDevice != NULL) {\n    g_object_unref(upDevice_.upDevice);\n    upDevice_.upDevice = NULL;\n  }\n\n  if (nativePath_.empty() && model_.empty()) {\n    upDevice_.upDevice = up_client_get_display_device(upClient_);\n    getUpDeviceInfo(upDevice_);\n  } else {\n    g_ptr_array_foreach(\n        up_client_get_devices2(upClient_),\n        [](gpointer data, gpointer user_data) {\n          upDevice_output upDevice;\n          auto thisPtr{static_cast<UPower*>(user_data)};\n          upDevice.upDevice = static_cast<UpDevice*>(data);\n          thisPtr->getUpDeviceInfo(upDevice);\n          upDevice_output displayDevice{NULL};\n          if (!thisPtr->nativePath_.empty()) {\n            if (upDevice.nativePath == nullptr) return;\n            if (0 == std::strcmp(upDevice.nativePath, thisPtr->nativePath_.c_str())) {\n              displayDevice = upDevice;\n            }\n          } else {\n            if (upDevice.model == nullptr) return;\n            if (0 == std::strcmp(upDevice.model, thisPtr->model_.c_str())) {\n              displayDevice = upDevice;\n            }\n          }\n          // Unref current upDevice if it exists\n          if (displayDevice.upDevice != NULL) {\n            thisPtr->upDevice_ = displayDevice;\n          }\n        },\n        this);\n  }\n\n  if (upDevice_.upDevice != NULL)\n    g_signal_connect(upDevice_.upDevice, \"notify\", G_CALLBACK(deviceNotify_cb), this);\n}\n\nvoid UPower::getUpDeviceInfo(upDevice_output& upDevice_) {\n  if (upDevice_.upDevice != NULL && G_IS_OBJECT(upDevice_.upDevice)) {\n    g_object_get(upDevice_.upDevice, \"kind\", &upDevice_.kind, \"state\", &upDevice_.state,\n                 \"percentage\", &upDevice_.percentage, \"icon-name\", &upDevice_.icon_name,\n                 \"time-to-empty\", &upDevice_.time_empty, \"time-to-full\", &upDevice_.time_full,\n                 \"temperature\", &upDevice_.temperature, \"native-path\", &upDevice_.nativePath,\n                 \"model\", &upDevice_.model, NULL);\n    spdlog::debug(\n        \"UPower. getUpDeviceInfo. kind: \\\"{0}\\\". state: \\\"{1}\\\". percentage: \\\"{2}\\\". \\\nicon_name: \\\"{3}\\\". time-to-empty: \\\"{4}\\\". time-to-full: \\\"{5}\\\". temperature: \\\"{6}\\\". \\\nnative_path: \\\"{7}\\\". model: \\\"{8}\\\"\",\n        fmt::format_int(upDevice_.kind).str(), fmt::format_int(upDevice_.state).str(),\n        upDevice_.percentage, upDevice_.icon_name, upDevice_.time_empty, upDevice_.time_full,\n        upDevice_.temperature, upDevice_.nativePath, upDevice_.model);\n  }\n}\n\nconst Glib::ustring UPower::getText(const upDevice_output& upDevice_, const std::string& format) {\n  Glib::ustring ret{\"\"};\n  if (upDevice_.upDevice != NULL) {\n    std::string timeStr{\"\"};\n    switch (upDevice_.state) {\n      case UP_DEVICE_STATE_CHARGING:\n      case UP_DEVICE_STATE_PENDING_CHARGE:\n        timeStr = secondsToString(std::chrono::seconds(upDevice_.time_full));\n        break;\n      case UP_DEVICE_STATE_DISCHARGING:\n      case UP_DEVICE_STATE_PENDING_DISCHARGE:\n        timeStr = secondsToString(std::chrono::seconds(upDevice_.time_empty));\n        break;\n      default:\n        break;\n    }\n\n    ret = fmt::format(\n        fmt::runtime(format),\n        fmt::arg(\"percentage\", std::to_string((int)std::round(upDevice_.percentage)) + '%'),\n        fmt::arg(\"time\", timeStr),\n        fmt::arg(\"temperature\", fmt::format(\"{:-.2g}C\", upDevice_.temperature)),\n        fmt::arg(\"model\", upDevice_.model), fmt::arg(\"native-path\", upDevice_.nativePath));\n  }\n\n  return ret;\n}\n\nbool UPower::queryTooltipCb(int x, int y, bool keyboard_tooltip,\n                            const Glib::RefPtr<Gtk::Tooltip>& tooltip) {\n  std::lock_guard<std::mutex> guard{mutex_};\n\n  // Clear content box\n  contentBox_.forall([this](Gtk::Widget& wg) { contentBox_.remove(wg); });\n\n  // Fill content box with the content\n  for (auto pairDev : devices_) {\n    // Get device info\n    getUpDeviceInfo(pairDev.second);\n\n    if (pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&\n        pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER) {\n      // Make box record\n      Gtk::Box* boxRec{new Gtk::Box{box_.get_orientation(), tooltip_spacing_}};\n      contentBox_.add(*boxRec);\n      Gtk::Box* boxDev{new Gtk::Box{box_.get_orientation()}};\n      Gtk::Box* boxUsr{new Gtk::Box{box_.get_orientation()}};\n      boxRec->add(*boxDev);\n      boxRec->add(*boxUsr);\n      // Construct device box\n      // Set icon from kind\n      std::string iconNameDev{getDeviceIcon(pairDev.second.kind)};\n      if (!gtkTheme_->has_icon(iconNameDev)) iconNameDev = (char*)NO_BATTERY.c_str();\n      Gtk::Image* iconDev{new Gtk::Image{}};\n      iconDev->set_from_icon_name(iconNameDev, Gtk::ICON_SIZE_INVALID);\n      iconDev->set_pixel_size(iconSize_);\n      boxDev->add(*iconDev);\n      // Set label from model\n      Gtk::Label* labelDev{new Gtk::Label{pairDev.second.model}};\n      boxDev->add(*labelDev);\n      // Construct user box\n      // Set icon from icon state\n      if (pairDev.second.icon_name == NULL || !gtkTheme_->has_icon(pairDev.second.icon_name))\n        pairDev.second.icon_name = (char*)NO_BATTERY.c_str();\n      Gtk::Image* iconTooltip{new Gtk::Image{}};\n      iconTooltip->set_from_icon_name(pairDev.second.icon_name, Gtk::ICON_SIZE_INVALID);\n      iconTooltip->set_pixel_size(iconSize_);\n      boxUsr->add(*iconTooltip);\n      // Set markup text\n      Gtk::Label* labelTooltip{new Gtk::Label{}};\n      labelTooltip->set_markup(getText(pairDev.second, tooltipFormat_));\n      boxUsr->add(*labelTooltip);\n    }\n  }\n  tooltip->set_custom(contentBox_);\n  contentBox_.show_all();\n\n  return true;\n}\n\n}  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/user.cpp",
    "content": "#include \"modules/user.hpp\"\n\n#include <fmt/chrono.h>\n#include <glibmm/miscutils.h>\n#include <unistd.h>\n\n#include <algorithm>\n#include <chrono>\n\n#include \"gdkmm/cursor.h\"\n#include \"gdkmm/event.h\"\n#include \"gdkmm/types.h\"\n#include \"glibmm/fileutils.h\"\n#include \"sigc++/functors/mem_fun.h\"\n#include \"sigc++/functors/ptr_fun.h\"\n\n#if HAVE_CPU_LINUX\n#include <sys/sysinfo.h>\n#endif\n\n#if HAVE_CPU_BSD\n#include <time.h>\n#endif\n\nconst static int LEFT_MOUSE_BUTTON_CODE = 1;\n\nnamespace waybar::modules {\nUser::User(const std::string& id, const Json::Value& config)\n    : AIconLabel(config, \"user\", id, \"{user} {work_H}:{work_M}\", 60, false, true, true) {\n  AIconLabel::box_.set_spacing(0);\n  if (AIconLabel::iconEnabled()) {\n    this->init_avatar(AIconLabel::config_);\n  }\n  this->init_update_worker();\n}\n\nbool User::handleToggle(GdkEventButton* const& e) {\n  if (AIconLabel::config_[\"open-on-click\"].isBool() &&\n      AIconLabel::config_[\"open-on-click\"].asBool() && e->button == LEFT_MOUSE_BUTTON_CODE) {\n    std::string openPath = this->get_user_home_dir();\n    if (AIconLabel::config_[\"open-path\"].isString()) {\n      std::string customPath = AIconLabel::config_[\"open-path\"].asString();\n      if (!customPath.empty()) {\n        openPath = std::move(customPath);\n      }\n    }\n\n    Gio::AppInfo::launch_default_for_uri(\"file:///\" + openPath);\n  }\n  return true;\n}\n\nlong User::uptime_as_seconds() {\n  long uptime = 0;\n\n#if HAVE_CPU_LINUX\n  struct sysinfo s_info;\n  if (0 == sysinfo(&s_info)) {\n    uptime = s_info.uptime;\n  }\n#endif\n\n#if HAVE_CPU_BSD\n  struct timespec s_info;\n  int flags = 0;\n#ifndef __OpenBSD__\n  flags = CLOCK_UPTIME_PRECISE;\n#else\n  flags = CLOCK_UPTIME;\n#endif\n  if (0 == clock_gettime(flags, &s_info)) {\n    uptime = s_info.tv_sec;\n  }\n#endif\n\n  return uptime;\n}\n\nstd::string User::get_user_login() const { return Glib::get_user_name(); }\n\nstd::string User::get_user_home_dir() const { return Glib::get_home_dir(); }\n\nvoid User::init_update_worker() {\n  this->thread_ = [this] {\n    ALabel::dp.emit();\n    auto now = std::chrono::system_clock::now();\n    auto diff = now.time_since_epoch() % ALabel::interval_;\n    this->thread_.sleep_for(ALabel::interval_ - diff);\n  };\n}\n\nvoid User::init_avatar(const Json::Value& config) {\n  int height =\n      config[\"height\"].isUInt() ? config[\"height\"].asUInt() : this->defaultUserImageHeight_;\n  int width = config[\"width\"].isUInt() ? config[\"width\"].asUInt() : this->defaultUserImageWidth_;\n\n  if (config[\"avatar\"].isString()) {\n    std::string userAvatar = config[\"avatar\"].asString();\n    if (!userAvatar.empty()) {\n      this->init_user_avatar(userAvatar, width, height);\n      return;\n    }\n  }\n\n  this->init_default_user_avatar(width, width);\n}\n\nstd::string User::get_default_user_avatar_path() const {\n  return this->get_user_home_dir() + \"/\" + \".face\";\n}\n\nvoid User::init_default_user_avatar(int width, int height) {\n  this->init_user_avatar(this->get_default_user_avatar_path(), width, height);\n}\n\nvoid User::init_user_avatar(const std::string& path, int width, int height) {\n  if (Glib::file_test(path, Glib::FILE_TEST_EXISTS)) {\n    Glib::RefPtr<Gdk::Pixbuf> pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);\n    AIconLabel::image_.set(pixbuf_);\n  } else {\n    AIconLabel::box_.remove(AIconLabel::image_);\n  }\n}\n\nauto User::update() -> void {\n  std::string systemUser = this->get_user_login();\n  std::transform(systemUser.cbegin(), systemUser.cend(), systemUser.begin(),\n                 [](unsigned char c) { return std::toupper(c); });\n\n  long uptimeSeconds = this->uptime_as_seconds();\n  auto workSystemTimeSeconds = std::chrono::seconds(uptimeSeconds);\n  auto currentSystemTime = std::chrono::system_clock::now();\n  auto startSystemTime = currentSystemTime - workSystemTimeSeconds;\n  long workSystemDays = uptimeSeconds / 86400;\n\n  auto label = fmt::format(\n      fmt::runtime(ALabel::format_), fmt::arg(\"up_H\", fmt::format(\"{:%H}\", startSystemTime)),\n      fmt::arg(\"up_M\", fmt::format(\"{:%M}\", startSystemTime)),\n      fmt::arg(\"up_d\", fmt::format(\"{:%d}\", startSystemTime)),\n      fmt::arg(\"up_m\", fmt::format(\"{:%m}\", startSystemTime)),\n      fmt::arg(\"up_Y\", fmt::format(\"{:%Y}\", startSystemTime)), fmt::arg(\"work_d\", workSystemDays),\n      fmt::arg(\"work_H\", fmt::format(\"{:%H}\", workSystemTimeSeconds)),\n      fmt::arg(\"work_M\", fmt::format(\"{:%M}\", workSystemTimeSeconds)),\n      fmt::arg(\"work_S\", fmt::format(\"{:%S}\", workSystemTimeSeconds)),\n      fmt::arg(\"user\", systemUser));\n  ALabel::label_.set_markup(label);\n  AIconLabel::update();\n}\n};  // namespace waybar::modules\n"
  },
  {
    "path": "src/modules/wayfire/backend.cpp",
    "content": "#include \"modules/wayfire/backend.hpp\"\n\n#include <json/json.h>\n#include <spdlog/spdlog.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n\n#include <algorithm>\n#include <bit>\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>\n#include <exception>\n#include <ranges>\n#include <thread>\n\nnamespace waybar::modules::wayfire {\n\nstd::weak_ptr<IPC> IPC::instance;\n\n// C++23: std::byteswap\ninline auto byteswap(uint32_t x) -> uint32_t {\n  return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 |\n         (x & 0x000000ff) << 24;\n}\n\nauto pack_and_write(Sock& sock, std::string&& buf) -> void {\n  uint32_t len = buf.size();\n  if constexpr (std::endian::native != std::endian::little) len = byteswap(len);\n  (void)write(sock, &len, 4);\n  (void)write(sock, buf.data(), buf.size());\n}\n\nauto read_exact(Sock& sock, size_t n) -> std::string {\n  auto buf = std::string(n, 0);\n  for (size_t i = 0; i < n;) {\n    auto r = read(sock, &buf[i], n - i);\n    if (r <= 0) {\n      throw std::runtime_error(\"Wayfire IPC: read failed\");\n    }\n    i += static_cast<size_t>(r);\n  }\n  return buf;\n}\n\n// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438\ninline auto is_mapped_toplevel_view(const Json::Value& view) -> bool {\n  return view[\"mapped\"].asBool() && view[\"role\"] != \"desktop-environment\" &&\n         view[\"pid\"].asInt() != -1;\n}\n\nauto State::Wset::count_ws(const Json::Value& pos) -> Workspace& {\n  auto x = pos[\"x\"].asInt();\n  auto y = pos[\"y\"].asInt();\n  return wss.at(ws_w * y + x);\n}\n\nauto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& {\n  return const_cast<Workspace&>(std::as_const(*this).locate_ws(geo));\n}\n\nauto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& {\n  if (!output.has_value()) {\n    throw std::runtime_error(\"Wayfire IPC: wset has no output assigned\");\n  }\n  const auto& out = output.value().get();\n  auto [qx, rx] = std::div(geo[\"x\"].asInt(), out.w);\n  auto [qy, ry] = std::div(geo[\"y\"].asInt(), out.h);\n  auto x = std::max(0, (int)ws_x + qx - int{rx < 0});\n  auto y = std::max(0, (int)ws_y + qy - int{ry < 0});\n  return wss.at(ws_w * y + x);\n}\n\nauto State::update_view(const Json::Value& view) -> void {\n  auto id = view[\"id\"].asUInt();\n\n  // erase old view information\n  if (views.contains(id)) {\n    auto& old_view = views.at(id);\n    auto& ws = wsets.at(old_view[\"wset-index\"].asUInt()).locate_ws(old_view[\"geometry\"]);\n    ws.num_views--;\n    if (old_view[\"sticky\"].asBool()) ws.num_sticky_views--;\n    views.erase(id);\n  }\n\n  // insert or assign new view information\n  if (is_mapped_toplevel_view(view)) {\n    try {\n      // view[\"wset-index\"] could be messed up\n      auto& ws = wsets.at(view[\"wset-index\"].asUInt()).locate_ws(view[\"geometry\"]);\n      ws.num_views++;\n      if (view[\"sticky\"].asBool()) ws.num_sticky_views++;\n      views.emplace(id, view);\n    } catch (const std::exception&) {\n    }\n  }\n}\n\nauto IPC::get_instance() -> std::shared_ptr<IPC> {\n  auto p = instance.lock();\n  if (!p) {\n    instance = p = std::shared_ptr<IPC>(new IPC);\n    p->start();\n  }\n  return p;\n}\n\nauto IPC::connect() -> Sock {\n  auto* path = std::getenv(\"WAYFIRE_SOCKET\");\n  if (path == nullptr) {\n    throw std::runtime_error{\"Wayfire IPC: ipc not available\"};\n  }\n\n  util::ScopedFd sock(socket(AF_UNIX, SOCK_STREAM, 0));\n  if (sock == -1) {\n    throw std::runtime_error{\"Wayfire IPC: socket() failed\"};\n  }\n\n  auto addr = sockaddr_un{.sun_family = AF_UNIX};\n  std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);\n  addr.sun_path[sizeof(addr.sun_path) - 1] = 0;\n\n  if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) {\n    throw std::runtime_error{\"Wayfire IPC: connect() failed\"};\n  }\n\n  return sock;\n}\n\nauto IPC::receive(Sock& sock) -> Json::Value {\n  auto len_buf = read_exact(sock, 4);\n  uint32_t len;\n  std::memcpy(&len, len_buf.data(), sizeof(len));\n  if constexpr (std::endian::native != std::endian::little) len = byteswap(len);\n  auto buf = read_exact(sock, len);\n\n  Json::Value json;\n  std::string err;\n  auto* reader = reader_builder.newCharReader();\n  if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) {\n    throw std::runtime_error{\"Wayfire IPC: parse json failed: \" + err};\n  }\n  return json;\n}\n\nauto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value {\n  spdlog::debug(\"Wayfire IPC: send method \\\"{}\\\"\", method);\n  auto sock = connect();\n\n  Json::Value json;\n  json[\"method\"] = method;\n  json[\"data\"] = std::move(data);\n\n  pack_and_write(sock, Json::writeString(writer_builder, json));\n  auto res = receive(sock);\n  root_event_handler(method, res);\n  return res;\n}\n\nauto IPC::start() -> void {\n  spdlog::info(\"Wayfire IPC: starting\");\n\n  // init state\n  send(\"window-rules/list-outputs\", {});\n  send(\"window-rules/list-wsets\", {});\n  send(\"window-rules/list-views\", {});\n  send(\"window-rules/get-focused-view\", {});\n  send(\"window-rules/get-focused-output\", {});\n\n  std::thread([self = shared_from_this()] {\n    auto sock = connect();\n\n    {\n      Json::Value json;\n      json[\"method\"] = \"window-rules/events/watch\";\n\n      pack_and_write(sock, Json::writeString(self->writer_builder, json));\n      if (self->receive(sock)[\"result\"] != \"ok\") {\n        spdlog::error(\n            \"Wayfire IPC: method \\\"window-rules/events/watch\\\"\"\n            \" have failed\");\n        return;\n      }\n    }\n\n    while (auto json = self->receive(sock)) {\n      auto ev = json[\"event\"].asString();\n      spdlog::debug(\"Wayfire IPC: received event \\\"{}\\\"\", ev);\n      self->root_event_handler(ev, json);\n    }\n  }).detach();\n}\n\nauto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void {\n  auto _ = std::lock_guard{handlers_mutex};\n  handlers.emplace_back(event, handler);\n}\n\nauto IPC::unregister_handler(EventHandler& handler) -> void {\n  auto _ = std::lock_guard{handlers_mutex};\n  handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; });\n}\n\nauto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void {\n  bool new_output_detected;\n  {\n    auto _ = lock_state();\n    update_state_handler(event, data);\n    new_output_detected = state.new_output_detected;\n    state.new_output_detected = false;\n  }\n  if (new_output_detected) {\n    send(\"window-rules/list-outputs\", {});\n    send(\"window-rules/list-wsets\", {});\n  }\n  {\n    auto _ = std::lock_guard{handlers_mutex};\n    for (const auto& [_event, handler] : handlers)\n      if (_event == event) handler(event);\n  }\n}\n\nauto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void {\n  // IPC events\n  // https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125\n  /*\n    [x] view-mapped\n    [x] view-unmapped\n    [-] view-set-output  // for detect new output\n    [ ] view-geometry-changed  // -> view-workspace-changed\n    [x] view-wset-changed\n    [x] view-focused\n    [x] view-title-changed\n    [x] view-app-id-changed\n    [x] plugin-activation-state-changed\n    [x] output-gain-focus\n\n    [ ] view-tiled\n    [ ] view-minimized\n    [ ] view-fullscreened\n    [x] view-sticky\n    [x] view-workspace-changed\n    [x] output-wset-changed\n    [x] wset-workspace-changed\n  */\n\n  if (event == \"view-mapped\") {\n    // data: { event, view }\n    state.update_view(data[\"view\"]);\n    return;\n  }\n\n  if (event == \"view-unmapped\") {\n    // data: { event, view }\n    try {\n      // data[\"view\"][\"wset-index\"] could be messed up\n      state.update_view(data[\"view\"]);\n      state.maybe_empty_focus_wset_idx = data[\"view\"][\"wset-index\"].asUInt();\n    } catch (const std::exception&) {\n    }\n    return;\n  }\n\n  if (event == \"view-set-output\") {\n    // data: { event, output?, view }\n    // new output event\n    if (!state.outputs.contains(data[\"view\"][\"output-name\"].asString())) {\n      state.new_output_detected = true;\n    }\n    return;\n  }\n\n  if (event == \"view-wset-changed\") {\n    // data: { event, old-wset: wset, new-wset: wset, view }\n    state.maybe_empty_focus_wset_idx = data[\"old-wset\"][\"index\"].asUInt();\n    state.update_view(data[\"view\"]);\n    return;\n  }\n\n  if (event == \"view-focused\") {\n    // data: { event, view? }\n    if (const auto& view = data[\"view\"]) {\n      try {\n        // view[\"wset-index\"] could be messed up\n        auto& wset = state.wsets.at(view[\"wset-index\"].asUInt());\n        wset.focused_view_id = view[\"id\"].asUInt();\n      } catch (const std::exception&) {\n      }\n    } else {\n      // focused to null\n      if (state.wsets.contains(state.maybe_empty_focus_wset_idx))\n        state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {};\n    }\n    return;\n  }\n\n  if (event == \"view-title-changed\" || event == \"view-app-id-changed\" || event == \"view-sticky\") {\n    // data: { event, view }\n    state.update_view(data[\"view\"]);\n    return;\n  }\n\n  if (event == \"plugin-activation-state-changed\") {\n    // data: { event, plugin: name, state: bool, output: id, output-data: output }\n    auto plugin = data[\"plugin\"].asString();\n    auto plugin_state = data[\"state\"].asBool();\n\n    if (plugin == \"vswitch\") {\n      state.vswitching = plugin_state;\n      if (plugin_state) {\n        state.maybe_empty_focus_wset_idx = data[\"output-data\"][\"wset-index\"].asUInt();\n      }\n    }\n\n    return;\n  }\n\n  if (event == \"output-gain-focus\") {\n    // data: { event, output }\n    state.focused_output_name = data[\"output\"][\"name\"].asString();\n    return;\n  }\n\n  if (event == \"view-workspace-changed\") {\n    // data: { event, from: point, to: point, view }\n    if (state.vswitching) {\n      if (state.vswitch_sticky_view_id == 0) {\n        auto& wset = state.wsets.at(data[\"view\"][\"wset-index\"].asUInt());\n        auto& old_ws = wset.locate_ws(state.views.at(data[\"view\"][\"id\"].asUInt())[\"geometry\"]);\n        auto& new_ws = wset.count_ws(data[\"to\"]);\n        old_ws.num_views--;\n        new_ws.num_views++;\n        if (data[\"view\"][\"sticky\"].asBool()) {\n          old_ws.num_sticky_views--;\n          new_ws.num_sticky_views++;\n        }\n        state.update_view(data[\"view\"]);\n        state.vswitch_sticky_view_id = data[\"view\"][\"id\"].asUInt();\n      } else {\n        state.vswitch_sticky_view_id = {};\n      }\n      return;\n    }\n    state.update_view(data[\"view\"]);\n    return;\n  }\n\n  if (event == \"output-wset-changed\") {\n    // data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output }\n    auto& output = state.outputs.at(data[\"output-data\"][\"name\"].asString());\n    auto wset_idx = data[\"new-wset-data\"][\"index\"].asUInt();\n    state.wsets.at(wset_idx).output = output;\n    output.wset_idx = wset_idx;\n    return;\n  }\n\n  if (event == \"wset-workspace-changed\") {\n    // data: { event, previous-workspace: point, new-workspace: point,\n    //         output: id, wset: wset.name, output-data: output, wset-data: wset }\n    auto wset_idx = data[\"wset-data\"][\"index\"].asUInt();\n    auto& wset = state.wsets.at(wset_idx);\n    wset.ws_x = data[\"new-workspace\"][\"x\"].asUInt();\n    wset.ws_y = data[\"new-workspace\"][\"y\"].asUInt();\n\n    // correct existing views geometry\n    auto& out = wset.output.value().get();\n    auto dx = (int)out.w * ((int)wset.ws_x - data[\"previous-workspace\"][\"x\"].asInt());\n    auto dy = (int)out.h * ((int)wset.ws_y - data[\"previous-workspace\"][\"y\"].asInt());\n    for (auto& [_, view] : state.views) {\n      if (view[\"wset-index\"].asUInt() == wset_idx &&\n          view[\"id\"].asUInt() != state.vswitch_sticky_view_id) {\n        view[\"geometry\"][\"x\"] = view[\"geometry\"][\"x\"].asInt() - dx;\n        view[\"geometry\"][\"y\"] = view[\"geometry\"][\"y\"].asInt() - dy;\n      }\n    }\n    return;\n  }\n\n  // IPC responses\n  // https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37\n\n  if (event == \"window-rules/list-views\") {\n    // data: [ view ]\n    state.views.clear();\n    for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{});\n    for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) {\n      state.update_view(view);\n    }\n    return;\n  }\n\n  if (event == \"window-rules/list-outputs\") {\n    // data: [ output ]\n    state.outputs.clear();\n    for (const auto& output_data : data) {\n      state.outputs.emplace(output_data[\"name\"].asString(),\n                            State::Output{\n                                .id = output_data[\"id\"].asUInt(),\n                                .w = output_data[\"geometry\"][\"width\"].asUInt(),\n                                .h = output_data[\"geometry\"][\"height\"].asUInt(),\n                                .wset_idx = output_data[\"wset-index\"].asUInt(),\n                            });\n    }\n    return;\n  }\n\n  if (event == \"window-rules/list-wsets\") {\n    // data: [ wset ]\n    std::unordered_map<size_t, State::Wset> wsets;\n    for (const auto& wset_data : data) {\n      auto wset_idx = wset_data[\"index\"].asUInt();\n\n      auto output_name = wset_data[\"output-name\"].asString();\n      auto output = state.outputs.contains(output_name)\n                        ? std::optional{std::ref(state.outputs.at(output_name))}\n                        : std::nullopt;\n\n      const auto& ws_data = wset_data[\"workspace\"];\n      auto ws_w = ws_data[\"grid_width\"].asUInt();\n      auto ws_h = ws_data[\"grid_height\"].asUInt();\n\n      wsets.emplace(wset_idx, State::Wset{\n                                  .output = output,\n                                  .wss = std::vector<State::Workspace>(ws_w * ws_h),\n                                  .ws_w = ws_w,\n                                  .ws_h = ws_h,\n                                  .ws_x = ws_data[\"x\"].asUInt(),\n                                  .ws_y = ws_data[\"y\"].asUInt(),\n                              });\n\n      if (state.wsets.contains(wset_idx)) {\n        auto& old_wset = state.wsets.at(wset_idx);\n        auto& new_wset = wsets.at(wset_idx);\n        new_wset.wss = std::move(old_wset.wss);\n        new_wset.focused_view_id = old_wset.focused_view_id;\n      }\n    }\n    state.wsets = std::move(wsets);\n    return;\n  }\n\n  if (event == \"window-rules/get-focused-view\") {\n    // data: { ok, info: view? }\n    if (const auto& view = data[\"info\"]) {\n      auto& wset = state.wsets.at(view[\"wset-index\"].asUInt());\n      wset.focused_view_id = view[\"id\"].asUInt();\n      state.update_view(view);\n    }\n    return;\n  }\n\n  if (event == \"window-rules/get-focused-output\") {\n    // data: { ok, info: output }\n    state.focused_output_name = data[\"info\"][\"name\"].asString();\n    return;\n  }\n}\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "src/modules/wayfire/window.cpp",
    "content": "#include \"modules/wayfire/window.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n\n#include \"util/rewrite_string.hpp\"\n#include \"util/sanitize_str.hpp\"\n\nnamespace waybar::modules::wayfire {\n\nWindow::Window(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AAppIconLabel(config, \"window\", id, \"{title}\", 0, true),\n      ipc{IPC::get_instance()},\n      handler{[this](const auto&) { dp.emit(); }},\n      bar_{bar} {\n  ipc->register_handler(\"view-unmapped\", handler);\n  ipc->register_handler(\"view-focused\", handler);\n  ipc->register_handler(\"view-title-changed\", handler);\n  ipc->register_handler(\"view-app-id-changed\", handler);\n\n  ipc->register_handler(\"window-rules/get-focused-view\", handler);\n\n  dp.emit();\n}\n\nWindow::~Window() { ipc->unregister_handler(handler); }\n\nauto Window::update() -> void {\n  update_icon_label();\n  AAppIconLabel::update();\n}\n\nauto Window::update_icon_label() -> void {\n  auto _ = ipc->lock_state();\n\n  auto out_it = ipc->get_outputs().find(bar_.output->name);\n  if (out_it == ipc->get_outputs().end()) return;\n  const auto& output = out_it->second;\n  auto wset_it = ipc->get_wsets().find(output.wset_idx);\n  if (wset_it == ipc->get_wsets().end()) return;\n  const auto& wset = wset_it->second;\n  const auto& views = ipc->get_views();\n  auto ctx = bar_.window.get_style_context();\n\n  if (views.contains(wset.focused_view_id)) {\n    const auto& view = views.at(wset.focused_view_id);\n    auto title = view[\"title\"].asString();\n    auto app_id = view[\"app-id\"].asString();\n\n    // update label\n    label_.set_markup(waybar::util::rewriteString(\n        fmt::format(fmt::runtime(format_), fmt::arg(\"title\", waybar::util::sanitize_string(title)),\n                    fmt::arg(\"app_id\", waybar::util::sanitize_string(app_id))),\n        config_[\"rewrite\"]));\n\n    // update window#waybar.solo\n    if (wset.locate_ws(view[\"geometry\"]).num_views > 1)\n      ctx->remove_class(\"solo\");\n    else\n      ctx->add_class(\"solo\");\n\n    // update window#waybar.<app_id>\n    ctx->remove_class(old_app_id_);\n    ctx->add_class(old_app_id_ = app_id);\n\n    // update window#waybar.empty\n    ctx->remove_class(\"empty\");\n\n    //\n    updateAppIconName(app_id, \"\");\n    label_.show();\n  } else {\n    ctx->add_class(\"empty\");\n\n    updateAppIconName(\"\", \"\");\n    label_.hide();\n  }\n}\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "src/modules/wayfire/workspaces.cpp",
    "content": "#include \"modules/wayfire/workspaces.hpp\"\n\n#include <gtkmm/button.h>\n#include <gtkmm/label.h>\n#include <spdlog/spdlog.h>\n\n#include <string>\n#include <utility>\n\n#include \"modules/wayfire/backend.hpp\"\n\nnamespace waybar::modules::wayfire {\n\nWorkspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config)\n    : AModule{config, \"workspaces\", id, false, !config[\"disable-scroll\"].asBool()},\n      ipc{IPC::get_instance()},\n      handler{[this](const auto&) { dp.emit(); }},\n      bar_{bar} {\n  // init box_\n  box_.set_name(\"workspaces\");\n  if (!id.empty()) box_.get_style_context()->add_class(id);\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  event_box_.add(box_);\n\n  // scroll events\n  if (!config_[\"disable-scroll\"].asBool()) {\n    auto& target = config_[\"enable-bar-scroll\"].asBool() ? const_cast<Bar&>(bar_).window\n                                                         : dynamic_cast<Gtk::Widget&>(box_);\n    target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n    target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));\n  }\n\n  // listen events\n  ipc->register_handler(\"view-mapped\", handler);\n  ipc->register_handler(\"view-unmapped\", handler);\n  ipc->register_handler(\"view-wset-changed\", handler);\n  ipc->register_handler(\"output-gain-focus\", handler);\n  ipc->register_handler(\"view-sticky\", handler);\n  ipc->register_handler(\"view-workspace-changed\", handler);\n  ipc->register_handler(\"output-wset-changed\", handler);\n  ipc->register_handler(\"wset-workspace-changed\", handler);\n\n  ipc->register_handler(\"window-rules/list-views\", handler);\n  ipc->register_handler(\"window-rules/list-outputs\", handler);\n  ipc->register_handler(\"window-rules/list-wsets\", handler);\n  ipc->register_handler(\"window-rules/get-focused-output\", handler);\n\n  // initial render\n  dp.emit();\n}\n\nWorkspaces::~Workspaces() { ipc->unregister_handler(handler); }\n\nauto Workspaces::handleScroll(GdkEventScroll* e) -> bool {\n  // Ignore emulated scroll events on window\n  if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false;\n\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) return true;\n\n  int delta;\n  if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT)\n    delta = 1;\n  else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT)\n    delta = -1;\n  else\n    return true;\n\n  // cycle workspace\n  Json::Value data;\n  {\n    auto _ = ipc->lock_state();\n    auto out_it = ipc->get_outputs().find(bar_.output->name);\n    if (out_it == ipc->get_outputs().end()) return true;\n    const auto& output = out_it->second;\n    auto wset_it = ipc->get_wsets().find(output.wset_idx);\n    if (wset_it == ipc->get_wsets().end()) return true;\n    const auto& wset = wset_it->second;\n    auto n = wset.ws_w * wset.ws_h;\n    auto i = (wset.ws_idx() + delta + n) % n;\n    data[\"x\"] = Json::Value((uint64_t)i % wset.ws_w);\n    data[\"y\"] = Json::Value((uint64_t)i / wset.ws_h);\n    data[\"output-id\"] = Json::Value((uint64_t)output.id);\n  }\n  ipc->send(\"vswitch/set-workspace\", std::move(data));\n\n  return true;\n}\n\nauto Workspaces::update() -> void {\n  update_box();\n  AModule::update();\n}\n\nauto Workspaces::update_box() -> void {\n  auto _ = ipc->lock_state();\n\n  const auto& output_name = bar_.output->name;\n  auto out_it = ipc->get_outputs().find(output_name);\n  if (out_it == ipc->get_outputs().end()) return;\n  const auto& output = out_it->second;\n  auto wset_it = ipc->get_wsets().find(output.wset_idx);\n  if (wset_it == ipc->get_wsets().end()) return;\n  const auto& wset = wset_it->second;\n\n  auto output_focused = ipc->get_focused_output_name() == output_name;\n  auto ws_w = wset.ws_w;\n  auto ws_h = wset.ws_h;\n  auto num_wss = ws_w * ws_h;\n\n  // add buttons for new workspaces\n  for (auto i = buttons_.size(); i < num_wss; i++) {\n    auto& btn = buttons_.emplace_back(\"\");\n    box_.pack_start(btn, false, false, 0);\n    btn.set_relief(Gtk::RELIEF_NONE);\n    if (!config_[\"disable-click\"].asBool()) {\n      btn.signal_pressed().connect([=, this] {\n        Json::Value data;\n        data[\"x\"] = Json::Value((uint64_t)i % ws_w);\n        data[\"y\"] = Json::Value((uint64_t)i / ws_h);\n        data[\"output-id\"] = Json::Value((uint64_t)output.id);\n        ipc->send(\"vswitch/set-workspace\", std::move(data));\n      });\n    }\n  }\n\n  // remove buttons for removed workspaces\n  buttons_.resize(num_wss);\n\n  // update buttons\n  for (size_t i = 0; i < num_wss; i++) {\n    const auto& ws = wset.wss[i];\n    auto& btn = buttons_[i];\n    auto ctx = btn.get_style_context();\n    auto ws_focused = i == wset.ws_idx();\n    auto ws_empty = ws.num_views == 0;\n\n    // update #workspaces button.focused\n    if (ws_focused)\n      ctx->add_class(\"focused\");\n    else\n      ctx->remove_class(\"focused\");\n\n    // update #workspaces button.empty\n    if (ws_empty)\n      ctx->add_class(\"empty\");\n    else\n      ctx->remove_class(\"empty\");\n\n    // update #workspaces button.current_output\n    if (output_focused)\n      ctx->add_class(\"current_output\");\n    else\n      ctx->remove_class(\"current_output\");\n\n    // update label\n    auto label = std::to_string(i + 1);\n    if (config_[\"format\"].isString()) {\n      auto format = config_[\"format\"].asString();\n      auto ws_idx = std::to_string(i + 1);\n\n      const auto& icons = config_[\"format-icons\"];\n      std::string icon;\n      if (!icons)\n        icon = ws_idx;\n      else if (ws_focused && icons[\"focused\"])\n        icon = icons[\"focused\"].asString();\n      else if (icons[ws_idx])\n        icon = icons[ws_idx].asString();\n      else if (icons[\"default\"])\n        icon = icons[\"default\"].asString();\n      else\n        icon = ws_idx;\n\n      label = fmt::format(fmt::runtime(format), fmt::arg(\"icon\", icon), fmt::arg(\"index\", ws_idx),\n                          fmt::arg(\"output\", output_name));\n    }\n    if (!config_[\"disable-markup\"].asBool())\n      static_cast<Gtk::Label*>(btn.get_children()[0])->set_markup(label);\n    else\n      btn.set_label(label);\n\n    //\n    if (config_[\"current-only\"].asBool() && i != wset.ws_idx())\n      btn.hide();\n    else\n      btn.show();\n  }\n}\n\n}  // namespace waybar::modules::wayfire\n"
  },
  {
    "path": "src/modules/wireplumber.cpp",
    "content": "#include \"modules/wireplumber.hpp\"\n\n#include <spdlog/spdlog.h>\n\nbool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }\n\nstd::list<waybar::modules::Wireplumber*> waybar::modules::Wireplumber::modules;\n\nwaybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)\n    : ALabel(config, \"wireplumber\", id, \"{volume}%\"),\n      wp_core_(nullptr),\n      apis_(nullptr),\n      om_(nullptr),\n      mixer_api_(nullptr),\n      def_nodes_api_(nullptr),\n      default_node_name_(nullptr),\n      pending_plugins_(0),\n      muted_(false),\n      volume_(0.0),\n      min_step_(0.0),\n      node_id_(0),\n      node_name_(\"\"),\n      source_name_(\"\"),\n      type_(nullptr),\n      source_node_id_(0),\n      source_muted_(false),\n      source_volume_(0.0),\n      default_source_name_(nullptr) {\n  waybar::modules::Wireplumber::modules.push_back(this);\n\n  wp_init(WP_INIT_PIPEWIRE);\n  wp_core_ = wp_core_new(nullptr, nullptr, nullptr);\n  apis_ = g_ptr_array_new_with_free_func(g_object_unref);\n  om_ = wp_object_manager_new();\n\n  type_ = g_strdup(config_[\"node-type\"].isString() ? config_[\"node-type\"].asString().c_str()\n                                                   : \"Audio/Sink\");\n\n  prepare(this);\n\n  spdlog::debug(\"[{}]: connecting to pipewire: '{}'...\", name_, type_);\n\n  if (wp_core_connect(wp_core_) == 0) {\n    spdlog::error(\"[{}]: Could not connect to PipeWire: '{}'\", name_, type_);\n    throw std::runtime_error(\"Could not connect to PipeWire\\n\");\n  }\n\n  spdlog::debug(\"[{}]: {} connected!\", name_, type_);\n\n  g_signal_connect_swapped(om_, \"installed\", (GCallback)onObjectManagerInstalled, this);\n\n  asyncLoadRequiredApiModules();\n}\n\nwaybar::modules::Wireplumber::~Wireplumber() {\n  waybar::modules::Wireplumber::modules.remove(this);\n  if (mixer_api_ != nullptr) {\n    g_signal_handlers_disconnect_by_data(mixer_api_, this);\n  }\n  if (def_nodes_api_ != nullptr) {\n    g_signal_handlers_disconnect_by_data(def_nodes_api_, this);\n  }\n  if (om_ != nullptr) {\n    g_signal_handlers_disconnect_by_data(om_, this);\n  }\n  wp_core_disconnect(wp_core_);\n  g_clear_pointer(&apis_, g_ptr_array_unref);\n  g_clear_object(&om_);\n  g_clear_object(&wp_core_);\n  g_clear_object(&mixer_api_);\n  g_clear_object(&def_nodes_api_);\n  g_free(default_node_name_);\n  g_free(default_source_name_);\n  g_free(type_);\n}\n\nvoid waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {\n  spdlog::debug(\"[{}]: updating '{}' node name with node.id {}\", self->name_, self->type_, id);\n\n  if (!isValidNodeId(id)) {\n    spdlog::warn(\"[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.\", self->name_,\n                 id, self->type_);\n    return;\n  }\n\n  auto* proxy = static_cast<WpProxy*>(wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY,\n                                                               WP_CONSTRAINT_TYPE_G_PROPERTY,\n                                                               \"bound-id\", \"=u\", id, nullptr));\n\n  if (proxy == nullptr) {\n    auto err = fmt::format(\"Object '{}' not found\\n\", id);\n    spdlog::error(\"[{}]: {}\", self->name_, err);\n    return;\n  }\n\n  g_autoptr(WpProperties) properties =\n      WP_IS_PIPEWIRE_OBJECT(proxy) != 0\n          ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))\n          : wp_properties_new_empty();\n  g_autoptr(WpProperties) globalP = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));\n  properties = wp_properties_ensure_unique_owner(properties);\n  wp_properties_add(properties, globalP);\n  wp_properties_set(properties, \"object.id\", nullptr);\n  const auto* nick = wp_properties_get(properties, \"node.nick\");\n  const auto* description = wp_properties_get(properties, \"node.description\");\n\n  self->node_name_ = nick != nullptr          ? nick\n                     : description != nullptr ? description\n                                              : \"Unknown node name\";\n  spdlog::debug(\"[{}]: Updating '{}' node name to: {}\", self->name_, self->type_, self->node_name_);\n}\n\nvoid waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self,\n                                                    uint32_t id) {\n  spdlog::debug(\"[{}]: updating source name with node.id {}\", self->name_, id);\n\n  if (!isValidNodeId(id)) {\n    spdlog::warn(\"[{}]: '{}' is not a valid source node ID. Ignoring source name update.\",\n                 self->name_, id);\n    return;\n  }\n\n  auto* proxy = static_cast<WpProxy*>(wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY,\n                                                               WP_CONSTRAINT_TYPE_G_PROPERTY,\n                                                               \"bound-id\", \"=u\", id, nullptr));\n\n  if (proxy == nullptr) {\n    auto err = fmt::format(\"Source object '{}' not found\\n\", id);\n    spdlog::error(\"[{}]: {}\", self->name_, err);\n    return;\n  }\n\n  g_autoptr(WpProperties) properties =\n      WP_IS_PIPEWIRE_OBJECT(proxy) != 0\n          ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))\n          : wp_properties_new_empty();\n  g_autoptr(WpProperties) globalP = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));\n  properties = wp_properties_ensure_unique_owner(properties);\n  wp_properties_add(properties, globalP);\n  wp_properties_set(properties, \"object.id\", nullptr);\n  const auto* nick = wp_properties_get(properties, \"node.nick\");\n  const auto* description = wp_properties_get(properties, \"node.description\");\n\n  self->source_name_ = nick != nullptr          ? nick\n                       : description != nullptr ? description\n                                                : \"Unknown source name\";\n  spdlog::debug(\"[{}]: Updating source name to: {}\", self->name_, self->source_name_);\n}\n\nvoid waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {\n  spdlog::debug(\"[{}]: updating volume\", self->name_);\n  GVariant* variant = nullptr;\n\n  if (!isValidNodeId(id)) {\n    spdlog::error(\"[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.\", self->name_,\n                  id, self->type_);\n    return;\n  }\n\n  g_signal_emit_by_name(self->mixer_api_, \"get-volume\", id, &variant);\n\n  if (variant == nullptr) {\n    auto err = fmt::format(\"Node {} does not support volume\\n\", id);\n    spdlog::error(\"[{}]: {}\", self->name_, err);\n    return;\n  }\n\n  g_variant_lookup(variant, \"volume\", \"d\", &self->volume_);\n  g_variant_lookup(variant, \"step\", \"d\", &self->min_step_);\n  g_variant_lookup(variant, \"mute\", \"b\", &self->muted_);\n  g_clear_pointer(&variant, g_variant_unref);\n\n  self->dp.emit();\n}\n\nvoid waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self,\n                                                      uint32_t id) {\n  spdlog::debug(\"[{}]: updating source volume\", self->name_);\n  GVariant* variant = nullptr;\n\n  if (!isValidNodeId(id)) {\n    spdlog::error(\"[{}]: '{}' is not a valid source node ID. Ignoring source volume update.\",\n                  self->name_, id);\n    return;\n  }\n\n  g_signal_emit_by_name(self->mixer_api_, \"get-volume\", id, &variant);\n\n  if (variant == nullptr) {\n    spdlog::debug(\"[{}]: Source node {} does not support volume\", self->name_, id);\n    return;\n  }\n\n  g_variant_lookup(variant, \"volume\", \"d\", &self->source_volume_);\n  g_variant_lookup(variant, \"mute\", \"b\", &self->source_muted_);\n  g_clear_pointer(&variant, g_variant_unref);\n\n  self->dp.emit();\n}\n\nvoid waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {\n  g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(\n      self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, \"bound-id\", \"=u\", id, nullptr));\n\n  if (node == nullptr) {\n    // log a warning only if no other widget is targeting the id.\n    // this reduces log spam when multiple instances of the module are used on different node types.\n    if (id != self->node_id_ && id != self->source_node_id_) {\n      for (auto const& module : waybar::modules::Wireplumber::modules) {\n        if (module->node_id_ == id || module->source_node_id_ == id) {\n          return;\n        }\n      }\n    }\n\n    spdlog::warn(\"[{}]: (onMixerChanged: {}) - Object with id {} not found\", self->name_,\n                 self->type_, id);\n    return;\n  }\n\n  const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), \"node.name\");\n\n  if (self->node_id_ == id) {\n    spdlog::debug(\"[{}]: (onMixerChanged: {}) - updating sink volume for node: {}\", self->name_,\n                  self->type_, name);\n    updateVolume(self, id);\n  } else if (self->source_node_id_ == id) {\n    spdlog::debug(\"[{}]: (onMixerChanged: {}) - updating source volume for node: {}\", self->name_,\n                  self->type_, name);\n    updateSourceVolume(self, id);\n  }\n}\n\nvoid waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {\n  spdlog::debug(\"[{}]: (onDefaultNodesApiChanged: {})\", self->name_, self->type_);\n\n  // Handle sink\n  uint32_t defaultNodeId;\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-node\", self->type_, &defaultNodeId);\n\n  if (isValidNodeId(defaultNodeId)) {\n    g_autoptr(WpNode) node = static_cast<WpNode*>(\n        wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, \"bound-id\",\n                                 \"=u\", defaultNodeId, nullptr));\n\n    if (node != nullptr) {\n      const gchar* defaultNodeName =\n          wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), \"node.name\");\n\n      if (g_strcmp0(self->default_node_name_, defaultNodeName) != 0 ||\n          self->node_id_ != defaultNodeId) {\n        spdlog::debug(\"[{}]: Default sink changed to -> Node(name: {}, id: {})\", self->name_,\n                      defaultNodeName, defaultNodeId);\n\n        g_free(self->default_node_name_);\n        self->default_node_name_ = g_strdup(defaultNodeName);\n        self->node_id_ = defaultNodeId;\n        updateVolume(self, defaultNodeId);\n        updateNodeName(self, defaultNodeId);\n      }\n    }\n  }\n\n  // Handle source\n  uint32_t defaultSourceId;\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-node\", \"Audio/Source\", &defaultSourceId);\n\n  if (isValidNodeId(defaultSourceId)) {\n    g_autoptr(WpNode) sourceNode = static_cast<WpNode*>(\n        wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, \"bound-id\",\n                                 \"=u\", defaultSourceId, nullptr));\n\n    if (sourceNode != nullptr) {\n      const gchar* defaultSourceName =\n          wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), \"node.name\");\n\n      if (g_strcmp0(self->default_source_name_, defaultSourceName) != 0 ||\n          self->source_node_id_ != defaultSourceId) {\n        spdlog::debug(\"[{}]: Default source changed to -> Node(name: {}, id: {})\", self->name_,\n                      defaultSourceName, defaultSourceId);\n\n        g_free(self->default_source_name_);\n        self->default_source_name_ = g_strdup(defaultSourceName);\n        self->source_node_id_ = defaultSourceId;\n        updateSourceVolume(self, defaultSourceId);\n        updateSourceName(self, defaultSourceId);\n      }\n    }\n  }\n}\n\nvoid waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {\n  spdlog::debug(\"[{}]: onObjectManagerInstalled\", self->name_);\n\n  self->def_nodes_api_ = wp_plugin_find(self->wp_core_, \"default-nodes-api\");\n\n  if (self->def_nodes_api_ == nullptr) {\n    spdlog::error(\"[{}]: default nodes api is not loaded.\", self->name_);\n    return;\n  }\n\n  self->mixer_api_ = wp_plugin_find(self->wp_core_, \"mixer-api\");\n\n  if (self->mixer_api_ == nullptr) {\n    spdlog::error(\"[{}]: mixer api is not loaded.\", self->name_);\n    return;\n  }\n\n  // Get default sink\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-configured-node-name\", self->type_,\n                        &self->default_node_name_);\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-node\", self->type_, &self->node_id_);\n\n  // Get default source\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-configured-node-name\", \"Audio/Source\",\n                        &self->default_source_name_);\n  g_signal_emit_by_name(self->def_nodes_api_, \"get-default-node\", \"Audio/Source\",\n                        &self->source_node_id_);\n\n  if (self->default_node_name_ != nullptr) {\n    spdlog::debug(\n        \"[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}\",\n        self->name_, self->type_, self->default_node_name_, self->node_id_);\n  }\n  if (self->default_source_name_ != nullptr) {\n    spdlog::debug(\"[{}]: default source: {} (id: {})\", self->name_, self->default_source_name_,\n                  self->source_node_id_);\n  }\n\n  updateVolume(self, self->node_id_);\n  updateNodeName(self, self->node_id_);\n  updateSourceVolume(self, self->source_node_id_);\n  updateSourceName(self, self->source_node_id_);\n\n  g_signal_connect_swapped(self->mixer_api_, \"changed\", (GCallback)onMixerChanged, self);\n  g_signal_connect_swapped(self->def_nodes_api_, \"changed\", (GCallback)onDefaultNodesApiChanged,\n                           self);\n}\n\nvoid waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,\n                                                     waybar::modules::Wireplumber* self) {\n  const auto* pluginName = wp_plugin_get_name(WP_PLUGIN(p));\n  spdlog::debug(\"[{}]: onPluginActivated: {}\", self->name_, pluginName);\n  g_autoptr(GError) error = nullptr;\n\n  if (wp_object_activate_finish(p, res, &error) == 0) {\n    spdlog::error(\"[{}]: error activating plugin: {}\", self->name_, error->message);\n    return;\n  }\n\n  if (--self->pending_plugins_ == 0) {\n    wp_core_install_object_manager(self->wp_core_, self->om_);\n  }\n}\n\nvoid waybar::modules::Wireplumber::activatePlugins() {\n  spdlog::debug(\"[{}]: activating plugins\", name_);\n  for (uint16_t i = 0; i < apis_->len; i++) {\n    WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));\n    pending_plugins_++;\n    wp_object_activate(WP_OBJECT(plugin), WP_PLUGIN_FEATURE_ENABLED, nullptr,\n                       (GAsyncReadyCallback)onPluginActivated, this);\n  }\n}\n\nvoid waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) {\n  spdlog::debug(\"[{}]: preparing object manager: '{}'\", name_, self->type_);\n  wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, \"media.class\",\n                                 \"=s\", self->type_, nullptr);\n  wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, \"media.class\",\n                                 \"=s\", \"Audio/Source\", nullptr);\n}\n\nvoid waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res,\n                                                           waybar::modules::Wireplumber* self) {\n  gboolean success = FALSE;\n  g_autoptr(GError) error = nullptr;\n\n  spdlog::debug(\"[{}]: callback loading default node api module\", self->name_);\n\n  success = wp_core_load_component_finish(self->wp_core_, res, &error);\n\n  if (success == FALSE) {\n    spdlog::error(\"[{}]: default nodes API load failed\", self->name_);\n    return;\n  }\n  spdlog::debug(\"[{}]: loaded default nodes api\", self->name_);\n  g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, \"default-nodes-api\"));\n\n  spdlog::debug(\"[{}]: loading mixer api module\", self->name_);\n  wp_core_load_component(self->wp_core_, \"libwireplumber-module-mixer-api\", \"module\", nullptr,\n                         \"mixer-api\", nullptr, (GAsyncReadyCallback)onMixerApiLoaded, self);\n}\n\nvoid waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* res,\n                                                    waybar::modules::Wireplumber* self) {\n  gboolean success = FALSE;\n  g_autoptr(GError) error = nullptr;\n\n  success = wp_core_load_component_finish(self->wp_core_, res, &error);\n\n  if (success == FALSE) {\n    spdlog::error(\"[{}]: mixer API load failed\", self->name_);\n    return;\n  }\n\n  spdlog::debug(\"[{}]: loaded mixer API\", self->name_);\n  g_ptr_array_add(self->apis_, ({\n                    WpPlugin* p = wp_plugin_find(self->wp_core_, \"mixer-api\");\n                    g_object_set(G_OBJECT(p), \"scale\", 1 /* cubic */, nullptr);\n                    p;\n                  }));\n\n  self->activatePlugins();\n\n  self->dp.emit();\n\n  self->event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);\n  self->event_box_.signal_scroll_event().connect(sigc::mem_fun(*self, &Wireplumber::handleScroll));\n}\n\nvoid waybar::modules::Wireplumber::asyncLoadRequiredApiModules() {\n  spdlog::debug(\"[{}]: loading default nodes api module\", name_);\n  wp_core_load_component(wp_core_, \"libwireplumber-module-default-nodes-api\", \"module\", nullptr,\n                         \"default-nodes-api\", nullptr, (GAsyncReadyCallback)onDefaultNodesApiLoaded,\n                         this);\n}\n\nauto waybar::modules::Wireplumber::update() -> void {\n  auto format = format_;\n  std::string tooltipFormat;\n\n  // Handle sink mute state\n  if (muted_) {\n    format = config_[\"format-muted\"].isString() ? config_[\"format-muted\"].asString() : format;\n    label_.get_style_context()->add_class(\"muted\");\n    label_.get_style_context()->add_class(\"sink-muted\");\n  } else {\n    label_.get_style_context()->remove_class(\"muted\");\n    label_.get_style_context()->remove_class(\"sink-muted\");\n  }\n\n  // Handle source mute state\n  if (source_muted_) {\n    label_.get_style_context()->add_class(\"source-muted\");\n  } else {\n    label_.get_style_context()->remove_class(\"source-muted\");\n  }\n\n  int vol = round(volume_ * 100.0);\n  int source_vol = round(source_volume_ * 100.0);\n\n  // Get the state and apply state-specific format if available\n  auto state = getState(vol);\n  if (!state.empty()) {\n    std::string format_name = muted_ ? \"format-muted\" : \"format\";\n    std::string state_format_name = format_name + \"-\" + state;\n    if (config_[state_format_name].isString()) {\n      format = config_[state_format_name].asString();\n    }\n  }\n\n  // Prepare source format string (similar to PulseAudio)\n  std::string format_source = \"{volume}%\";\n  if (source_muted_) {\n    if (config_[\"format-source-muted\"].isString()) {\n      format_source = config_[\"format-source-muted\"].asString();\n    }\n  } else {\n    if (config_[\"format-source\"].isString()) {\n      format_source = config_[\"format-source\"].asString();\n    }\n  }\n\n  // Format the source string with actual volume\n  std::string formatted_source =\n      fmt::format(fmt::runtime(format_source), fmt::arg(\"volume\", source_vol));\n\n  std::string markup =\n      fmt::format(fmt::runtime(format), fmt::arg(\"node_name\", node_name_), fmt::arg(\"volume\", vol),\n                  fmt::arg(\"icon\", getIcon(vol)), fmt::arg(\"format_source\", formatted_source),\n                  fmt::arg(\"source_volume\", source_vol), fmt::arg(\"source_desc\", source_name_));\n  label_.set_markup(markup);\n\n  if (tooltipEnabled()) {\n    if (tooltipFormat.empty() && config_[\"tooltip-format\"].isString()) {\n      tooltipFormat = config_[\"tooltip-format\"].asString();\n    }\n\n    if (!tooltipFormat.empty()) {\n      label_.set_tooltip_markup(fmt::format(\n          fmt::runtime(tooltipFormat), fmt::arg(\"node_name\", node_name_), fmt::arg(\"volume\", vol),\n          fmt::arg(\"icon\", getIcon(vol)), fmt::arg(\"format_source\", formatted_source),\n          fmt::arg(\"source_volume\", source_vol), fmt::arg(\"source_desc\", source_name_)));\n    } else {\n      label_.set_tooltip_markup(node_name_);\n    }\n  }\n\n  // Call parent update\n  ALabel::update();\n}\n\nbool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) {\n  if (config_[\"on-scroll-up\"].isString() || config_[\"on-scroll-down\"].isString()) {\n    return AModule::handleScroll(e);\n  }\n  auto dir = AModule::getScrollDir(e);\n  if (dir == SCROLL_DIR::NONE) {\n    return true;\n  }\n  double maxVolume = 1;\n  double step = 1.0 / 100.0;\n  if (config_[\"scroll-step\"].isDouble()) {\n    step = config_[\"scroll-step\"].asDouble() / 100.0;\n  }\n  if (config_[\"max-volume\"].isDouble()) {\n    maxVolume = config_[\"max-volume\"].asDouble() / 100.0;\n  }\n\n  if (step < min_step_) step = min_step_;\n\n  double newVol = volume_;\n  if (dir == SCROLL_DIR::UP) {\n    if (volume_ < maxVolume) {\n      newVol = volume_ + step;\n      if (newVol > maxVolume) newVol = maxVolume;\n    }\n  } else if (dir == SCROLL_DIR::DOWN) {\n    if (volume_ > 0) {\n      newVol = volume_ - step;\n      if (newVol < 0) newVol = 0;\n    }\n  }\n  if (newVol != volume_) {\n    if (mixer_api_ == nullptr) return true;\n    GVariant* variant = g_variant_new_double(newVol);\n    gboolean ret;\n    g_signal_emit_by_name(mixer_api_, \"set-volume\", node_id_, variant, &ret);\n    g_variant_unref(variant);\n  }\n  return true;\n}\n"
  },
  {
    "path": "src/modules/wlr/taskbar.cpp",
    "content": "#include \"modules/wlr/taskbar.hpp\"\n\n#include <fmt/core.h>\n#include <gdkmm/monitor.h>\n#include <gio/gdesktopappinfo.h>\n#include <giomm/desktopappinfo.h>\n#include <gtkmm/icontheme.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <cctype>\n#include <cstdlib>\n#include <cstring>\n#include <memory>\n#include <sstream>\n#include <utility>\n\n#include \"gdkmm/general.h\"\n#include \"glibmm/error.h\"\n#include \"glibmm/fileutils.h\"\n#include \"glibmm/refptr.h\"\n#include \"util/format.hpp\"\n#include \"util/gtk_icon.hpp\"\n#include \"util/rewrite_string.hpp\"\n#include \"util/string.hpp\"\n\nnamespace waybar::modules::wlr {\n\n/* Task class implementation */\nuint32_t Task::global_id = 0;\n\nstatic void tl_handle_title(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                            const char* title) {\n  return static_cast<Task*>(data)->handle_title(title);\n}\n\nstatic void tl_handle_app_id(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                             const char* app_id) {\n  return static_cast<Task*>(data)->handle_app_id(app_id);\n}\n\nstatic void tl_handle_output_enter(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                                   struct wl_output* output) {\n  return static_cast<Task*>(data)->handle_output_enter(output);\n}\n\nstatic void tl_handle_output_leave(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                                   struct wl_output* output) {\n  return static_cast<Task*>(data)->handle_output_leave(output);\n}\n\nstatic void tl_handle_state(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                            struct wl_array* state) {\n  return static_cast<Task*>(data)->handle_state(state);\n}\n\nstatic void tl_handle_done(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {\n  return static_cast<Task*>(data)->handle_done();\n}\n\nstatic void tl_handle_parent(void* data, struct zwlr_foreign_toplevel_handle_v1* handle,\n                             struct zwlr_foreign_toplevel_handle_v1* parent) {\n  /* This is explicitly left blank */\n}\n\nstatic void tl_handle_closed(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) {\n  return static_cast<Task*>(data)->handle_closed();\n}\n\nstatic const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_impl = {\n    .title = tl_handle_title,\n    .app_id = tl_handle_app_id,\n    .output_enter = tl_handle_output_enter,\n    .output_leave = tl_handle_output_leave,\n    .state = tl_handle_state,\n    .done = tl_handle_done,\n    .closed = tl_handle_closed,\n    .parent = tl_handle_parent,\n};\n\nstatic const std::vector<Gtk::TargetEntry> target_entries = {\n    Gtk::TargetEntry(\"WAYBAR_TOPLEVEL\", Gtk::TARGET_SAME_APP, 0)};\n\nTask::Task(const waybar::Bar& bar, const Json::Value& config, Taskbar* tbar,\n           struct zwlr_foreign_toplevel_handle_v1* tl_handle, struct wl_seat* seat)\n    : bar_{bar},\n      config_{config},\n      tbar_{tbar},\n      handle_{tl_handle},\n      seat_{seat},\n      id_{global_id++},\n      content_{bar.orientation, 0} {\n  zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);\n\n  button.set_relief(Gtk::RELIEF_NONE);\n\n  content_.add(text_before_);\n  content_.add(icon_);\n  content_.add(text_after_);\n\n  content_.show();\n  button.add(content_);\n\n  format_before_.clear();\n  format_after_.clear();\n\n  if (config_[\"format\"].isString()) {\n    /* The user defined a format string, use it */\n    auto format = config_[\"format\"].asString();\n    if (format.find(\"{name}\") != std::string::npos) {\n      with_name_ = true;\n    }\n\n    auto parts = split(format, \"{icon}\", 1);\n    format_before_ = parts[0];\n    if (parts.size() > 1) {\n      with_icon_ = true;\n      format_after_ = parts[1];\n    }\n  } else {\n    /* The default is to only show the icon */\n    with_icon_ = true;\n  }\n\n  if (app_id_.empty()) {\n    handle_app_id(\"unknown\");\n  }\n\n  /* Strip spaces at the beginning and end of the format strings */\n  format_tooltip_.clear();\n  if (!config_[\"tooltip\"].isBool() || config_[\"tooltip\"].asBool()) {\n    if (config_[\"tooltip-format\"].isString())\n      format_tooltip_ = config_[\"tooltip-format\"].asString();\n    else\n      format_tooltip_ = \"{title}\";\n  }\n\n  /* Handle click events if configured */\n  if (config_[\"on-click\"].isString() || config_[\"on-click-middle\"].isString() ||\n      config_[\"on-click-right\"].isString()) {\n  }\n\n  button.add_events(Gdk::BUTTON_PRESS_MASK);\n  button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false);\n\n  button.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Task::handle_motion_notify),\n                                              false);\n\n  button.drag_source_set(target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);\n  button.drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);\n\n  button.signal_drag_data_get().connect(sigc::mem_fun(*this, &Task::handle_drag_data_get), false);\n  button.signal_drag_data_received().connect(sigc::mem_fun(*this, &Task::handle_drag_data_received),\n                                             false);\n}\n\nTask::~Task() {\n  if (handle_) {\n    zwlr_foreign_toplevel_handle_v1_destroy(handle_);\n    handle_ = nullptr;\n  }\n  if (button_visible_) {\n    tbar_->remove_button(button);\n    button_visible_ = false;\n  }\n}\n\nstd::string Task::repr() const {\n  std::stringstream ss;\n  ss << \"Task (\" << id_ << \") \" << title_ << \" [\" << app_id_ << \"] <\" << (active() ? \"A\" : \"a\")\n     << (maximized() ? \"M\" : \"m\") << (minimized() ? \"I\" : \"i\") << (fullscreen() ? \"F\" : \"f\") << \">\";\n\n  return ss.str();\n}\n\nstd::string Task::state_string(bool shortened) const {\n  std::stringstream ss;\n  if (shortened)\n    ss << (minimized() ? \"m\" : \"\") << (maximized() ? \"M\" : \"\") << (active() ? \"A\" : \"\")\n       << (fullscreen() ? \"F\" : \"\");\n  else\n    ss << (minimized() ? \"minimized \" : \"\") << (maximized() ? \"maximized \" : \"\")\n       << (active() ? \"active \" : \"\") << (fullscreen() ? \"fullscreen \" : \"\");\n\n  std::string res = ss.str();\n  if (shortened || res.empty())\n    return res;\n  else\n    return res.substr(0, res.size() - 1);\n}\n\nvoid Task::handle_title(const char* title) {\n  if (title_.empty()) {\n    spdlog::debug(fmt::format(\"Task ({}) setting title to {}\", id_, title_));\n  } else {\n    spdlog::debug(fmt::format(\"Task ({}) overwriting title '{}' with '{}'\", id_, title_, title));\n  }\n  title_ = title;\n  hide_if_ignored();\n\n  if (!with_icon_ && !with_name_ || app_info_) {\n    return;\n  }\n\n  app_info_ = IconLoader::get_app_info_from_app_id_list(title_);\n  name_ = app_info_ ? app_info_->get_display_name() : title;\n\n  if (!with_icon_) {\n    return;\n  }\n\n  int icon_size = config_[\"icon-size\"].isInt() ? config_[\"icon-size\"].asInt() : 16;\n  if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))\n    icon_.show();\n  else\n    spdlog::debug(\"Couldn't find icon for {}\", title_);\n}\n\nvoid Task::set_minimize_hint() {\n  zwlr_foreign_toplevel_handle_v1_set_rectangle(handle_, bar_.surface, minimize_hint.x,\n                                                minimize_hint.y, minimize_hint.w, minimize_hint.h);\n}\n\nvoid Task::hide_if_ignored() {\n  if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {\n    ignored_ = true;\n    if (button_visible_) {\n      auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n      handle_output_leave(output);\n    }\n  } else {\n    bool is_was_ignored = ignored_;\n    ignored_ = false;\n    if (is_was_ignored) {\n      auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n      handle_output_enter(output);\n    }\n  }\n}\n\nvoid Task::handle_app_id(const char* app_id) {\n  if (app_id_.empty()) {\n    spdlog::debug(fmt::format(\"Task ({}) setting app_id to {}\", id_, app_id));\n  } else {\n    spdlog::debug(fmt::format(\"Task ({}) overwriting app_id '{}' with '{}'\", id_, app_id_, app_id));\n  }\n  app_id_ = app_id;\n  hide_if_ignored();\n\n  auto ids_replace_map = tbar_->app_ids_replace_map();\n  if (ids_replace_map.count(app_id_)) {\n    auto replaced_id = ids_replace_map[app_id_];\n    spdlog::debug(\n        fmt::format(\"Task ({}) [{}] app_id was replaced with {}\", id_, app_id_, replaced_id));\n    app_id_ = replaced_id;\n  }\n\n  if (!with_icon_ && !with_name_) {\n    return;\n  }\n\n  app_info_ = IconLoader::get_app_info_from_app_id_list(app_id_);\n  name_ = app_info_ ? app_info_->get_display_name() : app_id;\n\n  if (!with_icon_) {\n    return;\n  }\n\n  int icon_size = config_[\"icon-size\"].isInt() ? config_[\"icon-size\"].asInt() : 16;\n  if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size))\n    icon_.show();\n  else\n    spdlog::debug(\"Couldn't find icon for {}\", app_id_);\n}\n\nvoid Task::on_button_size_allocated(Gtk::Allocation& alloc) {\n  gtk_widget_translate_coordinates(GTK_WIDGET(button.gobj()), GTK_WIDGET(bar_.window.gobj()), 0, 0,\n                                   &minimize_hint.x, &minimize_hint.y);\n  minimize_hint.w = button.get_width();\n  minimize_hint.h = button.get_height();\n}\n\nvoid Task::handle_output_enter(struct wl_output* output) {\n  if (ignored_) {\n    spdlog::debug(\"{} is ignored\", repr());\n    return;\n  }\n\n  spdlog::debug(\"{} entered output {}\", repr(), (void*)output);\n\n  if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {\n    /* The task entered the output of the current bar make the button visible */\n    button.signal_size_allocate().connect_notify(\n        sigc::mem_fun(this, &Task::on_button_size_allocated));\n    tbar_->add_button(button);\n    button.show();\n    button_visible_ = true;\n    spdlog::debug(\"{} now visible on {}\", repr(), bar_.output->name);\n  }\n}\n\nvoid Task::handle_output_leave(struct wl_output* output) {\n  spdlog::debug(\"{} left output {}\", repr(), (void*)output);\n\n  if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) {\n    /* The task left the output of the current bar, make the button invisible */\n    tbar_->remove_button(button);\n    button.hide();\n    button_visible_ = false;\n    spdlog::debug(\"{} now invisible on {}\", repr(), bar_.output->name);\n  }\n}\n\nvoid Task::handle_state(struct wl_array* state) {\n  state_ = 0;\n  size_t size = state->size / sizeof(uint32_t);\n  for (size_t i = 0; i < size; ++i) {\n    auto entry = static_cast<uint32_t*>(state->data)[i];\n    if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) state_ |= MAXIMIZED;\n    if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) state_ |= MINIMIZED;\n    if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) state_ |= ACTIVE;\n    if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) state_ |= FULLSCREEN;\n  }\n}\n\nvoid Task::handle_done() {\n  spdlog::debug(\"{} changed\", repr());\n\n  if (state_ & MAXIMIZED) {\n    button.get_style_context()->add_class(\"maximized\");\n  } else if (!(state_ & MAXIMIZED)) {\n    button.get_style_context()->remove_class(\"maximized\");\n  }\n\n  if (state_ & MINIMIZED) {\n    button.get_style_context()->add_class(\"minimized\");\n  } else if (!(state_ & MINIMIZED)) {\n    button.get_style_context()->remove_class(\"minimized\");\n  }\n\n  if (state_ & ACTIVE) {\n    button.get_style_context()->add_class(\"active\");\n  } else if (!(state_ & ACTIVE)) {\n    button.get_style_context()->remove_class(\"active\");\n  }\n\n  if (state_ & FULLSCREEN) {\n    button.get_style_context()->add_class(\"fullscreen\");\n  } else if (!(state_ & FULLSCREEN)) {\n    button.get_style_context()->remove_class(\"fullscreen\");\n  }\n\n  if (config_[\"active-first\"].isBool() && config_[\"active-first\"].asBool() && active())\n    tbar_->move_button(button, 0);\n\n  tbar_->dp.emit();\n}\n\nvoid Task::handle_closed() {\n  spdlog::debug(\"{} closed\", repr());\n  zwlr_foreign_toplevel_handle_v1_destroy(handle_);\n  handle_ = nullptr;\n  if (button_visible_) {\n    tbar_->remove_button(button);\n    button_visible_ = false;\n  }\n  tbar_->remove_task(id_);\n}\n\nbool Task::handle_clicked(GdkEventButton* bt) {\n  /* filter out additional events for double/triple clicks */\n  if (bt->type == GDK_BUTTON_PRESS) {\n    /* save where the button press occurred in case it becomes a drag */\n    drag_start_button = bt->button;\n    drag_start_x = bt->x;\n    drag_start_y = bt->y;\n  }\n\n  std::string action;\n  if (config_[\"on-click\"].isString() && bt->button == 1)\n    action = config_[\"on-click\"].asString();\n  else if (config_[\"on-click-middle\"].isString() && bt->button == 2)\n    action = config_[\"on-click-middle\"].asString();\n  else if (config_[\"on-click-right\"].isString() && bt->button == 3)\n    action = config_[\"on-click-right\"].asString();\n\n  if (action.empty())\n    return true;\n  else if (action == \"activate\")\n    activate();\n  else if (action == \"minimize\") {\n    set_minimize_hint();\n    minimize(!minimized());\n  } else if (action == \"minimize-raise\") {\n    set_minimize_hint();\n    if (minimized())\n      minimize(false);\n    else if (active())\n      minimize(true);\n    else\n      activate();\n  } else if (action == \"maximize\")\n    maximize(!maximized());\n  else if (action == \"fullscreen\")\n    fullscreen(!fullscreen());\n  else if (action == \"close\")\n    close();\n  else\n    spdlog::warn(\"Unknown action {}\", action);\n\n  drag_start_button = -1;\n  return true;\n}\n\nbool Task::handle_motion_notify(GdkEventMotion* mn) {\n  if (drag_start_button == -1) return false;\n\n  if (button.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) {\n    /* start drag in addition to other assigned action */\n    auto target_list = Gtk::TargetList::create(target_entries);\n    auto refptr = Glib::RefPtr<Gtk::TargetList>(target_list);\n    auto drag_context =\n        button.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent*)mn);\n  }\n\n  return false;\n}\n\nvoid Task::handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context,\n                                Gtk::SelectionData& selection_data, guint info, guint time) {\n  spdlog::debug(\"drag_data_get\");\n  void* button_addr = (void*)&this->button;\n\n  selection_data.set(\"WAYBAR_TOPLEVEL\", 32, (const guchar*)&button_addr, sizeof(gpointer));\n}\n\nvoid Task::handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,\n                                     Gtk::SelectionData selection_data, guint info, guint time) {\n  spdlog::debug(\"drag_data_received\");\n  auto* raw = selection_data.get_data();\n  if (!raw || selection_data.get_length() < static_cast<int>(sizeof(gpointer))) return;\n  gpointer handle = *(gpointer*)raw;\n  auto dragged_button = (Gtk::Button*)handle;\n\n  if (dragged_button == &this->button) return;\n\n  auto parent_of_dragged = dragged_button->get_parent();\n  auto parent_of_dest = this->button.get_parent();\n\n  if (parent_of_dragged != parent_of_dest) return;\n\n  auto box = (Gtk::Box*)parent_of_dragged;\n\n  auto position_prop = box->child_property_position(this->button);\n  auto position = position_prop.get_value();\n\n  box->reorder_child(*dragged_button, position);\n}\n\nbool Task::operator==(const Task& o) const { return o.id_ == id_; }\n\nbool Task::operator!=(const Task& o) const { return o.id_ != id_; }\n\nvoid Task::update() {\n  bool markup = config_[\"markup\"].isBool() ? config_[\"markup\"].asBool() : false;\n  std::string title = title_;\n  std::string name = name_;\n  std::string app_id = app_id_;\n  if (markup) {\n    title = Glib::Markup::escape_text(title);\n    name = Glib::Markup::escape_text(name);\n    app_id = Glib::Markup::escape_text(app_id);\n  }\n  if (!format_before_.empty()) {\n    auto txt =\n        fmt::format(fmt::runtime(format_before_), fmt::arg(\"title\", title), fmt::arg(\"name\", name),\n                    fmt::arg(\"app_id\", app_id), fmt::arg(\"state\", state_string()),\n                    fmt::arg(\"short_state\", state_string(true)));\n\n    txt = waybar::util::rewriteString(txt, config_[\"rewrite\"]);\n\n    if (markup)\n      text_before_.set_markup(txt);\n    else\n      text_before_.set_label(txt);\n    text_before_.show();\n  }\n  if (!format_after_.empty()) {\n    auto txt =\n        fmt::format(fmt::runtime(format_after_), fmt::arg(\"title\", title), fmt::arg(\"name\", name),\n                    fmt::arg(\"app_id\", app_id), fmt::arg(\"state\", state_string()),\n                    fmt::arg(\"short_state\", state_string(true)));\n\n    txt = waybar::util::rewriteString(txt, config_[\"rewrite\"]);\n\n    if (markup)\n      text_after_.set_markup(txt);\n    else\n      text_after_.set_label(txt);\n    text_after_.show();\n  }\n\n  if (!format_tooltip_.empty()) {\n    auto txt =\n        fmt::format(fmt::runtime(format_tooltip_), fmt::arg(\"title\", title), fmt::arg(\"name\", name),\n                    fmt::arg(\"app_id\", app_id), fmt::arg(\"state\", state_string()),\n                    fmt::arg(\"short_state\", state_string(true)));\n\n    txt = waybar::util::rewriteString(txt, config_[\"rewrite\"]);\n\n    if (markup)\n      button.set_tooltip_markup(txt);\n    else\n      button.set_tooltip_markup(txt);\n  }\n}\n\nvoid Task::maximize(bool set) {\n  if (set)\n    zwlr_foreign_toplevel_handle_v1_set_maximized(handle_);\n  else\n    zwlr_foreign_toplevel_handle_v1_unset_maximized(handle_);\n}\n\nvoid Task::minimize(bool set) {\n  if (set)\n    zwlr_foreign_toplevel_handle_v1_set_minimized(handle_);\n  else\n    zwlr_foreign_toplevel_handle_v1_unset_minimized(handle_);\n}\n\nvoid Task::activate() { zwlr_foreign_toplevel_handle_v1_activate(handle_, seat_); }\n\nvoid Task::fullscreen(bool set) {\n  if (zwlr_foreign_toplevel_handle_v1_get_version(handle_) <\n      ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {\n    spdlog::warn(\"Foreign toplevel manager server does not support for set/unset fullscreen.\");\n    return;\n  }\n\n  if (set)\n    zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr);\n  else\n    zwlr_foreign_toplevel_handle_v1_unset_fullscreen(handle_);\n}\n\nvoid Task::close() { zwlr_foreign_toplevel_handle_v1_close(handle_); }\n\n/* Taskbar class implementation */\nstatic void handle_global(void* data, struct wl_registry* registry, uint32_t name,\n                          const char* interface, uint32_t version) {\n  if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) {\n    static_cast<Taskbar*>(data)->register_manager(registry, name, version);\n  } else if (std::strcmp(interface, wl_seat_interface.name) == 0) {\n    static_cast<Taskbar*>(data)->register_seat(registry, name, version);\n  }\n}\n\nstatic void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) {\n  /* Nothing to do here */\n}\n\nstatic const wl_registry_listener registry_listener_impl = {.global = handle_global,\n                                                            .global_remove = handle_global_remove};\n\nTaskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Value& config)\n    : waybar::AModule(config, \"taskbar\", id, false, false),\n      bar_(bar),\n      box_{bar.orientation, 0},\n      manager_{nullptr},\n      seat_{nullptr} {\n  box_.set_name(\"taskbar\");\n  if (!id.empty()) {\n    box_.get_style_context()->add_class(id);\n  }\n  box_.get_style_context()->add_class(MODULE_CLASS);\n  box_.get_style_context()->add_class(\"empty\");\n  event_box_.add(box_);\n\n  struct wl_display* display = Client::inst()->wl_display;\n  struct wl_registry* registry = wl_display_get_registry(display);\n\n  wl_registry_add_listener(registry, &registry_listener_impl, this);\n  wl_display_roundtrip(display);\n\n  if (!manager_) {\n    spdlog::error(\"Failed to register as toplevel manager\");\n    return;\n  }\n  if (!seat_) {\n    spdlog::error(\"Failed to get wayland seat\");\n    return;\n  }\n\n  /* Get the configured icon theme if specified */\n  if (config_[\"icon-theme\"].isArray()) {\n    for (auto& c : config_[\"icon-theme\"]) {\n      icon_loader_.add_custom_icon_theme(c.asString());\n    }\n  } else if (config_[\"icon-theme\"].isString()) {\n    icon_loader_.add_custom_icon_theme(config_[\"icon-theme\"].asString());\n  }\n\n  // Load ignore-list\n  if (config_[\"ignore-list\"].isArray()) {\n    for (auto& app_name : config_[\"ignore-list\"]) {\n      ignore_list_.emplace(app_name.asString());\n    }\n  }\n\n  // Load app_id remappings\n  if (config_[\"app_ids-mapping\"].isObject()) {\n    const Json::Value& mapping = config_[\"app_ids-mapping\"];\n    const std::vector<std::string> app_ids = config_[\"app_ids-mapping\"].getMemberNames();\n    for (auto& app_id : app_ids) {\n      app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());\n    }\n  }\n\n  for (auto& t : tasks_) {\n    t->handle_app_id(t->app_id().c_str());\n  }\n}\n\nTaskbar::~Taskbar() {\n  if (manager_) {\n    struct wl_display* display = Client::inst()->wl_display;\n    /*\n     * Send `stop` request and wait for one roundtrip.\n     * This is not quite correct as the protocol encourages us to wait for the .finished event,\n     * but it should work with wlroots foreign toplevel manager implementation.\n     */\n    zwlr_foreign_toplevel_manager_v1_stop(manager_);\n    wl_display_roundtrip(display);\n\n    if (manager_) {\n      spdlog::warn(\"Foreign toplevel manager destroyed before .finished event\");\n      zwlr_foreign_toplevel_manager_v1_destroy(manager_);\n      manager_ = nullptr;\n    }\n  }\n}\n\nvoid Taskbar::update() {\n  for (auto& t : tasks_) {\n    t->update();\n  }\n\n  if (config_[\"sort-by-app-id\"].asBool()) {\n    std::stable_sort(tasks_.begin(), tasks_.end(),\n                     [](const std::unique_ptr<Task>& a, const std::unique_ptr<Task>& b) {\n                       return a->app_id() < b->app_id();\n                     });\n\n    for (unsigned long i = 0; i < tasks_.size(); i++) {\n      move_button(tasks_[i]->button, i);\n    }\n  }\n\n  AModule::update();\n}\n\nstatic void tm_handle_toplevel(void* data, struct zwlr_foreign_toplevel_manager_v1* manager,\n                               struct zwlr_foreign_toplevel_handle_v1* tl_handle) {\n  return static_cast<Taskbar*>(data)->handle_toplevel_create(tl_handle);\n}\n\nstatic void tm_handle_finished(void* data, struct zwlr_foreign_toplevel_manager_v1* manager) {\n  return static_cast<Taskbar*>(data)->handle_finished();\n}\n\nstatic const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = {\n    .toplevel = tm_handle_toplevel,\n    .finished = tm_handle_finished,\n};\n\nvoid Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) {\n  if (manager_) {\n    spdlog::warn(\"Register foreign toplevel manager again although already existing!\");\n    return;\n  }\n  if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) {\n    spdlog::warn(\n        \"Foreign toplevel manager server does not have the appropriate version.\"\n        \" To be able to use all features, you need at least version 2, but server is version {}\",\n        version);\n  }\n\n  // limit version to a highest supported by the client protocol file\n  version = std::min<uint32_t>(version, zwlr_foreign_toplevel_manager_v1_interface.version);\n\n  manager_ = static_cast<struct zwlr_foreign_toplevel_manager_v1*>(\n      wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version));\n\n  if (manager_)\n    zwlr_foreign_toplevel_manager_v1_add_listener(manager_, &toplevel_manager_impl, this);\n  else\n    spdlog::debug(\"Failed to register manager\");\n}\n\nvoid Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) {\n  if (seat_) {\n    spdlog::warn(\"Register seat again although already existing!\");\n    return;\n  }\n  version = std::min<uint32_t>(version, wl_seat_interface.version);\n\n  seat_ = static_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, version));\n}\n\nvoid Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1* tl_handle) {\n  tasks_.push_back(std::make_unique<Task>(bar_, config_, this, tl_handle, seat_));\n}\n\nvoid Taskbar::handle_finished() {\n  zwlr_foreign_toplevel_manager_v1_destroy(manager_);\n  manager_ = nullptr;\n}\n\nvoid Taskbar::add_button(Gtk::Button& bt) {\n  box_.pack_start(bt, false, false);\n  box_.get_style_context()->remove_class(\"empty\");\n}\n\nvoid Taskbar::move_button(Gtk::Button& bt, int pos) { box_.reorder_child(bt, pos); }\n\nvoid Taskbar::remove_button(Gtk::Button& bt) {\n  box_.remove(bt);\n  if (box_.get_children().empty()) {\n    box_.get_style_context()->add_class(\"empty\");\n  }\n}\n\nvoid Taskbar::remove_task(uint32_t id) {\n  auto it = std::find_if(std::begin(tasks_), std::end(tasks_),\n                         [id](const TaskPtr& p) { return p->id() == id; });\n\n  if (it == std::end(tasks_)) {\n    spdlog::warn(\"Can't find task with id {}\", id);\n    return;\n  }\n\n  tasks_.erase(it);\n}\n\nbool Taskbar::show_output(struct wl_output* output) const {\n  return output == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());\n}\n\nbool Taskbar::all_outputs() const {\n  return config_[\"all-outputs\"].isBool() && config_[\"all-outputs\"].asBool();\n}\n\nconst IconLoader& Taskbar::icon_loader() const { return icon_loader_; }\n\nconst std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }\n\nconst std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const {\n  return app_ids_replace_map_;\n}\n\n} /* namespace waybar::modules::wlr */\n"
  },
  {
    "path": "src/util/audio_backend.cpp",
    "content": "#include \"util/audio_backend.hpp\"\n\n#include <fmt/core.h>\n#include <pulse/def.h>\n#include <pulse/error.h>\n#include <pulse/introspect.h>\n#include <pulse/subscribe.h>\n#include <pulse/volume.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <cmath>\n#include <stdexcept>\n#include <utility>\n\nnamespace waybar::util {\n\nAudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag)\n    : mainloop_(nullptr),\n      mainloop_api_(nullptr),\n      context_(nullptr),\n      volume_(0),\n      muted_(false),\n      source_volume_(0),\n      source_muted_(false),\n      on_updated_cb_(std::move(on_updated_cb)) {\n  // Initialize pa_volume_ with safe defaults\n  pa_cvolume_init(&pa_volume_);\n  mainloop_ = pa_threaded_mainloop_new();\n  if (mainloop_ == nullptr) {\n    throw std::runtime_error(\"pa_mainloop_new() failed.\");\n  }\n  pa_threaded_mainloop_lock(mainloop_);\n  mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);\n  connectContext();\n  if (pa_threaded_mainloop_start(mainloop_) < 0) {\n    throw std::runtime_error(\"pa_mainloop_run() failed.\");\n  }\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nAudioBackend::~AudioBackend() {\n  if (mainloop_ != nullptr) {\n    // Lock the mainloop so we can safely disconnect the context.\n    // This must be done before stopping the thread.\n    pa_threaded_mainloop_lock(mainloop_);\n    if (context_ != nullptr) {\n      pa_context_disconnect(context_);\n      pa_context_unref(context_);\n      context_ = nullptr;\n    }\n    pa_threaded_mainloop_unlock(mainloop_);\n    pa_threaded_mainloop_stop(mainloop_);\n    pa_threaded_mainloop_free(mainloop_);\n  }\n}\n\nstd::shared_ptr<AudioBackend> AudioBackend::getInstance(std::function<void()> on_updated_cb) {\n  private_constructor_tag tag;\n  return std::make_shared<AudioBackend>(on_updated_cb, tag);\n}\n\nvoid AudioBackend::connectContext() {\n  context_ = pa_context_new(mainloop_api_, \"waybar\");\n  if (context_ == nullptr) {\n    throw std::runtime_error(\"pa_context_new() failed.\");\n  }\n  pa_context_set_state_callback(context_, contextStateCb, this);\n  if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {\n    auto err =\n        fmt::format(\"pa_context_connect() failed: {}\", pa_strerror(pa_context_errno(context_)));\n    throw std::runtime_error(err);\n  }\n}\n\nvoid AudioBackend::contextStateCb(pa_context* c, void* data) {\n  auto* backend = static_cast<AudioBackend*>(data);\n  switch (pa_context_get_state(c)) {\n    case PA_CONTEXT_TERMINATED:\n      // Only quit the mainloop if this is still the active context.\n      // During reconnection, the old context fires TERMINATED after the new one\n      // has already been created; quitting in that case would kill the new context.\n      // Note: context_ is only written from PA callbacks (while the mainloop lock is\n      // held), so this comparison is safe within any PA callback.\n      if (backend->context_ == nullptr || backend->context_ == c) {\n        backend->mainloop_api_->quit(backend->mainloop_api_, 0);\n      }\n      break;\n    case PA_CONTEXT_READY:\n      pa_context_get_server_info(c, serverInfoCb, data);\n      pa_context_set_subscribe_callback(c, subscribeCb, data);\n      pa_context_subscribe(c,\n                           static_cast<enum pa_subscription_mask>(\n                               static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |\n                               static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |\n                               static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |\n                               static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |\n                               static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),\n                           nullptr, nullptr);\n      break;\n    case PA_CONTEXT_FAILED:\n      // When pulseaudio server restarts, the connection is \"failed\". Try to reconnect.\n      // pa_threaded_mainloop_lock is already acquired in callback threads.\n      // So there is no need to lock it again.\n      if (backend->context_ != nullptr) {\n        pa_context_disconnect(backend->context_);\n        pa_context_unref(backend->context_);\n        backend->context_ = nullptr;\n      }\n      backend->connectContext();\n      break;\n    case PA_CONTEXT_CONNECTING:\n    case PA_CONTEXT_AUTHORIZING:\n    case PA_CONTEXT_SETTING_NAME:\n    default:\n      break;\n  }\n}\n\n/*\n * Called when an event we subscribed to occurs.\n */\nvoid AudioBackend::subscribeCb(pa_context* context, pa_subscription_event_type_t type, uint32_t idx,\n                               void* data) {\n  unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;\n  unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;\n  if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) {\n    return;\n  }\n  if (facility == PA_SUBSCRIPTION_EVENT_SERVER) {\n    pa_context_get_server_info(context, serverInfoCb, data);\n  } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {\n    pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);\n  } else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {\n    pa_context_get_sink_info_list(context, sinkInfoCb, data);\n  } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {\n    pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);\n  } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {\n    pa_context_get_source_info_list(context, sourceInfoCb, data);\n  }\n}\n\n/*\n * Called in response to a volume change request\n */\nvoid AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) {\n  auto* backend = static_cast<AudioBackend*>(data);\n  if (success != 0) {\n    if ((backend->context_ != nullptr) &&\n        pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {\n      pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);\n    }\n  } else {\n    spdlog::debug(\"Volume modification failed\");\n  }\n}\n\n/*\n * Called when the requested sink information is ready.\n */\nvoid AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, int /*eol*/,\n                              void* data) {\n  if (i == nullptr) return;\n\n  auto running = i->state == PA_SINK_RUNNING;\n  auto idle = i->state == PA_SINK_IDLE;\n  spdlog::trace(\"Sink name {} Running:[{}] Idle:[{}]\", i->name, running, idle);\n\n  auto* backend = static_cast<AudioBackend*>(data);\n\n  if (!backend->ignored_sinks_.empty()) {\n    for (const auto& ignored_sink : backend->ignored_sinks_) {\n      if (ignored_sink == i->description) {\n        if (i->name == backend->current_sink_name_) {\n          // If the current sink happens to be ignored it is never considered running\n          // so it will be replaced with another sink.\n          backend->current_sink_running_ = false;\n        }\n\n        return;\n      }\n    }\n  }\n\n  backend->default_sink_running_ = backend->default_sink_name == i->name &&\n                                   (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);\n\n  if (i->name != backend->default_sink_name && !backend->default_sink_running_) {\n    return;\n  }\n\n  if (backend->current_sink_name_ == i->name) {\n    backend->current_sink_running_ = (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE);\n  }\n\n  if (!backend->current_sink_running_ &&\n      (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE)) {\n    backend->current_sink_name_ = i->name;\n    backend->current_sink_running_ = true;\n  }\n\n  if (backend->current_sink_name_ == i->name) {\n    // Safely copy the volume structure\n    if (pa_cvolume_valid(&i->volume) != 0) {\n      backend->pa_volume_ = i->volume;\n      float volume =\n          static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};\n      backend->sink_idx_ = i->index;\n      backend->volume_ = std::round(volume * 100.0F);\n    } else {\n      spdlog::error(\"Invalid volume structure received from PulseAudio\");\n      // Initialize with safe defaults\n      pa_cvolume_init(&backend->pa_volume_);\n      backend->volume_ = 0;\n    }\n\n    backend->muted_ = i->mute != 0;\n    backend->desc_ = i->description;\n    backend->monitor_ = i->monitor_source_name;\n    backend->port_name_ = i->active_port != nullptr ? i->active_port->name : \"Unknown\";\n    if (const auto* ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {\n      backend->form_factor_ = ff;\n    } else {\n      backend->form_factor_ = \"\";\n    }\n    backend->on_updated_cb_();\n  }\n}\n\n/*\n * Called when the requested source information is ready.\n */\nvoid AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i, int /*eol*/,\n                                void* data) {\n  auto* backend = static_cast<AudioBackend*>(data);\n  if (i != nullptr && backend->default_source_name_ == i->name) {\n    auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};\n    backend->source_volume_ = std::round(source_volume * 100.0F);\n    backend->source_idx_ = i->index;\n    backend->source_muted_ = i->mute != 0;\n    backend->source_desc_ = i->description;\n    backend->source_port_name_ = i->active_port != nullptr ? i->active_port->name : \"Unknown\";\n    backend->on_updated_cb_();\n  }\n}\n\n/*\n * Called when the requested information on the server is ready. This is\n * used to find the default PulseAudio sink.\n */\nvoid AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) {\n  auto* backend = static_cast<AudioBackend*>(data);\n  if (i == nullptr) return;\n  backend->current_sink_name_ = i->default_sink_name ? i->default_sink_name : \"\";\n  backend->default_sink_name = i->default_sink_name ? i->default_sink_name : \"\";\n  backend->default_source_name_ = i->default_source_name ? i->default_source_name : \"\";\n\n  pa_context_get_sink_info_list(context, sinkInfoCb, data);\n  pa_context_get_source_info_list(context, sourceInfoCb, data);\n}\n\nvoid AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {\n  // Early return if context is not ready\n  if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {\n    spdlog::error(\"PulseAudio context not ready\");\n    return;\n  }\n\n  // Prepare volume structure\n  pa_cvolume pa_volume;\n\n  pa_cvolume_init(&pa_volume);\n\n  // Use existing volume structure if valid, otherwise create a safe default\n  if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {\n    pa_volume = pa_volume_;\n  } else {\n    // Set stereo as a safe default\n    pa_volume.channels = 2;\n    spdlog::debug(\"Using default stereo volume structure\");\n  }\n\n  // Set the volume safely\n  volume = std::clamp(volume, min_volume, max_volume);\n  pa_volume_t vol = volume * (static_cast<double>(PA_VOLUME_NORM) / 100);\n\n  // Set all channels to the same volume manually to avoid pa_cvolume_set\n  for (uint8_t i = 0; i < pa_volume.channels; i++) {\n    pa_volume.values[i] = vol;\n  }\n\n  // Apply the volume change\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nvoid AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {\n  // Early return if context is not ready\n  if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {\n    spdlog::error(\"PulseAudio context not ready\");\n    return;\n  }\n\n  // Prepare volume structure\n  pa_cvolume pa_volume;\n  pa_cvolume_init(&pa_volume);\n\n  // Use existing volume structure if valid, otherwise create a safe default\n  if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {\n    pa_volume = pa_volume_;\n  } else {\n    // Set stereo as a safe default\n    pa_volume.channels = 2;\n    spdlog::debug(\"Using default stereo volume structure\");\n\n    // Initialize all channels to current volume level\n    double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;\n    pa_volume_t vol = volume_ * volume_tick;\n    for (uint8_t i = 0; i < pa_volume.channels; i++) {\n      pa_volume.values[i] = vol;\n    }\n\n    // No need to continue with volume change if we had to create a new structure\n    pa_threaded_mainloop_lock(mainloop_);\n    pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);\n    pa_threaded_mainloop_unlock(mainloop_);\n    return;\n  }\n\n  // Calculate volume change\n  double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;\n  pa_volume_t change;\n  max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));\n\n  if (change_type == ChangeType::Increase && volume_ < max_volume) {\n    // Calculate how much to increase\n    if (volume_ + step > max_volume) {\n      change = round((max_volume - volume_) * volume_tick);\n    } else {\n      change = round(step * volume_tick);\n    }\n\n    // Manually increase each channel's volume\n    for (uint8_t i = 0; i < pa_volume.channels; i++) {\n      pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);\n    }\n  } else if (change_type == ChangeType::Decrease && volume_ > 0) {\n    // Calculate how much to decrease\n    if (volume_ - step < 0) {\n      change = round(volume_ * volume_tick);\n    } else {\n      change = round(step * volume_tick);\n    }\n\n    // Manually decrease each channel's volume\n    for (uint8_t i = 0; i < pa_volume.channels; i++) {\n      pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0;\n    }\n  } else {\n    // No change needed\n    return;\n  }\n\n  // Apply the volume change\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nvoid AudioBackend::toggleSinkMute() {\n  if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;\n  muted_ = !muted_;\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,\n                                    nullptr);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nvoid AudioBackend::toggleSinkMute(bool mute) {\n  if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;\n  muted_ = mute;\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast<int>(muted_), nullptr,\n                                    nullptr);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nvoid AudioBackend::toggleSourceMute() {\n  if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;\n  source_muted_ = !source_muted_;\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),\n                                      nullptr, nullptr);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nvoid AudioBackend::toggleSourceMute(bool mute) {\n  if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;\n  source_muted_ = mute;\n  pa_threaded_mainloop_lock(mainloop_);\n  pa_context_set_source_mute_by_index(context_, source_idx_, static_cast<int>(source_muted_),\n                                      nullptr, nullptr);\n  pa_threaded_mainloop_unlock(mainloop_);\n}\n\nbool AudioBackend::isBluetooth() {\n  return monitor_.find(\"a2dp_sink\") != std::string::npos ||  // PulseAudio\n         monitor_.find(\"a2dp-sink\") != std::string::npos ||  // PipeWire\n         monitor_.find(\"bluez\") != std::string::npos;\n}\n\nvoid AudioBackend::setIgnoredSinks(const Json::Value& config) {\n  if (config.isArray()) {\n    for (const auto& ignored_sink : config) {\n      if (ignored_sink.isString()) {\n        ignored_sinks_.push_back(ignored_sink.asString());\n      }\n    }\n  }\n}\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/backlight_backend.cpp",
    "content": "#include \"util/backlight_backend.hpp\"\n\n#include <fmt/core.h>\n#include <spdlog/spdlog.h>\n#include <sys/epoll.h>\n\n#include <cmath>\n#include <optional>\n#include <utility>\n\n#include \"util/udev_deleter.hpp\"\n\nnamespace {\nclass FileDescriptor {\n public:\n  explicit FileDescriptor(int fd) : fd_(fd) {}\n  FileDescriptor(const FileDescriptor& other) = delete;\n  FileDescriptor(FileDescriptor&& other) noexcept = delete;\n  FileDescriptor& operator=(const FileDescriptor& other) = delete;\n  FileDescriptor& operator=(FileDescriptor&& other) noexcept = delete;\n  ~FileDescriptor() {\n    if (fd_ != -1) {\n      if (close(fd_) != 0) {\n        fmt::print(stderr, \"Failed to close fd: {}\\n\", errno);\n      }\n    }\n  }\n  int get() const { return fd_; }\n\n private:\n  int fd_;\n};\n\nvoid check_eq(int rc, int expected, const char* message = \"eq, rc was: \") {\n  if (rc != expected) {\n    throw std::runtime_error(fmt::format(fmt::runtime(message), rc));\n  }\n}\n\nvoid check_neq(int rc, int bad_rc, const char* message = \"neq, rc was: \") {\n  if (rc == bad_rc) {\n    throw std::runtime_error(fmt::format(fmt::runtime(message), rc));\n  }\n}\n\nvoid check0(int rc, const char* message = \"rc wasn't 0\") { check_eq(rc, 0, message); }\n\nvoid check_gte(int rc, int gte, const char* message = \"rc was: \") {\n  if (rc < gte) {\n    throw std::runtime_error(fmt::format(fmt::runtime(message), rc));\n  }\n}\n\nvoid check_nn(const void* ptr, const char* message = \"ptr was null\") {\n  if (ptr == nullptr) {\n    throw std::runtime_error(message);\n  }\n}\n\n}  // namespace\n\nnamespace waybar::util {\n\nstatic void upsert_device(std::vector<BacklightDevice>& devices, udev_device* dev) {\n  const char* name = udev_device_get_sysname(dev);\n  check_nn(name);\n\n  const char* actual_brightness_attr =\n      strncmp(name, \"amdgpu_bl\", 9) == 0 || strcmp(name, \"apple-panel-bl\") == 0\n          ? \"brightness\"\n          : \"actual_brightness\";\n\n  const char* actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);\n  const char* max = udev_device_get_sysattr_value(dev, \"max_brightness\");\n  const char* power = udev_device_get_sysattr_value(dev, \"bl_power\");\n\n  auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice& device) {\n    return device.name() == name;\n  });\n  if (found != devices.end()) {\n    if (actual != nullptr) {\n      try {\n        found->set_actual(std::stoi(actual));\n      } catch (const std::exception&) {\n      }\n    }\n    if (max != nullptr) {\n      try {\n        found->set_max(std::stoi(max));\n      } catch (const std::exception&) {\n      }\n    }\n    if (power != nullptr) {\n      try {\n        found->set_powered(std::stoi(power) == 0);\n      } catch (const std::exception&) {\n      }\n    }\n  } else {\n    int actual_int = 0, max_int = 0;\n    bool power_bool = true;\n    try {\n      if (actual != nullptr) actual_int = std::stoi(actual);\n    } catch (const std::exception&) {\n    }\n    try {\n      if (max != nullptr) max_int = std::stoi(max);\n    } catch (const std::exception&) {\n    }\n    try {\n      if (power != nullptr) power_bool = std::stoi(power) == 0;\n    } catch (const std::exception&) {\n    }\n    devices.emplace_back(name, actual_int, max_int, power_bool);\n  }\n}\n\nstatic void enumerate_devices(std::vector<BacklightDevice>& devices, udev* udev) {\n  std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};\n  udev_enumerate_add_match_subsystem(enumerate.get(), \"backlight\");\n  udev_enumerate_scan_devices(enumerate.get());\n  udev_list_entry* enum_devices = udev_enumerate_get_list_entry(enumerate.get());\n  udev_list_entry* dev_list_entry;\n  udev_list_entry_foreach(dev_list_entry, enum_devices) {\n    const char* path = udev_list_entry_get_name(dev_list_entry);\n    std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};\n    check_nn(dev.get(), \"dev new failed\");\n    upsert_device(devices, dev.get());\n  }\n}\n\nBacklightDevice::BacklightDevice(std::string name, int actual, int max, bool powered)\n    : name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}\n\nstd::string BacklightDevice::name() const { return name_; }\n\nint BacklightDevice::get_actual() const { return actual_; }\n\nvoid BacklightDevice::set_actual(int actual) { actual_ = actual; }\n\nint BacklightDevice::get_max() const { return max_; }\n\nvoid BacklightDevice::set_max(int max) { max_ = max; }\n\nbool BacklightDevice::get_powered() const { return powered_; }\n\nvoid BacklightDevice::set_powered(bool powered) { powered_ = powered; }\n\nBacklightBackend::BacklightBackend(std::chrono::milliseconds interval,\n                                   std::function<void()> on_updated_cb)\n    : on_updated_cb_(std::move(on_updated_cb)), polling_interval_(interval), previous_best_({}) {\n  std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};\n  check_nn(udev_check.get(), \"Udev check new failed\");\n  enumerate_devices(devices_, udev_check.get());\n  if (devices_.empty()) {\n    throw std::runtime_error(\"No backlight found\");\n  }\n\n#ifdef HAVE_LOGIN_PROXY\n  // Connect to the login interface\n  login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n      Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"org.freedesktop.login1\",\n      \"/org/freedesktop/login1/session/auto\", \"org.freedesktop.login1.Session\");\n\n  if (!login_proxy_) {\n    login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(\n        Gio::DBus::BusType::BUS_TYPE_SYSTEM, \"org.freedesktop.login1\",\n        \"/org/freedesktop/login1/session/self\", \"org.freedesktop.login1.Session\");\n  }\n#endif\n\n  udev_thread_ = [this] {\n    std::unique_ptr<udev, UdevDeleter> udev{udev_new()};\n    check_nn(udev.get(), \"Udev new failed\");\n\n    std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{\n        udev_monitor_new_from_netlink(udev.get(), \"udev\")};\n    check_nn(mon.get(), \"udev monitor new failed\");\n    check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), \"backlight\", nullptr), 0,\n              \"udev failed to add monitor filter: \");\n    udev_monitor_enable_receiving(mon.get());\n\n    auto udev_fd = udev_monitor_get_fd(mon.get());\n\n    auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};\n    check_neq(epoll_fd.get(), -1, \"epoll init failed: \");\n    epoll_event ctl_event{};\n    ctl_event.events = EPOLLIN;\n    ctl_event.data.fd = udev_fd;\n\n    check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),\n           \"epoll_ctl failed: {}\");\n    epoll_event events[EPOLL_MAX_EVENTS];\n\n    while (udev_thread_.isRunning()) {\n      const int event_count =\n          epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, this->polling_interval_.count());\n      if (!udev_thread_.isRunning()) {\n        break;\n      }\n      decltype(devices_) devices;\n      {\n        std::scoped_lock<std::mutex> lock(udev_thread_mutex_);\n        devices = devices_;\n      }\n      for (int i = 0; i < event_count; ++i) {\n        const auto& event = events[i];\n        check_eq(event.data.fd, udev_fd, \"unexpected udev fd\");\n        std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};\n        if (!dev) {\n          continue;\n        }\n        upsert_device(devices, dev.get());\n      }\n\n      // Refresh state if timed out\n      if (event_count == 0) {\n        enumerate_devices(devices, udev.get());\n      }\n      {\n        std::scoped_lock<std::mutex> lock(udev_thread_mutex_);\n        devices_ = devices;\n      }\n      this->on_updated_cb_();\n    }\n  };\n}\n\nconst BacklightDevice* BacklightBackend::best_device(const std::vector<BacklightDevice>& devices,\n                                                     std::string_view preferred_device) {\n  const auto found = std::find_if(\n      devices.begin(), devices.end(),\n      [preferred_device](const BacklightDevice& dev) { return dev.name() == preferred_device; });\n  if (found != devices.end()) {\n    return &(*found);\n  }\n\n  const auto max = std::max_element(\n      devices.begin(), devices.end(),\n      [](const BacklightDevice& l, const BacklightDevice& r) { return l.get_max() < r.get_max(); });\n\n  return max == devices.end() ? nullptr : &(*max);\n}\n\nconst BacklightDevice* BacklightBackend::get_previous_best_device() {\n  return previous_best_.has_value() ? &(*previous_best_) : nullptr;\n}\n\nvoid BacklightBackend::set_previous_best_device(const BacklightDevice* device) {\n  if (device == nullptr) {\n    previous_best_ = std::nullopt;\n  } else {\n    previous_best_ = std::optional{*device};\n  }\n}\n\nvoid BacklightBackend::set_scaled_brightness(const std::string& preferred_device, int brightness) {\n  GET_BEST_DEVICE(best, (*this), preferred_device);\n\n  if (best != nullptr) {\n    const auto max = best->get_max();\n    const auto abs_val = static_cast<int>(std::round(brightness * max / 100.0F));\n    set_brightness_internal(best->name(), abs_val, best->get_max());\n  }\n}\n\nvoid BacklightBackend::set_brightness(const std::string& preferred_device, ChangeType change_type,\n                                      double step) {\n  GET_BEST_DEVICE(best, (*this), preferred_device);\n\n  if (best != nullptr) {\n    const auto max = best->get_max();\n\n    const auto abs_step = static_cast<int>(round(step * max / 100.0F));\n\n    const int new_brightness = change_type == ChangeType::Increase ? best->get_actual() + abs_step\n                                                                   : best->get_actual() - abs_step;\n    set_brightness_internal(best->name(), new_brightness, max);\n  }\n}\n\nvoid BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness,\n                                               int max_brightness) {\n  if (!login_proxy_) {\n    spdlog::error(\"Login proxy not available, cannot set brightness\");\n    return;\n  }\n\n  brightness = std::clamp(brightness, 0, max_brightness);\n\n  auto call_args = Glib::VariantContainerBase(\n      g_variant_new(\"(ssu)\", \"backlight\", device_name.c_str(), brightness));\n\n  login_proxy_->call_sync(\"SetBrightness\", call_args);\n}\n\nint BacklightBackend::get_scaled_brightness(const std::string& preferred_device) {\n  GET_BEST_DEVICE(best, (*this), preferred_device);\n\n  if (best != nullptr) {\n    if (best->get_max() == 0) return 0;\n    return static_cast<int>(std::round(best->get_actual() * 100.0F / best->get_max()));\n  }\n\n  return 0;\n}\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/css_reload_helper.cpp",
    "content": "#include \"util/css_reload_helper.hpp\"\n\n#include <poll.h>\n#include <spdlog/spdlog.h>\n#ifndef __OpenBSD__\n#include <sys/inotify.h>\n#else\n#include <sys/event.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#endif\n\n#include <filesystem>\n#include <fstream>\n#include <regex>\n#include <unordered_map>\n\n#include \"config.hpp\"\n#include \"giomm/file.h\"\n#include \"glibmm/refptr.h\"\n\nnamespace {\nconst std::regex IMPORT_REGEX(R\"(@import\\s+(?:url\\()?(?:\"|')([^\"')]+)(?:\"|')\\)?;)\");\n}\n\nwaybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function<void()> callback)\n    : m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {}\n\nstd::string waybar::CssReloadHelper::getFileContents(const std::string& filename) {\n  if (filename.empty()) {\n    return {};\n  }\n\n  std::ifstream file(filename);\n  if (!file.is_open()) {\n    return {};\n  }\n\n  return {(std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()};\n}\n\nstd::string waybar::CssReloadHelper::findPath(const std::string& filename) {\n  // try path and fallback to looking relative to the config\n  std::string result;\n  if (std::filesystem::exists(filename)) {\n    result = filename;\n  } else {\n    result = Config::findConfigPath({filename}).value_or(\"\");\n  }\n\n  // File monitor does not work with symlinks, so resolve them\n  std::string original = result;\n  while (std::filesystem::is_symlink(result)) {\n    result = std::filesystem::read_symlink(result);\n\n    // prevent infinite cycle\n    if (result == original) {\n      break;\n    }\n  }\n\n  return result;\n}\n\nvoid waybar::CssReloadHelper::monitorChanges() {\n  auto files = parseImports(m_cssFile);\n  for (const auto& file : files) {\n    auto gioFile = Gio::File::create_for_path(file);\n    if (!gioFile) {\n      spdlog::error(\"Failed to create file for path: {}\", file);\n      continue;\n    }\n\n    auto fileMonitor = gioFile->monitor_file();\n    if (!fileMonitor) {\n      spdlog::error(\"Failed to create file monitor for path: {}\", file);\n      continue;\n    }\n\n    auto connection = fileMonitor->signal_changed().connect(\n        sigc::mem_fun(*this, &CssReloadHelper::handleFileChange));\n\n    if (!connection.connected()) {\n      spdlog::error(\"Failed to connect to file monitor for path: {}\", file);\n      continue;\n    }\n    m_fileMonitors.emplace_back(std::move(fileMonitor));\n  }\n}\n\nvoid waybar::CssReloadHelper::handleFileChange(Glib::RefPtr<Gio::File> const& file,\n                                               Glib::RefPtr<Gio::File> const& other_type,\n                                               Gio::FileMonitorEvent event_type) {\n  // Multiple events are fired on file changed (attributes, write, changes done hint, etc.), only\n  // fire for one\n  if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {\n    spdlog::debug(\"Reloading style, file changed: {}\", file->get_path());\n    m_callback();\n  }\n}\n\nstd::vector<std::string> waybar::CssReloadHelper::parseImports(const std::string& cssFile) {\n  std::unordered_map<std::string, bool> imports;\n\n  auto cssFullPath = findPath(cssFile);\n  if (cssFullPath.empty()) {\n    spdlog::error(\"Failed to find css file: {}\", cssFile);\n    return {};\n  }\n\n  spdlog::debug(\"Parsing imports for file: {}\", cssFullPath);\n  imports[cssFullPath] = false;\n\n  auto previousSize = 0UL;\n  auto maxIterations = 100U;\n  do {\n    previousSize = imports.size();\n    std::vector<std::string> to_parse;\n    for (const auto& [file, parsed] : imports) {\n      if (!parsed) {\n        to_parse.push_back(file);\n      }\n    }\n    for (const auto& file : to_parse) {\n      parseImports(file, imports);\n    }\n\n  } while (imports.size() > previousSize && maxIterations-- > 0);\n\n  std::vector<std::string> result;\n  for (const auto& [file, parsed] : imports) {\n    if (parsed) {\n      spdlog::debug(\"Adding file to watch list: {}\", file);\n      result.push_back(file);\n    }\n  }\n\n  return result;\n}\n\nvoid waybar::CssReloadHelper::parseImports(const std::string& cssFile,\n                                           std::unordered_map<std::string, bool>& imports) {\n  // if the file has already been parsed, skip\n  if (imports.find(cssFile) != imports.end() && imports[cssFile]) {\n    return;\n  }\n\n  auto contents = getFileContents(cssFile);\n  std::smatch matches;\n  while (std::regex_search(contents, matches, IMPORT_REGEX)) {\n    auto importFile = findPath({matches[1].str()});\n    if (!importFile.empty() && imports.find(importFile) == imports.end()) {\n      imports[importFile] = false;\n    }\n\n    contents = matches.suffix().str();\n  }\n\n  imports[cssFile] = true;\n}\n"
  },
  {
    "path": "src/util/enum.cpp",
    "content": "#include \"util/enum.hpp\"\n\n#include <algorithm>  // for std::transform\n#include <cctype>     // for std::toupper\n#include <iostream>\n#include <map>\n#include <stdexcept>\n#include <string>\n\n#include \"modules/hyprland/workspaces.hpp\"\n#include \"util/string.hpp\"\n\nnamespace waybar::util {\n\ntemplate <typename EnumType>\nEnumParser<EnumType>::EnumParser() = default;\n\ntemplate <typename EnumType>\nEnumParser<EnumType>::~EnumParser() = default;\n\ntemplate <typename EnumType>\nEnumType EnumParser<EnumType>::parseStringToEnum(const std::string& str,\n                                                 const std::map<std::string, EnumType>& enumMap) {\n  // Convert the input string to uppercase\n  std::string uppercaseStr = capitalize(str);\n\n  // Capitalize the map keys before searching\n  std::map<std::string, EnumType> capitalizedEnumMap;\n  std::transform(\n      enumMap.begin(), enumMap.end(), std::inserter(capitalizedEnumMap, capitalizedEnumMap.end()),\n      [](const auto& pair) { return std::make_pair(capitalize(pair.first), pair.second); });\n\n  // Return enum match of string\n  auto it = capitalizedEnumMap.find(uppercaseStr);\n  if (it != capitalizedEnumMap.end()) return it->second;\n\n  // Throw error if it doesn't return\n  throw std::invalid_argument(\"Invalid string representation for enum\");\n}\n\n// Explicit instantiations for specific EnumType types you intend to use\n// Add explicit instantiations for all relevant EnumType types\ntemplate struct EnumParser<modules::hyprland::Workspaces::SortMethod>;\ntemplate struct EnumParser<modules::hyprland::Workspaces::ActiveWindowPosition>;\ntemplate struct EnumParser<util::KillSignalAction>;\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/gtk_icon.cpp",
    "content": "#include \"util/gtk_icon.hpp\"\n\n/* We need a global mutex for accessing the object returned by Gtk::IconTheme::get_default()\n * because it always returns the same object across different threads, and concurrent\n * access can cause data corruption and lead to invalid memory access and crashes.\n * Even concurrent calls that seem read only such as has_icon can cause issues because\n * the GTK lib may update the internal icon cache on this calls.\n */\n\nstd::mutex DefaultGtkIconThemeWrapper::default_theme_mutex;\n\nbool DefaultGtkIconThemeWrapper::has_icon(const std::string& value) {\n  const std::lock_guard<std::mutex> lock(default_theme_mutex);\n\n  return Gtk::IconTheme::get_default()->has_icon(value);\n}\n\nGlib::RefPtr<Gdk::Pixbuf> DefaultGtkIconThemeWrapper::load_icon(\n    const char* name, int tmp_size, Gtk::IconLookupFlags flags,\n    Glib::RefPtr<Gtk::StyleContext> style) {\n  const std::lock_guard<std::mutex> lock(default_theme_mutex);\n\n  auto default_theme = Gtk::IconTheme::get_default();\n\n  auto icon_info = default_theme->lookup_icon(name, tmp_size, flags);\n\n  if (icon_info == nullptr) {\n    return default_theme->load_icon(name, tmp_size, flags);\n  }\n\n  if (style.get() == nullptr) {\n    return icon_info.load_icon();\n  }\n\n  bool is_sym = false;\n  return icon_info.load_symbolic(style, is_sym);\n}\n"
  },
  {
    "path": "src/util/icon_loader.cpp",
    "content": "#include \"util/icon_loader.hpp\"\n\n#include \"util/string.hpp\"\n\nstd::vector<std::string> IconLoader::search_prefix() {\n  std::vector<std::string> prefixes = {\"\"};\n\n  const char* home_env = std::getenv(\"HOME\");\n  std::string home_dir = home_env ? home_env : \"\";\n  if (!home_dir.empty()) {\n    prefixes.push_back(home_dir + \"/.local/share/\");\n  }\n\n  auto xdg_data_dirs = std::getenv(\"XDG_DATA_DIRS\");\n  if (!xdg_data_dirs) {\n    prefixes.emplace_back(\"/usr/share/\");\n    prefixes.emplace_back(\"/usr/local/share/\");\n  } else {\n    std::string xdg_data_dirs_str(xdg_data_dirs);\n    size_t start = 0;\n    size_t end = 0;\n\n    do {\n      end = xdg_data_dirs_str.find(':', start);\n      auto p = xdg_data_dirs_str.substr(start, end - start);\n      prefixes.push_back(trim(p) + \"/\");\n\n      start = end == std::string::npos ? end : end + 1;\n    } while (end != std::string::npos);\n  }\n\n  for (auto& p : prefixes) spdlog::debug(\"Using 'desktop' search path prefix: {}\", p);\n\n  return prefixes;\n}\n\nGlib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_by_name(const std::string& app_id) {\n  static std::vector<std::string> prefixes = search_prefix();\n\n  std::vector<std::string> app_folders = {\"\", \"applications/\", \"applications/kde/\",\n                                          \"applications/org.kde.\"};\n\n  std::vector<std::string> suffixes = {\"\", \".desktop\"};\n\n  for (auto const& prefix : prefixes) {\n    for (auto const& folder : app_folders) {\n      for (auto const& suffix : suffixes) {\n        auto app_info_ =\n            Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);\n        if (!app_info_) {\n          continue;\n        }\n\n        return app_info_;\n      }\n    }\n  }\n\n  return {};\n}\n\nGlib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_desktop_app_info(const std::string& app_id) {\n  auto app_info = get_app_info_by_name(app_id);\n  if (app_info) {\n    return app_info;\n  }\n\n  std::string desktop_file = \"\";\n\n  gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());\n  if (desktop_list != nullptr && desktop_list[0] != nullptr) {\n    for (size_t i = 0; desktop_list[0][i]; i++) {\n      if (desktop_file == \"\") {\n        desktop_file = desktop_list[0][i];\n      } else {\n        auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);\n        if (!tmp_info)\n          // see https://github.com/Alexays/Waybar/issues/1446\n          continue;\n\n        auto startup_class = tmp_info->get_startup_wm_class();\n        if (startup_class == app_id) {\n          desktop_file = desktop_list[0][i];\n          break;\n        }\n      }\n    }\n    g_strfreev(desktop_list[0]);\n  }\n  g_free(desktop_list);\n\n  return get_app_info_by_name(desktop_file);\n}\n\nGlib::RefPtr<Gdk::Pixbuf> IconLoader::load_icon_from_file(std::string const& icon_path, int size) {\n  try {\n    auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size);\n    return pb;\n  } catch (...) {\n    return {};\n  }\n}\n\nstd::string IconLoader::get_icon_name_from_icon_theme(\n    const Glib::RefPtr<Gtk::IconTheme>& icon_theme, const std::string& app_id) {\n  if (icon_theme->lookup_icon(app_id, 24)) return app_id;\n\n  return \"\";\n}\n\nbool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,\n                                 Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size) {\n  std::string ret_icon_name = \"unknown\";\n  if (app_info) {\n    std::string icon_name =\n        get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());\n    if (!icon_name.empty()) {\n      ret_icon_name = icon_name;\n    } else {\n      if (app_info->get_icon()) {\n        ret_icon_name = app_info->get_icon()->to_string();\n      }\n    }\n  }\n\n  Glib::RefPtr<Gdk::Pixbuf> pixbuf;\n  auto scaled_icon_size = size * image.get_scale_factor();\n\n  try {\n    pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE);\n  } catch (...) {\n    if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) {\n      pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size);\n    } else {\n      try {\n        pixbuf = DefaultGtkIconThemeWrapper::load_icon(\n            \"image-missing\", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);\n      } catch (...) {\n        pixbuf = {};\n      }\n    }\n  }\n\n  if (pixbuf) {\n    if (pixbuf->get_width() != scaled_icon_size && pixbuf->get_height() > 0) {\n      int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();\n      pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);\n    }\n    auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(),\n                                                          image.get_window());\n    image.set(surface);\n    return true;\n  }\n\n  return false;\n}\n\nvoid IconLoader::add_custom_icon_theme(const std::string& theme_name) {\n  auto icon_theme = Gtk::IconTheme::create();\n  icon_theme->set_custom_theme(theme_name);\n  custom_icon_themes_.push_back(icon_theme);\n  spdlog::debug(\"Use custom icon theme: {}\", theme_name);\n}\n\nbool IconLoader::image_load_icon(Gtk::Image& image, Glib::RefPtr<Gio::DesktopAppInfo> app_info,\n                                 int size) const {\n  for (auto& icon_theme : custom_icon_themes_) {\n    if (image_load_icon(image, icon_theme, app_info, size)) {\n      return true;\n    }\n  }\n  return image_load_icon(image, default_icon_theme_, app_info, size);\n}\n\nGlib::RefPtr<Gio::DesktopAppInfo> IconLoader::get_app_info_from_app_id_list(\n    const std::string& app_id_list) {\n  std::string app_id;\n  std::istringstream stream(app_id_list);\n  Glib::RefPtr<Gio::DesktopAppInfo> app_info_;\n\n  /* Wayfire sends a list of app-id's in space separated format, other compositors\n   * send a single app-id, but in any case this works fine */\n  while (stream >> app_id) {\n    app_info_ = get_desktop_app_info(app_id);\n    if (app_info_) {\n      return app_info_;\n    }\n\n    auto lower_app_id = app_id;\n    std::ranges::transform(lower_app_id, lower_app_id.begin(),\n                           [](char c) { return std::tolower(c); });\n    app_info_ = get_desktop_app_info(lower_app_id);\n    if (app_info_) {\n      return app_info_;\n    }\n\n    size_t start = 0, end = app_id.size();\n    start = app_id.rfind(\".\", end);\n    std::string app_name = app_id.substr(start + 1, app_id.size());\n    app_info_ = get_desktop_app_info(app_name);\n    if (app_info_) {\n      return app_info_;\n    }\n\n    start = app_id.find(\"-\");\n    app_name = app_id.substr(0, start);\n    app_info_ = get_desktop_app_info(app_name);\n  }\n  return app_info_;\n}\n"
  },
  {
    "path": "src/util/pipewire/pipewire_backend.cpp",
    "content": "#include \"util/pipewire/pipewire_backend.hpp\"\n\n#include \"util/pipewire/privacy_node_info.hpp\"\n\nnamespace waybar::util::PipewireBackend {\n\nstatic void getNodeInfo(void* data_, const struct pw_node_info* info) {\n  auto* pNodeInfo = static_cast<PrivacyNodeInfo*>(data_);\n  pNodeInfo->handleNodeEventInfo(info);\n\n  static_cast<PipewireBackend*>(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit();\n}\n\nstatic const struct pw_node_events NODE_EVENTS = {\n    .version = PW_VERSION_NODE_EVENTS,\n    .info = getNodeInfo,\n};\n\nstatic void proxyDestroy(void* data) {\n  static_cast<PrivacyNodeInfo*>(data)->handleProxyEventDestroy();\n}\n\nstatic const struct pw_proxy_events PROXY_EVENTS = {\n    .version = PW_VERSION_PROXY_EVENTS,\n    .destroy = proxyDestroy,\n};\n\nstatic void registryEventGlobal(void* _data, uint32_t id, uint32_t permissions, const char* type,\n                                uint32_t version, const struct spa_dict* props) {\n  static_cast<PipewireBackend*>(_data)->handleRegistryEventGlobal(id, permissions, type, version,\n                                                                  props);\n}\n\nstatic void registryEventGlobalRemove(void* _data, uint32_t id) {\n  static_cast<PipewireBackend*>(_data)->handleRegistryEventGlobalRemove(id);\n}\n\nstatic const struct pw_registry_events REGISTRY_EVENTS = {\n    .version = PW_VERSION_REGISTRY_EVENTS,\n    .global = registryEventGlobal,\n    .global_remove = registryEventGlobalRemove,\n};\n\nPipewireBackend::PipewireBackend(PrivateConstructorTag tag)\n    : mainloop_(nullptr), context_(nullptr), core_(nullptr) {\n  pw_init(nullptr, nullptr);\n  mainloop_ = pw_thread_loop_new(\"waybar\", nullptr);\n  if (mainloop_ == nullptr) {\n    throw std::runtime_error(\"pw_thread_loop_new() failed.\");\n  }\n\n  pw_thread_loop_lock(mainloop_);\n\n  context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0);\n  if (context_ == nullptr) {\n    pw_thread_loop_unlock(mainloop_);\n    pw_thread_loop_destroy(mainloop_);\n    mainloop_ = nullptr;\n    throw std::runtime_error(\"pa_context_new() failed.\");\n  }\n  core_ = pw_context_connect(context_, nullptr, 0);\n  if (core_ == nullptr) {\n    pw_thread_loop_unlock(mainloop_);\n    pw_context_destroy(context_);\n    context_ = nullptr;\n    pw_thread_loop_destroy(mainloop_);\n    mainloop_ = nullptr;\n    throw std::runtime_error(\"pw_context_connect() failed\");\n  }\n  registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0);\n\n  spa_zero(registryListener_);\n  pw_registry_add_listener(registry_, &registryListener_, &REGISTRY_EVENTS, this);\n  if (pw_thread_loop_start(mainloop_) < 0) {\n    pw_thread_loop_unlock(mainloop_);\n    throw std::runtime_error(\"pw_thread_loop_start() failed.\");\n  }\n  pw_thread_loop_unlock(mainloop_);\n}\n\nPipewireBackend::~PipewireBackend() {\n  if (mainloop_ != nullptr) {\n    pw_thread_loop_lock(mainloop_);\n  }\n\n  if (registry_ != nullptr) {\n    pw_proxy_destroy((struct pw_proxy*)registry_);\n  }\n\n  spa_zero(registryListener_);\n\n  if (core_ != nullptr) {\n    pw_core_disconnect(core_);\n  }\n\n  if (context_ != nullptr) {\n    pw_context_destroy(context_);\n  }\n\n  if (mainloop_ != nullptr) {\n    pw_thread_loop_unlock(mainloop_);\n    pw_thread_loop_stop(mainloop_);\n    pw_thread_loop_destroy(mainloop_);\n  }\n}\n\nstd::shared_ptr<PipewireBackend> PipewireBackend::getInstance() {\n  PrivateConstructorTag tag;\n  return std::make_shared<PipewireBackend>(tag);\n}\n\nvoid PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char* type,\n                                                uint32_t version, const struct spa_dict* props) {\n  if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return;\n\n  const char* lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);\n  if (lookupStr == nullptr) return;\n  std::string mediaClass = lookupStr;\n  enum PrivacyNodeType mediaType = PRIVACY_NODE_TYPE_NONE;\n  if (mediaClass == \"Stream/Input/Video\") {\n    mediaType = PRIVACY_NODE_TYPE_VIDEO_INPUT;\n  } else if (mediaClass == \"Stream/Input/Audio\") {\n    mediaType = PRIVACY_NODE_TYPE_AUDIO_INPUT;\n  } else if (mediaClass == \"Stream/Output/Audio\") {\n    mediaType = PRIVACY_NODE_TYPE_AUDIO_OUTPUT;\n  } else {\n    return;\n  }\n\n  auto* proxy = (pw_proxy*)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo));\n\n  if (proxy == nullptr) return;\n\n  auto* pNodeInfo = (PrivacyNodeInfo*)pw_proxy_get_user_data(proxy);\n  new (pNodeInfo) PrivacyNodeInfo{};\n  pNodeInfo->id = id;\n  pNodeInfo->data = this;\n  pNodeInfo->type = mediaType;\n  pNodeInfo->media_class = mediaClass;\n\n  pw_proxy_add_listener(proxy, &pNodeInfo->proxy_listener, &PROXY_EVENTS, pNodeInfo);\n\n  pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo);\n\n  {\n    std::lock_guard<std::mutex> lock(mutex_);\n    privacy_nodes.insert_or_assign(id, pNodeInfo);\n  }\n}\n\nvoid PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) {\n  mutex_.lock();\n  auto iter = privacy_nodes.find(id);\n  if (iter != privacy_nodes.end()) {\n    privacy_nodes[id]->~PrivacyNodeInfo();\n    privacy_nodes.erase(id);\n  }\n  mutex_.unlock();\n\n  privacy_nodes_changed_signal_event.emit();\n}\n\n}  // namespace waybar::util::PipewireBackend\n"
  },
  {
    "path": "src/util/pipewire/privacy_node_info.cpp",
    "content": "#include \"util/pipewire/privacy_node_info.hpp\"\n\nnamespace waybar::util::PipewireBackend {\n\nstd::string PrivacyNodeInfo::getName() {\n  const std::vector<std::string*> names{&application_name, &node_name};\n  std::string name = \"Unknown Application\";\n  for (const auto& item : names) {\n    if (item != nullptr && !item->empty()) {\n      name = *item;\n      name[0] = toupper(name[0]);\n      break;\n    }\n  }\n  return name;\n}\n\nstd::string PrivacyNodeInfo::getIconName() {\n  const std::vector<std::string*> names{&application_icon_name, &pipewire_access_portal_app_id,\n                                        &application_name, &node_name};\n  std::string name = \"application-x-executable-symbolic\";\n  for (const auto& item : names) {\n    if (item != nullptr && !item->empty() && DefaultGtkIconThemeWrapper::has_icon(*item)) {\n      return *item;\n    }\n  }\n  return name;\n}\n\nvoid PrivacyNodeInfo::handleProxyEventDestroy() {\n  spa_hook_remove(&proxy_listener);\n  spa_hook_remove(&object_listener);\n}\n\nvoid PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info* info) {\n  state = info->state;\n\n  const struct spa_dict_item* item;\n  spa_dict_for_each(item, info->props) {\n    if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) {\n      client_id = strtoul(item->value, nullptr, 10);\n    } else if (strcmp(item->key, PW_KEY_MEDIA_NAME) == 0) {\n      media_name = item->value;\n    } else if (strcmp(item->key, PW_KEY_NODE_NAME) == 0) {\n      node_name = item->value;\n    } else if (strcmp(item->key, PW_KEY_APP_NAME) == 0) {\n      application_name = item->value;\n    } else if (strcmp(item->key, \"pipewire.access.portal.app_id\") == 0) {\n      pipewire_access_portal_app_id = item->value;\n    } else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) {\n      application_icon_name = item->value;\n    } else if (strcmp(item->key, \"stream.monitor\") == 0) {\n      is_monitor = strcmp(item->value, \"true\") == 0;\n    }\n  }\n}\n\n}  // namespace waybar::util::PipewireBackend\n"
  },
  {
    "path": "src/util/portal.cpp",
    "content": "#include \"util/portal.hpp\"\n\n#include <giomm/dbusproxy.h>\n#include <glibmm/variant.h>\n#include <spdlog/spdlog.h>\n\n#include <iostream>\n#include <string>\n\n#include \"fmt/format.h\"\n\nnamespace waybar {\nstatic constexpr const char* PORTAL_BUS_NAME = \"org.freedesktop.portal.Desktop\";\nstatic constexpr const char* PORTAL_OBJ_PATH = \"/org/freedesktop/portal/desktop\";\nstatic constexpr const char* PORTAL_INTERFACE = \"org.freedesktop.portal.Settings\";\nstatic constexpr const char* PORTAL_NAMESPACE = \"org.freedesktop.appearance\";\nstatic constexpr const char* PORTAL_KEY = \"color-scheme\";\n}  // namespace waybar\n\nauto fmt::formatter<waybar::Appearance>::format(waybar::Appearance c, format_context& ctx) const {\n  string_view name;\n  switch (c) {\n    case waybar::Appearance::LIGHT:\n      name = \"light\";\n      break;\n    case waybar::Appearance::DARK:\n      name = \"dark\";\n      break;\n    default:\n      name = \"unknown\";\n      break;\n  }\n  return formatter<string_view>::format(name, ctx);\n}\n\nwaybar::Portal::Portal()\n    : Gio::DBus::Proxy(Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION),\n                       PORTAL_BUS_NAME, PORTAL_OBJ_PATH, PORTAL_INTERFACE),\n      currentMode(Appearance::UNKNOWN) {\n  refreshAppearance();\n};\n\nvoid waybar::Portal::refreshAppearance() {\n  auto params = Glib::Variant<std::tuple<Glib::ustring, Glib::ustring>>::create(\n      {PORTAL_NAMESPACE, PORTAL_KEY});\n  Glib::VariantBase response;\n  try {\n    response = call_sync(std::string(PORTAL_INTERFACE) + \".Read\", params);\n  } catch (const Glib::Error& e) {\n    spdlog::info(\"Unable to receive desktop appearance: {}\", std::string(e.what()));\n    return;\n  }\n\n  // unfortunately, the response is triple-nested, with type (v<v<uint32_t>>),\n  // so we have cast thrice. This is a variation from the freedesktop standard\n  // (it should only be doubly nested) but all implementations appear to do so.\n  //\n  // xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method,\n  // but this version is not yet released.\n  // TODO(xdg-desktop-portal v1.17): switch to ReadOne\n  try {\n    auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(response);\n    Glib::VariantBase modev;\n    container.get_child(modev, 0);\n    auto mode =\n        Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<Glib::Variant<uint32_t>>>>(\n            modev)\n            .get()\n            .get()\n            .get();\n    auto newMode = Appearance(mode);\n    if (newMode == currentMode) {\n      return;\n    }\n    spdlog::info(\"Discovered appearance '{}'\", newMode);\n    currentMode = newMode;\n    m_signal_appearance_changed.emit(currentMode);\n  } catch (const std::bad_cast& e) {\n    spdlog::error(\"Unexpected appearance variant format: {}\", e.what());\n    return;\n  }\n}\n\nwaybar::Appearance waybar::Portal::getAppearance() { return currentMode; };\n\nvoid waybar::Portal::on_signal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,\n                               const Glib::VariantContainerBase& parameters) {\n  spdlog::debug(\"Received signal {}\", (std::string)signal_name);\n  if (signal_name != \"SettingChanged\" || parameters.get_n_children() != 3) {\n    return;\n  }\n  Glib::VariantBase nspcv;\n  Glib::VariantBase keyv;\n  Glib::VariantBase valuev;\n  parameters.get_child(nspcv, 0);\n  parameters.get_child(keyv, 1);\n  parameters.get_child(valuev, 2);\n  auto nspc = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(nspcv).get();\n  auto key = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(keyv).get();\n  if (nspc != PORTAL_NAMESPACE || key != PORTAL_KEY) {\n    return;\n  }\n  auto value =\n      Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::Variant<uint32_t>>>(valuev).get().get();\n  auto newMode = Appearance(value);\n  if (newMode == currentMode) {\n    return;\n  }\n  spdlog::info(\"Received new appearance '{}'\", newMode);\n  currentMode = newMode;\n  m_signal_appearance_changed.emit(currentMode);\n}\n"
  },
  {
    "path": "src/util/prepare_for_sleep.cpp",
    "content": "#include \"util/prepare_for_sleep.h\"\n\n#include <gio/gio.h>\n#include <spdlog/spdlog.h>\n\nnamespace {\nclass PrepareForSleep {\n private:\n  PrepareForSleep() {\n    login1_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);\n    if (login1_connection == nullptr) {\n      spdlog::warn(\"Unable to connect to the SYSTEM Bus!...\");\n    } else {\n      login1_id = g_dbus_connection_signal_subscribe(\n          login1_connection, \"org.freedesktop.login1\", \"org.freedesktop.login1.Manager\",\n          \"PrepareForSleep\", \"/org/freedesktop/login1\", nullptr, G_DBUS_SIGNAL_FLAGS_NONE,\n          prepareForSleep_cb, this, nullptr);\n    }\n  }\n\n  static void prepareForSleep_cb(GDBusConnection* system_bus, const gchar* sender_name,\n                                 const gchar* object_path, const gchar* interface_name,\n                                 const gchar* signal_name, GVariant* parameters,\n                                 gpointer user_data) {\n    if (g_variant_is_of_type(parameters, G_VARIANT_TYPE(\"(b)\")) != 0) {\n      gboolean sleeping;\n      g_variant_get(parameters, \"(b)\", &sleeping);\n\n      auto* self = static_cast<PrepareForSleep*>(user_data);\n      self->signal.emit(sleeping);\n    }\n  }\n\n public:\n  static PrepareForSleep& GetInstance() {\n    static PrepareForSleep instance;\n    return instance;\n  }\n  waybar::SafeSignal<bool> signal;\n\n private:\n  guint login1_id;\n  GDBusConnection* login1_connection;\n};\n}  // namespace\n\nwaybar::SafeSignal<bool>& waybar::util::prepare_for_sleep() {\n  return PrepareForSleep::GetInstance().signal;\n}\n"
  },
  {
    "path": "src/util/regex_collection.cpp",
    "content": "#include \"util/regex_collection.hpp\"\n\n#include <json/value.h>\n#include <spdlog/spdlog.h>\n\n#include <algorithm>\n#include <utility>\n\nnamespace waybar::util {\n\nint default_priority_function(std::string& key) { return 0; }\n\nRegexCollection::RegexCollection(const Json::Value& map, std::string default_repr,\n                                 const std::function<int(std::string&)>& priority_function)\n    : default_repr(std::move(default_repr)) {\n  if (!map.isObject()) {\n    spdlog::warn(\"Mapping is not an object\");\n    return;\n  }\n\n  for (auto it = map.begin(); it != map.end(); ++it) {\n    if (it.key().isString() && it->isString()) {\n      std::string key = it.key().asString();\n      int priority = priority_function(key);\n      try {\n        const std::regex rule{key, std::regex_constants::icase};\n        rules.emplace_back(rule, it->asString(), priority);\n      } catch (const std::regex_error& e) {\n        spdlog::error(\"Invalid rule '{}': {}\", key, e.what());\n      }\n    }\n  }\n\n  std::sort(rules.begin(), rules.end(), [](Rule& a, Rule& b) { return a.priority > b.priority; });\n}\n\nstd::string RegexCollection::find_match(std::string& value, bool& matched_any) {\n  for (auto& rule : rules) {\n    std::smatch match;\n    if (std::regex_search(value, match, rule.rule)) {\n      matched_any = true;\n      return match.format(rule.repr.data());\n    }\n  }\n\n  return value;\n}\n\nstd::string& RegexCollection::get(std::string& value, bool& matched_any) {\n  if (regex_cache.contains(value)) {\n    return regex_cache[value];\n  }\n\n  // std::string repr =\n  // waybar::util::find_match(value, window_rewrite_rules_, matched_any);\n\n  std::string repr = find_match(value, matched_any);\n\n  if (!matched_any) {\n    repr = default_repr;\n  }\n\n  regex_cache.emplace(value, repr);\n\n  return regex_cache[value];  // Necessary in order to return a reference to the heap\n}\n\nstd::string& RegexCollection::get(std::string& value) {\n  bool matched_any = false;\n  return get(value, matched_any);\n}\n\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/rewrite_string.cpp",
    "content": "#include \"util/rewrite_string.hpp\"\n\n#include <spdlog/spdlog.h>\n\n#include <regex>\n\nnamespace waybar::util {\nstd::string rewriteString(const std::string& value, const Json::Value& rules) {\n  if (!rules.isObject()) {\n    return value;\n  }\n\n  std::string res = value;\n\n  for (auto it = rules.begin(); it != rules.end(); ++it) {\n    if (it.key().isString() && it->isString()) {\n      try {\n        // malformated regexes will cause an exception.\n        // in this case, log error and try the next rule.\n        const std::regex rule{it.key().asString(), std::regex_constants::icase};\n        if (std::regex_match(value, rule)) {\n          res = std::regex_replace(res, rule, it->asString());\n        }\n      } catch (const std::regex_error& e) {\n        spdlog::error(\"Invalid rule {}: {}\", it.key().asString(), e.what());\n      }\n    }\n  }\n\n  return res;\n}\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/rfkill.cpp",
    "content": "/* https://git.kernel.org/pub/scm/linux/kernel/git/jberg/rfkill.git/\n *\n * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>\n * Copyright 2009 Marcel Holtmann <marcel@holtmann.org>\n * Copyright 2009 Tim Gardner <tim.gardner@canonical.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */\n\n#include \"util/rfkill.hpp\"\n\n#include <fcntl.h>\n#include <glibmm/main.h>\n#include <linux/rfkill.h>\n#include <spdlog/spdlog.h>\n#include <unistd.h>\n\n#include <cerrno>\n\nwaybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type) : rfkill_type_(rfkill_type) {\n  fd_ = open(\"/dev/rfkill\", O_RDONLY);\n  if (fd_ < 0) {\n    spdlog::error(\"Can't open RFKILL control device\");\n    return;\n  }\n  int rc = fcntl(fd_, F_SETFL, O_NONBLOCK);\n  if (rc < 0) {\n    spdlog::error(\"Can't set RFKILL control device to non-blocking: {}\", errno);\n    close(fd_);\n    fd_ = -1;\n    return;\n  }\n  Glib::signal_io().connect(sigc::mem_fun(*this, &Rfkill::on_event), fd_,\n                            Glib::IO_IN | Glib::IO_ERR | Glib::IO_HUP);\n}\n\nwaybar::util::Rfkill::~Rfkill() {\n  if (fd_ >= 0) {\n    close(fd_);\n  }\n}\n\nbool waybar::util::Rfkill::on_event(Glib::IOCondition cond) {\n  if (cond & Glib::IO_IN) {\n    struct rfkill_event event;\n    ssize_t len;\n\n    len = read(fd_, &event, sizeof(event));\n    if (len < 0) {\n      if (errno == EAGAIN) {\n        return true;\n      }\n      spdlog::error(\"Reading of RFKILL events failed: {}\", errno);\n      return false;\n    }\n\n    if (static_cast<size_t>(len) < RFKILL_EVENT_SIZE_V1) {\n      spdlog::error(\"Wrong size of RFKILL event: {} < {}\", len, RFKILL_EVENT_SIZE_V1);\n      return true;\n    }\n\n    if (event.type == rfkill_type_ && (event.op == RFKILL_OP_ADD || event.op == RFKILL_OP_CHANGE)) {\n      state_ = event.soft || event.hard;\n      on_update.emit(event);\n    }\n    return true;\n  }\n  spdlog::error(\"Failed to poll RFKILL control device\");\n  return false;\n}\n\nbool waybar::util::Rfkill::getState() const { return state_; }\n"
  },
  {
    "path": "src/util/sanitize_str.cpp",
    "content": "#include <array>\n#include <string>\n#include <util/sanitize_str.hpp>\n#include <utility>\n\nnamespace waybar::util {\n// replaces ``<>&\"'`` with their encoded counterparts\nstd::string sanitize_string(std::string str) {\n  // note: it's important that '&' is replaced first; therefore we *can't* use std::map\n  const std::pair<char, std::string> replacement_table[] = {\n      {'&', \"&amp;\"}, {'<', \"&lt;\"}, {'>', \"&gt;\"}, {'\"', \"&quot;\"}, {'\\'', \"&apos;\"}};\n  size_t startpoint;\n  for (const auto& pair : replacement_table) {\n    startpoint = 0;\n    while ((startpoint = str.find(pair.first, startpoint)) != std::string::npos) {\n      str.replace(startpoint, 1, pair.second);\n      startpoint += pair.second.length();\n    }\n  }\n\n  return str;\n}\n}  // namespace waybar::util\n"
  },
  {
    "path": "src/util/ustring_clen.cpp",
    "content": "#include \"util/ustring_clen.hpp\"\n\nint ustring_clen(const Glib::ustring& str) {\n  int total = 0;\n  for (unsigned int i : str) {\n    total += g_unichar_iswide(i) + 1;\n  }\n  return total;\n}\n"
  },
  {
    "path": "test/config/hyprland-workspaces.json",
    "content": "[\n  {\n    \"height\": 20,\n    \"layer\": \"bottom\",\n    \"output\": [\n      \"HDMI-0\",\n      \"DP-0\"\n    ],\n    \"hyprland/workspaces\": {\n      \"active-only\": true,\n      \"all-outputs\": false,\n      \"show-special\": true,\n      \"move-to-monitor\": true,\n      \"format\": \"{icon} {windows}\",\n      \"format-window-separator\": \" \",\n      \"format-icons\": {\n        \"1\": \"󰎤\",\n        \"2\": \"󰎧\",\n        \"3\": \"󰎪\",\n        \"default\": \"\",\n        \"empty\": \"󱓼\",\n        \"urgent\": \"󱨇\"\n      },\n      \"persistent-workspaces\": {\n        \"1\": \"HDMI-0\"\n      },\n      \"on-scroll-down\": \"hyprctl dispatch workspace e-1\",\n      \"on-scroll-up\": \"hyprctl dispatch workspace e+1\",\n      \"window-rewrite\": {\n        \"title<Steam>\": \"\"\n      },\n      \"window-rewrite-default\": \"\",\n      \"window-rewrite-separator\": \" \",\n      \"sort-by\": \"number\"\n    }\n  }\n]\n"
  },
  {
    "path": "test/config/include-1.json",
    "content": "{\n  \"layer\": \"top\",\n  \"position\": \"bottom\",\n  \"height\": 30,\n  \"output\": [\"HDMI-0\", \"DP-0\"],\n  \"nullOption\": \"not null\"\n}\n"
  },
  {
    "path": "test/config/include-2.json",
    "content": "{\n  \"layer\": \"bottom\"\n}\n"
  },
  {
    "path": "test/config/include-multi-0.json",
    "content": "{\n  \"output\": \"OUT-0\",\n  \"height\": 20\n}\n"
  },
  {
    "path": "test/config/include-multi-1.json",
    "content": "{\n  \"height\": 21\n}\n"
  },
  {
    "path": "test/config/include-multi-2.json",
    "content": "{\n  \"output\": \"OUT-1\",\n  \"height\": 22\n}\n"
  },
  {
    "path": "test/config/include-multi-3-0.json",
    "content": "{\n  \"height\": 23\n}\n"
  },
  {
    "path": "test/config/include-multi-3.json",
    "content": "{\n  \"output\": \"OUT-3\",\n  \"include\": \"test/config/include-multi-3-0.json\"\n}\n"
  },
  {
    "path": "test/config/include-multi.json",
    "content": "[\n  {\n    \"include\": \"test/config/include-multi-0.json\"\n  },\n  {\n    \"output\": \"OUT-1\",\n    \"include\": \"test/config/include-multi-1.json\"\n  },\n  {\n    \"output\": \"OUT-2\",\n    \"include\": \"test/config/include-multi-2.json\"\n  },\n  {\n    \"include\": \"test/config/include-multi-3.json\"\n  }\n]\n"
  },
  {
    "path": "test/config/include-relative-path.json",
    "content": "{\n  \"include\": [\"modules/*.jsonc\"],\n  \"position\": \"top\",\n  \"nullOption\": null\n}\n"
  },
  {
    "path": "test/config/include-wildcard.json",
    "content": "{\n  \"include\": [\"test/config/modules/*.jsonc\"],\n  \"position\": \"top\",\n  \"nullOption\": null\n}\n"
  },
  {
    "path": "test/config/include.json",
    "content": "{\n  \"include\": [\"test/config/include-1.json\", \"test/config/include-2.json\"],\n  \"position\": \"top\",\n  \"nullOption\": null\n}\n"
  },
  {
    "path": "test/config/modules/cpu.jsonc",
    "content": "{\n    \"cpu\": {\n        \"interval\": 2,\n        \"format\": \"goo\"\n    }\n}\n"
  },
  {
    "path": "test/config/modules/memory.jsonc",
    "content": "{\n    \"memory\": {\n        \"interval\": 2,\n        \"format\": \"foo\",\n    }\n}\n"
  },
  {
    "path": "test/config/multi.json",
    "content": "[\n  {\n    \"layer\": \"bottom\",\n    \"height\": 20,\n    \"output\": [\"HDMI-0\", \"DP-0\"]\n  },\n  {\n    \"position\": \"bottom\",\n    \"layer\": \"top\",\n    \"height\": 21,\n    \"output\": [\"DP-0\"]\n  },\n  {\n    \"position\": \"left\",\n    \"layer\": \"overlay\",\n    \"height\": 22,\n    \"output\": \"Fake HDMI output #1\"\n  },\n  {\n    \"position\": \"right\",\n    \"layer\": \"overlay\",\n    \"height\": 23,\n    \"output\": \"!HDMI-1\"\n  },\n  {\n    \"height\": 24,\n    \"output\": [\"!HDMI-0\", \"!HDMI-1\", \"*\"]\n  }\n]\n"
  },
  {
    "path": "test/config/simple.json",
    "content": "{\n  \"layer\": \"top\",\n  \"height\": 30,\n  \"output\": [\"HDMI-0\", \"DP-0\"]\n}\n"
  },
  {
    "path": "test/config.cpp",
    "content": "#include \"config.hpp\"\n\n#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\nTEST_CASE(\"Load simple config\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/simple.json\");\n\n  SECTION(\"validate the config data\") {\n    auto& data = conf.getConfig();\n    REQUIRE(data[\"layer\"].asString() == \"top\");\n    REQUIRE(data[\"height\"].asInt() == 30);\n  }\n  SECTION(\"select configs for configured output\") {\n    auto configs = conf.getOutputConfigs(\"HDMI-0\", \"Fake HDMI output #0\");\n    REQUIRE(configs.size() == 1);\n  }\n  SECTION(\"select configs for missing output\") {\n    auto configs = conf.getOutputConfigs(\"HDMI-1\", \"Fake HDMI output #1\");\n    REQUIRE(configs.empty());\n  }\n}\n\nTEST_CASE(\"Load config with multiple bars\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/multi.json\");\n\n  SECTION(\"select multiple configs #1\") {\n    auto data = conf.getOutputConfigs(\"DP-0\", \"Fake DisplayPort output #0\");\n    REQUIRE(data.size() == 4);\n    REQUIRE(data[0][\"layer\"].asString() == \"bottom\");\n    REQUIRE(data[0][\"height\"].asInt() == 20);\n    REQUIRE(data[1][\"layer\"].asString() == \"top\");\n    REQUIRE(data[1][\"position\"].asString() == \"bottom\");\n    REQUIRE(data[1][\"height\"].asInt() == 21);\n    REQUIRE(data[2][\"layer\"].asString() == \"overlay\");\n    REQUIRE(data[2][\"position\"].asString() == \"right\");\n    REQUIRE(data[2][\"height\"].asInt() == 23);\n    REQUIRE(data[3][\"height\"].asInt() == 24);\n  }\n  SECTION(\"select multiple configs #2\") {\n    auto data = conf.getOutputConfigs(\"HDMI-0\", \"Fake HDMI output #0\");\n    REQUIRE(data.size() == 2);\n    REQUIRE(data[0][\"layer\"].asString() == \"bottom\");\n    REQUIRE(data[0][\"height\"].asInt() == 20);\n    REQUIRE(data[1][\"layer\"].asString() == \"overlay\");\n    REQUIRE(data[1][\"position\"].asString() == \"right\");\n    REQUIRE(data[1][\"height\"].asInt() == 23);\n  }\n  SECTION(\"select single config by output description\") {\n    auto data = conf.getOutputConfigs(\"HDMI-1\", \"Fake HDMI output #1\");\n    REQUIRE(data.size() == 1);\n    REQUIRE(data[0][\"layer\"].asString() == \"overlay\");\n    REQUIRE(data[0][\"position\"].asString() == \"left\");\n    REQUIRE(data[0][\"height\"].asInt() == 22);\n  }\n}\n\nTEST_CASE(\"Load simple config with include\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/include.json\");\n\n  SECTION(\"validate the config data\") {\n    auto& data = conf.getConfig();\n    // config override behavior: preserve first included value\n    REQUIRE(data[\"layer\"].asString() == \"top\");\n    REQUIRE(data[\"height\"].asInt() == 30);\n    // config override behavior: preserve value from the top config\n    REQUIRE(data[\"position\"].asString() == \"top\");\n    // config override behavior: explicit null is still a value and should be preserved\n    REQUIRE((data.isMember(\"nullOption\") && data[\"nullOption\"].isNull()));\n  }\n  SECTION(\"select configs for configured output\") {\n    auto configs = conf.getOutputConfigs(\"HDMI-0\", \"Fake HDMI output #0\");\n    REQUIRE(configs.size() == 1);\n  }\n  SECTION(\"select configs for missing output\") {\n    auto configs = conf.getOutputConfigs(\"HDMI-1\", \"Fake HDMI output #1\");\n    REQUIRE(configs.empty());\n  }\n}\n\nTEST_CASE(\"Load simple config with wildcard include\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/include-wildcard.json\");\n\n  auto& data = conf.getConfig();\n  SECTION(\"validate cpu include file\") { REQUIRE(data[\"cpu\"][\"format\"].asString() == \"goo\"); }\n  SECTION(\"validate memory include file\") { REQUIRE(data[\"memory\"][\"format\"].asString() == \"foo\"); }\n}\n\nTEST_CASE(\"Load config using relative paths and wildcards\", \"[config]\") {\n  waybar::Config conf;\n\n  const char* old_config_path = std::getenv(waybar::Config::CONFIG_PATH_ENV);\n  setenv(waybar::Config::CONFIG_PATH_ENV, \"test/config\", 1);\n\n  conf.load(\"test/config/include-relative-path.json\");\n\n  auto& data = conf.getConfig();\n  SECTION(\"validate cpu include file\") { REQUIRE(data[\"cpu\"][\"format\"].asString() == \"goo\"); }\n  SECTION(\"validate memory include file\") { REQUIRE(data[\"memory\"][\"format\"].asString() == \"foo\"); }\n\n  if (old_config_path)\n    setenv(waybar::Config::CONFIG_PATH_ENV, old_config_path, 1);\n  else\n    unsetenv(waybar::Config::CONFIG_PATH_ENV);\n}\n\nTEST_CASE(\"Load multiple bar config with include\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/include-multi.json\");\n\n  SECTION(\"bar config with sole include\") {\n    auto data = conf.getOutputConfigs(\"OUT-0\", \"Fake output #0\");\n    REQUIRE(data.size() == 1);\n    REQUIRE(data[0][\"height\"].asInt() == 20);\n  }\n\n  SECTION(\"bar config with output and include\") {\n    auto data = conf.getOutputConfigs(\"OUT-1\", \"Fake output #1\");\n    REQUIRE(data.size() == 1);\n    REQUIRE(data[0][\"height\"].asInt() == 21);\n  }\n\n  SECTION(\"bar config with output override\") {\n    auto data = conf.getOutputConfigs(\"OUT-2\", \"Fake output #2\");\n    REQUIRE(data.size() == 1);\n    REQUIRE(data[0][\"height\"].asInt() == 22);\n  }\n\n  SECTION(\"multiple levels of include\") {\n    auto data = conf.getOutputConfigs(\"OUT-3\", \"Fake output #3\");\n    REQUIRE(data.size() == 1);\n    REQUIRE(data[0][\"height\"].asInt() == 23);\n  }\n\n  auto& data = conf.getConfig();\n  REQUIRE(data.isArray());\n  REQUIRE(data.size() == 4);\n  REQUIRE(data[0][\"output\"].asString() == \"OUT-0\");\n}\n\nTEST_CASE(\"Load Hyprland Workspaces bar config\", \"[config]\") {\n  waybar::Config conf;\n  conf.load(\"test/config/hyprland-workspaces.json\");\n\n  auto& data = conf.getConfig();\n  auto hyprland = data[0][\"hyprland/workspaces\"];\n  auto hyprland_window_rewrite = data[0][\"hyprland/workspaces\"][\"window-rewrite\"];\n  auto hyprland_format_icons = data[0][\"hyprland/workspaces\"][\"format-icons\"];\n  auto hyprland_persistent_workspaces = data[0][\"hyprland/workspaces\"][\"persistent-workspaces\"];\n\n  REQUIRE(data.isArray());\n  REQUIRE(data.size() == 1);\n  REQUIRE(data[0][\"height\"].asInt() == 20);\n  REQUIRE(data[0][\"layer\"].asString() == \"bottom\");\n  REQUIRE(data[0][\"output\"].isArray());\n  REQUIRE(data[0][\"output\"][0].asString() == \"HDMI-0\");\n  REQUIRE(data[0][\"output\"][1].asString() == \"DP-0\");\n\n  REQUIRE(hyprland[\"active-only\"].asBool() == true);\n  REQUIRE(hyprland[\"all-outputs\"].asBool() == false);\n  REQUIRE(hyprland[\"move-to-monitor\"].asBool() == true);\n  REQUIRE(hyprland[\"format\"].asString() == \"{icon} {windows}\");\n  REQUIRE(hyprland[\"format-window-separator\"].asString() == \" \");\n  REQUIRE(hyprland[\"on-scroll-down\"].asString() == \"hyprctl dispatch workspace e-1\");\n  REQUIRE(hyprland[\"on-scroll-up\"].asString() == \"hyprctl dispatch workspace e+1\");\n  REQUIRE(hyprland[\"show-special\"].asBool() == true);\n  REQUIRE(hyprland[\"window-rewrite-default\"].asString() == \"\");\n  REQUIRE(hyprland[\"window-rewrite-separator\"].asString() == \" \");\n  REQUIRE(hyprland_format_icons[\"1\"].asString() == \"󰎤\");\n  REQUIRE(hyprland_format_icons[\"2\"].asString() == \"󰎧\");\n  REQUIRE(hyprland_format_icons[\"3\"].asString() == \"󰎪\");\n  REQUIRE(hyprland_format_icons[\"default\"].asString() == \"\");\n  REQUIRE(hyprland_format_icons[\"empty\"].asString() == \"󱓼\");\n  REQUIRE(hyprland_format_icons[\"urgent\"].asString() == \"󱨇\");\n  REQUIRE(hyprland_persistent_workspaces[\"1\"].asString() == \"HDMI-0\");\n  REQUIRE(hyprland_window_rewrite[\"title<Steam>\"].asString() == \"\");\n  REQUIRE(hyprland[\"sort-by\"].asString() == \"number\");\n}\n"
  },
  {
    "path": "test/hyprland/backend.cpp",
    "content": "#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\n#include <system_error>\n\n#include \"modules/hyprland/backend.hpp\"\n\nnamespace fs = std::filesystem;\nnamespace hyprland = waybar::modules::hyprland;\n\nnamespace {\nclass IPCTestHelper : public hyprland::IPC {\n public:\n  static void resetSocketFolder() { socketFolder_.clear(); }\n};\n\nstd::size_t countOpenFds() {\n#if defined(__linux__)\n  std::size_t count = 0;\n  for (const auto& _ : fs::directory_iterator(\"/proc/self/fd\")) {\n    (void)_;\n    ++count;\n  }\n  return count;\n#else\n  return 0;\n#endif\n}\n}  // namespace\n\nTEST_CASE(\"XDGRuntimeDirExists\", \"[getSocketFolder]\") {\n  // Test case: XDG_RUNTIME_DIR exists and contains \"hypr\" directory\n  // Arrange\n  constexpr auto instanceSig = \"instance_sig\";\n  const fs::path tempDir = fs::temp_directory_path() / \"hypr_test/run/user/1000\";\n  std::error_code ec;\n  fs::remove_all(tempDir, ec);\n  fs::path expectedPath = tempDir / \"hypr\" / instanceSig;\n  fs::create_directories(expectedPath);\n  setenv(\"XDG_RUNTIME_DIR\", tempDir.c_str(), 1);\n  IPCTestHelper::resetSocketFolder();\n\n  // Act\n  fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);\n\n  // Assert expected result\n  REQUIRE(actualPath == expectedPath);\n  fs::remove_all(tempDir, ec);\n}\n\nTEST_CASE(\"XDGRuntimeDirDoesNotExist\", \"[getSocketFolder]\") {\n  // Test case: XDG_RUNTIME_DIR does not exist\n  // Arrange\n  constexpr auto instanceSig = \"instance_sig\";\n  unsetenv(\"XDG_RUNTIME_DIR\");\n  fs::path expectedPath = fs::path(\"/tmp\") / \"hypr\" / instanceSig;\n  IPCTestHelper::resetSocketFolder();\n\n  // Act\n  fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);\n\n  // Assert expected result\n  REQUIRE(actualPath == expectedPath);\n}\n\nTEST_CASE(\"XDGRuntimeDirExistsNoHyprDir\", \"[getSocketFolder]\") {\n  // Test case: XDG_RUNTIME_DIR exists but does not contain \"hypr\" directory\n  // Arrange\n  constexpr auto instanceSig = \"instance_sig\";\n  fs::path tempDir = fs::temp_directory_path() / \"hypr_test/run/user/1000\";\n  std::error_code ec;\n  fs::remove_all(tempDir, ec);\n  fs::create_directories(tempDir);\n  setenv(\"XDG_RUNTIME_DIR\", tempDir.c_str(), 1);\n  fs::path expectedPath = fs::path(\"/tmp\") / \"hypr\" / instanceSig;\n  IPCTestHelper::resetSocketFolder();\n\n  // Act\n  fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig);\n\n  // Assert expected result\n  REQUIRE(actualPath == expectedPath);\n  fs::remove_all(tempDir, ec);\n}\n\nTEST_CASE(\"Socket folder is resolved per instance signature\", \"[getSocketFolder]\") {\n  const fs::path tempDir = fs::temp_directory_path() / \"hypr_test/run/user/1000\";\n  std::error_code ec;\n  fs::remove_all(tempDir, ec);\n  fs::create_directories(tempDir / \"hypr\");\n  setenv(\"XDG_RUNTIME_DIR\", tempDir.c_str(), 1);\n  IPCTestHelper::resetSocketFolder();\n\n  const auto firstPath = hyprland::IPC::getSocketFolder(\"instance_a\");\n  const auto secondPath = hyprland::IPC::getSocketFolder(\"instance_b\");\n\n  REQUIRE(firstPath == tempDir / \"hypr\" / \"instance_a\");\n  REQUIRE(secondPath == tempDir / \"hypr\" / \"instance_b\");\n  REQUIRE(firstPath != secondPath);\n\n  fs::remove_all(tempDir, ec);\n}\n\nTEST_CASE(\"getSocket1Reply throws on no socket\", \"[getSocket1Reply]\") {\n  unsetenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n  IPCTestHelper::resetSocketFolder();\n  std::string request = \"test_request\";\n\n  CHECK_THROWS(hyprland::IPC::getSocket1Reply(request));\n}\n\n#if defined(__linux__)\nTEST_CASE(\"getSocket1Reply failure paths do not leak fds\", \"[getSocket1Reply][fd-leak]\") {\n  const auto baseline = countOpenFds();\n\n  unsetenv(\"HYPRLAND_INSTANCE_SIGNATURE\");\n  for (int i = 0; i < 16; ++i) {\n    IPCTestHelper::resetSocketFolder();\n    CHECK_THROWS(hyprland::IPC::getSocket1Reply(\"test_request\"));\n  }\n  const auto after_missing_signature = countOpenFds();\n  REQUIRE(after_missing_signature == baseline);\n\n  setenv(\"HYPRLAND_INSTANCE_SIGNATURE\", \"definitely-not-running\", 1);\n  for (int i = 0; i < 16; ++i) {\n    IPCTestHelper::resetSocketFolder();\n    CHECK_THROWS(hyprland::IPC::getSocket1Reply(\"test_request\"));\n  }\n  const auto after_connect_failures = countOpenFds();\n  REQUIRE(after_connect_failures == baseline);\n}\n#endif\n"
  },
  {
    "path": "test/hyprland/meson.build",
    "content": "test_inc = include_directories('../../include')\n\ntest_dep = [\n    catch2,\n    fmt,\n    gtkmm,\n    jsoncpp,\n    spdlog,\n]\n\ntest_src = files(\n    '../main.cpp',\n    'backend.cpp',\n    '../../src/modules/hyprland/backend.cpp'\n)\n\nhyprland_test = executable(\n    'hyprland_test',\n    test_src,\n    dependencies: test_dep,\n    include_directories: test_inc,\n)\n\ntest(\n    'hyprland',\n    hyprland_test,\n    workdir: meson.project_source_root(),\n)\n"
  },
  {
    "path": "test/main.cpp",
    "content": "#define CATCH_CONFIG_RUNNER\n#include <glibmm.h>\n#include <spdlog/sinks/stdout_sinks.h>\n#include <spdlog/spdlog.h>\n\n#if __has_include(<catch2/catch_session.hpp>)\n#include <catch2/catch_session.hpp>\n#include <catch2/catch_version_macros.hpp>\n#include <catch2/reporters/catch_reporter_tap.hpp>\n#else\n#include <catch2/catch.hpp>\n#include <catch2/catch_reporter_tap.hpp>\n#endif\n#include <memory>\n\nint main(int argc, char* argv[]) {\n  Catch::Session session;\n  Glib::init();\n\n  session.applyCommandLine(argc, argv);\n  const auto logger = spdlog::default_logger();\n#if CATCH_VERSION_MAJOR >= 3\n  for (const auto& spec : session.config().getReporterSpecs()) {\n    const auto& reporter_name = spec.name();\n#else\n  {\n    const auto& reporter_name = session.config().getReporterName();\n#endif\n    if (reporter_name == \"tap\") {\n      spdlog::set_pattern(\"# [%l] %v\");\n    } else if (reporter_name == \"compact\") {\n      logger->sinks().clear();\n    } else {\n      logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});\n    }\n  }\n\n  return session.run();\n}\n"
  },
  {
    "path": "test/meson.build",
    "content": "test_inc = include_directories('../include')\n\ntest_dep = [\n    catch2,\n    fmt,\n    gtkmm,\n    jsoncpp,\n    spdlog,\n]\n\ntest_src = files(\n    'main.cpp',\n    'config.cpp',\n    '../src/config.cpp',\n)\n\nwaybar_test = executable(\n    'waybar_test',\n    test_src,\n    dependencies: test_dep,\n    include_directories: test_inc,\n)\n\ntest(\n    'waybar',\n    waybar_test,\n    workdir: meson.project_source_root(),\n)\n\nsubdir('utils')\nsubdir('hyprland')\n"
  },
  {
    "path": "test/utils/JsonParser.cpp",
    "content": "#include \"util/json.hpp\"\n\n#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\nTEST_CASE(\"Simple json\", \"[json]\") {\n  SECTION(\"Parse simple json\") {\n    std::string stringToTest = R\"({\"number\": 5, \"string\": \"test\"})\";\n    waybar::util::JsonParser parser;\n    Json::Value jsonValue = parser.parse(stringToTest);\n    REQUIRE(jsonValue[\"number\"].asInt() == 5);\n    REQUIRE(jsonValue[\"string\"].asString() == \"test\");\n  }\n}\n\nTEST_CASE(\"Json with unicode\", \"[json]\") {\n  SECTION(\"Parse json with unicode\") {\n    std::string stringToTest = R\"({\"test\": \"\\xab\"})\";\n    waybar::util::JsonParser parser;\n    Json::Value jsonValue = parser.parse(stringToTest);\n    // compare with \"\\u00ab\" because \"\\xab\" is replaced with \"\\u00ab\" in the parser\n    REQUIRE(jsonValue[\"test\"].asString() == \"\\u00ab\");\n  }\n}\n\nTEST_CASE(\"Json with emoji\", \"[json]\") {\n  SECTION(\"Parse json with emoji\") {\n    std::string stringToTest = R\"({\"test\": \"😊\"})\";\n    waybar::util::JsonParser parser;\n    Json::Value jsonValue = parser.parse(stringToTest);\n    REQUIRE(jsonValue[\"test\"].asString() == \"😊\");\n  }\n}\n\nTEST_CASE(\"Json with chinese characters\", \"[json]\") {\n  SECTION(\"Parse json with chinese characters\") {\n    std::string stringToTest = R\"({\"test\": \"你好\"})\";\n    waybar::util::JsonParser parser;\n    Json::Value jsonValue = parser.parse(stringToTest);\n    REQUIRE(jsonValue[\"test\"].asString() == \"你好\");\n  }\n}"
  },
  {
    "path": "test/utils/SafeSignal.cpp",
    "content": "#include \"util/SafeSignal.hpp\"\n\n#include <glibmm.h>\n\n#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n#include <thread>\n#include <type_traits>\n#include <vector>\n\n#include \"fixtures/GlibTestsFixture.hpp\"\n\nusing namespace waybar;\n\ntemplate <typename T>\nusing remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;\n\n/**\n * Basic sanity test for SafeSignal:\n * check that type deduction works, events are delivered and the order is right\n * Running this with -fsanitize=thread should not fail\n */\nTEST_CASE_METHOD(GlibTestsFixture, \"SafeSignal basic functionality\", \"[signal][thread][util]\") {\n  const int NUM_EVENTS = 100;\n  int count = 0;\n  int last_value = 0;\n\n  SafeSignal<int, std::string> test_signal;\n\n  const auto main_tid = std::this_thread::get_id();\n  std::thread producer;\n\n  // timeout the test in 500ms\n  setTimeout(500);\n\n  test_signal.connect([&](auto val, auto str) {\n    static_assert(std::is_same<int, decltype(val)>::value);\n    static_assert(std::is_same<std::string, decltype(str)>::value);\n    // check that we're in the same thread as the main loop\n    REQUIRE(std::this_thread::get_id() == main_tid);\n    // check event order\n    REQUIRE(val == last_value + 1);\n\n    last_value = val;\n    if (++count >= NUM_EVENTS) {\n      this->quit();\n    };\n  });\n\n  run([&]() {\n    // check that events from the same thread are delivered and processed synchronously\n    test_signal.emit(1, \"test\");\n    REQUIRE(count == 1);\n\n    // start another thread and generate events\n    producer = std::thread([&]() {\n      for (auto i = 2; i <= NUM_EVENTS; ++i) {\n        test_signal.emit(i, \"test\");\n      }\n    });\n  });\n  producer.join();\n  REQUIRE(count == NUM_EVENTS);\n}\n\ntemplate <typename T>\nstruct TestObject {\n  T value;\n  unsigned copied = 0;\n  unsigned moved = 0;\n\n  TestObject(const T& v) : value(v) {};\n  ~TestObject() = default;\n\n  TestObject(const TestObject& other)\n      : value(other.value), copied(other.copied + 1), moved(other.moved) {}\n\n  TestObject(TestObject&& other) noexcept\n      : value(std::move(other.value)),\n        copied(std::exchange(other.copied, 0)),\n        moved(std::exchange(other.moved, 0) + 1) {}\n\n  TestObject& operator=(const TestObject& other) {\n    value = other.value;\n    copied = other.copied + 1;\n    moved = other.moved;\n    return *this;\n  }\n\n  TestObject& operator=(TestObject&& other) noexcept {\n    value = std::move(other.value);\n    copied = std::exchange(other.copied, 0);\n    moved = std::exchange(other.moved, 0) + 1;\n    return *this;\n  }\n\n  bool operator==(T other) const { return value == other; }\n  operator T() const { return value; }\n};\n\n/*\n * Check the number of copies/moves performed on the object passed through SafeSignal\n */\nTEST_CASE_METHOD(GlibTestsFixture, \"SafeSignal copy/move counter\", \"[signal][thread][util]\") {\n  const int NUM_EVENTS = 3;\n  int count = 0;\n\n  SafeSignal<TestObject<int>> test_signal;\n\n  std::thread producer;\n\n  // timeout the test in 500ms\n  setTimeout(500);\n\n  test_signal.connect([&](auto& val) {\n    static_assert(std::is_same<TestObject<int>, remove_cvref_t<decltype(val)>>::value);\n\n    /* explicit move in the producer thread */\n    REQUIRE(val.moved <= 1);\n    /* copy within the SafeSignal queuing code */\n    REQUIRE(val.copied <= 1);\n\n    if (++count >= NUM_EVENTS) {\n      this->quit();\n    };\n  });\n\n  run([&]() {\n    test_signal.emit(1);\n    REQUIRE(count == 1);\n    producer = std::thread([&]() {\n      for (auto i = 2; i <= NUM_EVENTS; ++i) {\n        TestObject<int> t{i};\n        // check that signal.emit accepts moved objects\n        test_signal.emit(std::move(t));\n      }\n    });\n  });\n  producer.join();\n  REQUIRE(count == NUM_EVENTS);\n}\n\nTEST_CASE_METHOD(GlibTestsFixture, \"SafeSignal queue stays bounded under burst load\",\n                 \"[signal][thread][util][perf]\") {\n  constexpr int NUM_EVENTS = 200;\n  constexpr std::size_t MAX_QUEUED_EVENTS = 8;\n  std::vector<int> received;\n\n  SafeSignal<int> test_signal;\n  test_signal.set_max_queued_events(MAX_QUEUED_EVENTS);\n\n  setTimeout(500);\n\n  test_signal.connect([&](auto value) { received.push_back(value); });\n\n  run([&]() {\n    std::thread producer([&]() {\n      for (int i = 1; i <= NUM_EVENTS; ++i) {\n        test_signal.emit(i);\n      }\n    });\n    producer.join();\n\n    Glib::signal_timeout().connect_once([this]() { this->quit(); }, 50);\n  });\n\n  REQUIRE(received.size() <= MAX_QUEUED_EVENTS);\n  REQUIRE_FALSE(received.empty());\n  REQUIRE(received.back() == NUM_EVENTS);\n  REQUIRE(received.front() == NUM_EVENTS - static_cast<int>(received.size()) + 1);\n}\n"
  },
  {
    "path": "test/utils/command.cpp",
    "content": "#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\n#include <sys/wait.h>\n#include <unistd.h>\n\n#include <cerrno>\n#include <list>\n#include <mutex>\n\nstd::mutex reap_mtx;\nstd::list<pid_t> reap;\n\nextern \"C\" int waybar_test_execl(const char* path, const char* arg, ...);\nextern \"C\" int waybar_test_execlp(const char* file, const char* arg, ...);\n\n#define execl waybar_test_execl\n#define execlp waybar_test_execlp\n#include \"util/command.hpp\"\n#undef execl\n#undef execlp\n\nextern \"C\" int waybar_test_execl(const char* path, const char* arg, ...) {\n  (void)path;\n  (void)arg;\n  errno = ENOENT;\n  return -1;\n}\n\nextern \"C\" int waybar_test_execlp(const char* file, const char* arg, ...) {\n  (void)file;\n  (void)arg;\n  errno = ENOENT;\n  return -1;\n}\n\nTEST_CASE(\"command::execNoRead returns 127 when shell exec fails\", \"[util][command]\") {\n  const auto result = waybar::util::command::execNoRead(\"echo should-not-run\");\n  REQUIRE(result.exit_code == waybar::util::command::kExecFailureExitCode);\n  REQUIRE(result.out.empty());\n}\n\nTEST_CASE(\"command::forkExec child exits 127 when shell exec fails\", \"[util][command]\") {\n  const auto pid = waybar::util::command::forkExec(\"echo should-not-run\", \"test-output\");\n  REQUIRE(pid > 0);\n\n  int status = -1;\n  REQUIRE(waitpid(pid, &status, 0) == pid);\n  REQUIRE(WIFEXITED(status));\n  REQUIRE(WEXITSTATUS(status) == waybar::util::command::kExecFailureExitCode);\n\n  std::scoped_lock<std::mutex> lock(reap_mtx);\n  reap.remove(pid);\n}\n"
  },
  {
    "path": "test/utils/css_reload_helper.cpp",
    "content": "#include \"util/css_reload_helper.hpp\"\n\n#include <map>\n\n#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\nclass CssReloadHelperTest : public waybar::CssReloadHelper {\n public:\n  CssReloadHelperTest() : CssReloadHelper(\"/tmp/waybar_test.css\", [this]() { callback(); }) {}\n\n  void callback() { m_callbackCounter++; }\n\n protected:\n  std::string getFileContents(const std::string& filename) override {\n    return m_fileContents[filename];\n  }\n\n  std::string findPath(const std::string& filename) override { return filename; }\n\n  void setFileContents(const std::string& filename, const std::string& contents) {\n    m_fileContents[filename] = contents;\n  }\n\n  int getCallbackCounter() const { return m_callbackCounter; }\n\n private:\n  int m_callbackCounter{};\n  std::map<std::string, std::string> m_fileContents;\n};\n\nTEST_CASE_METHOD(CssReloadHelperTest, \"parse_imports\", \"[util][css_reload_helper]\") {\n  SECTION(\"no imports\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"body { color: red; }\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    REQUIRE(files.size() == 1);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n  }\n\n  SECTION(\"single import\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"@import 'test.css';\");\n    setFileContents(\"test.css\", \"body { color: red; }\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    std::sort(files.begin(), files.end());\n    REQUIRE(files.size() == 2);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n    CHECK(files[1] == \"test.css\");\n  }\n\n  SECTION(\"multiple imports\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"@import 'test.css'; @import 'test2.css';\");\n    setFileContents(\"test.css\", \"body { color: red; }\");\n    setFileContents(\"test2.css\", \"body { color: blue; }\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    std::sort(files.begin(), files.end());\n    REQUIRE(files.size() == 3);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n    CHECK(files[1] == \"test.css\");\n    CHECK(files[2] == \"test2.css\");\n  }\n\n  SECTION(\"nested imports\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"@import 'test.css';\");\n    setFileContents(\"test.css\", \"@import 'test2.css';\");\n    setFileContents(\"test2.css\", \"body { color: red; }\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    std::sort(files.begin(), files.end());\n    REQUIRE(files.size() == 3);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n    CHECK(files[1] == \"test.css\");\n    CHECK(files[2] == \"test2.css\");\n  }\n\n  SECTION(\"circular imports\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"@import 'test.css';\");\n    setFileContents(\"test.css\", \"@import 'test2.css';\");\n    setFileContents(\"test2.css\", \"@import 'test.css';\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    std::sort(files.begin(), files.end());\n    REQUIRE(files.size() == 3);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n    CHECK(files[1] == \"test.css\");\n    CHECK(files[2] == \"test2.css\");\n  }\n\n  SECTION(\"empty\") {\n    setFileContents(\"/tmp/waybar_test.css\", \"\");\n    auto files = parseImports(\"/tmp/waybar_test.css\");\n    REQUIRE(files.size() == 1);\n    CHECK(files[0] == \"/tmp/waybar_test.css\");\n  }\n\n  SECTION(\"empty name\") {\n    auto files = parseImports(\"\");\n    REQUIRE(files.empty());\n  }\n}\n"
  },
  {
    "path": "test/utils/date.cpp",
    "content": "#include \"util/date.hpp\"\n\n#include <ctime>\n#include <iomanip>\n#include <sstream>\n#include <stdexcept>\n\n#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#include <catch2/matchers/catch_matchers_string.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\n#ifndef SKIP\n#define SKIP(...)    \\\n  WARN(__VA_ARGS__); \\\n  return\n#endif\n\nusing namespace date;\nusing namespace std::literals::chrono_literals;\nnamespace fmt_lib = waybar::util::date::format;\n\n/*\n * Check that the date/time formatter with locale and timezone support is working as expected.\n */\n\nconst zoned_time<std::chrono::seconds> TEST_TIME{\n    \"UTC\", local_days{Monday[1] / January / 2022} + 13h + 4min + 5s};\n\n/*\n * Check if the date formatted with LC_TIME=en_US is within expectations.\n *\n * The check expects Glibc output style and will fail with FreeBSD (different implementation)\n * or musl (no implementation).\n */\nstatic const bool LC_TIME_is_sane = []() {\n  try {\n    std::stringstream ss;\n    ss.imbue(std::locale(\"en_US.UTF-8\"));\n\n    time_t t = 1641211200;\n    std::tm tm = *std::gmtime(&t);\n\n    ss << std::put_time(&tm, \"%x %X\");\n    return ss.str() == \"01/03/2022 12:00:00 PM\";\n  } catch (std::exception&) {\n    return false;\n  }\n}();\n\nTEST_CASE(\"Format UTC time\", \"[clock][util]\") {\n  const auto loc = std::locale(\"C\");\n  const auto tm = TEST_TIME;\n#if not HAVE_CHRONO_TIMEZONES\n  CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n#endif\n  CHECK(fmt_lib::format(loc, \"{:%c %Z}\", tm) == \"Mon Jan  3 13:04:05 2022 UTC\");\n  CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103130405\");\n\n  if (!LC_TIME_is_sane) {\n    SKIP(\"Locale support check failed, skip tests\");\n  }\n\n  /* Test a few locales that are most likely to be present */\n  SECTION(\"US locale\") {\n    try {\n      const auto loc = std::locale(\"en_US.UTF-8\");\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(loc, \"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 01:04:05 PM\"));\n      CHECK(fmt_lib::format(loc, \"{:%x %X}\", tm) == \"01/03/2022 01:04:05 PM\");\n#else\n      CHECK(fmt_lib::format(loc, \"{:%F %r}\", tm) == \"2022-01-03 01:04:05 PM\");\n#endif\n      CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103130405\");\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_US not found, skip tests\");\n    }\n  }\n  SECTION(\"GB locale\") {\n    try {\n      const auto loc = std::locale(\"en_GB.UTF-8\");\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(loc, \"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 13:04:05\"));\n      CHECK(fmt_lib::format(loc, \"{:%x %X}\", tm) == \"03/01/22 13:04:05\");\n#else\n      CHECK(fmt_lib::format(loc, \"{:%F %T}\", tm) == \"2022-01-03 13:04:05\");\n#endif\n      CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103130405\");\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_GB not found, skip tests\");\n    }\n  }\n  SECTION(\"Global locale\") {\n    try {\n      const auto loc = std::locale::global(std::locale(\"en_US.UTF-8\"));\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(\"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(\"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 01:04:05 PM\"));\n      CHECK(fmt_lib::format(\"{:%x %X}\", tm) == \"01/03/2022 01:04:05 PM\");\n#else\n      CHECK(fmt_lib::format(\"{:%F %r}\", tm) == \"2022-01-03 01:04:05 PM\");\n#endif\n      CHECK(fmt_lib::format(\"{:%Y%m%d%H%M%S}\", tm) == \"20220103130405\");\n\n      std::locale::global(loc);\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_US not found, skip tests\");\n    }\n  }\n}\n\nTEST_CASE(\"Format zoned time\", \"[clock][util]\") {\n  const auto loc = std::locale(\"C\");\n  const auto tm = zoned_time{\"America/New_York\", TEST_TIME};\n\n#if not HAVE_CHRONO_TIMEZONES\n  CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n#endif\n  CHECK(fmt_lib::format(loc, \"{:%c %Z}\", tm) == \"Mon Jan  3 08:04:05 2022 EST\");\n  CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103080405\");\n\n  if (!LC_TIME_is_sane) {\n    SKIP(\"Locale support check failed, skip tests\");\n  }\n\n  /* Test a few locales that are most likely to be present */\n  SECTION(\"US locale\") {\n    try {\n      const auto loc = std::locale(\"en_US.UTF-8\");\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(loc, \"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 08:04:05 AM\"));\n      CHECK(fmt_lib::format(loc, \"{:%x %X}\", tm) == \"01/03/2022 08:04:05 AM\");\n#else\n      CHECK(fmt_lib::format(loc, \"{:%F %r}\", tm) == \"2022-01-03 08:04:05 AM\");\n#endif\n      CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103080405\");\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_US not found, skip tests\");\n    }\n  }\n  SECTION(\"GB locale\") {\n    try {\n      const auto loc = std::locale(\"en_GB.UTF-8\");\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(loc, \"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(loc, \"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 08:04:05\"));\n      CHECK(fmt_lib::format(loc, \"{:%x %X}\", tm) == \"03/01/22 08:04:05\");\n#else\n      CHECK(fmt_lib::format(loc, \"{:%F %T}\", tm) == \"2022-01-03 08:04:05\");\n#endif\n      CHECK(fmt_lib::format(loc, \"{:%Y%m%d%H%M%S}\", tm) == \"20220103080405\");\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_GB not found, skip tests\");\n    }\n  }\n  SECTION(\"Global locale\") {\n    try {\n      const auto loc = std::locale::global(std::locale(\"en_US.UTF-8\"));\n\n#if not HAVE_CHRONO_TIMEZONES\n      CHECK(fmt_lib::format(\"{}\", tm).empty());  // no format specified\n      CHECK_THAT(fmt_lib::format(\"{:%c}\", tm),   // HowardHinnant/date#704\n                 Catch::Matchers::StartsWith(\"Mon 03 Jan 2022 08:04:05 AM\"));\n      CHECK(fmt_lib::format(\"{:%x %X}\", tm) == \"01/03/2022 08:04:05 AM\");\n#else\n      CHECK(fmt_lib::format(\"{:%F %r}\", tm) == \"2022-01-03 08:04:05 AM\");\n#endif\n      CHECK(fmt_lib::format(\"{:%Y%m%d%H%M%S}\", tm) == \"20220103080405\");\n\n      std::locale::global(loc);\n    } catch (const std::runtime_error&) {\n      WARN(\"Locale en_US not found, skip tests\");\n    }\n  }\n}\n"
  },
  {
    "path": "test/utils/fixtures/GlibTestsFixture.hpp",
    "content": "#pragma once\n#include <glibmm/main.h>\n/**\n * Minimal Glib application to be used for tests that require Glib main loop\n */\nclass GlibTestsFixture : public sigc::trackable {\n public:\n  GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {}\n\n  void setTimeout(int timeout) {\n    Glib::signal_timeout().connect_once([]() { throw std::runtime_error(\"Test timed out\"); },\n                                        timeout);\n  }\n\n  void run(std::function<void()> fn) {\n    Glib::signal_idle().connect_once(fn);\n    main_loop_->run();\n  }\n\n  void quit() { main_loop_->quit(); }\n\n protected:\n  Glib::RefPtr<Glib::MainLoop> main_loop_;\n};\n"
  },
  {
    "path": "test/utils/meson.build",
    "content": "test_inc = include_directories('../../include')\n\ntest_dep = [\n    catch2,\n    fmt,\n    gtkmm,\n    jsoncpp,\n    spdlog,\n]\ntest_src = files(\n    '../main.cpp',\n    '../config.cpp',\n    '../../src/config.cpp',\n    'JsonParser.cpp',\n    'SafeSignal.cpp',\n    'sleeper_thread.cpp',\n    'command.cpp',\n    'css_reload_helper.cpp',\n    '../../src/util/css_reload_helper.cpp',\n)\n\nif tz_dep.found()\n  test_dep += tz_dep\n  test_src += files('date.cpp')\nendif\n\nutils_test = executable(\n    'utils_test',\n    test_src,\n    dependencies: test_dep,\n    include_directories: test_inc,\n)\n\ntest(\n    'utils',\n    utils_test,\n    workdir: meson.project_source_root(),\n)\n"
  },
  {
    "path": "test/utils/sleeper_thread.cpp",
    "content": "#if __has_include(<catch2/catch_test_macros.hpp>)\n#include <catch2/catch_test_macros.hpp>\n#else\n#include <catch2/catch.hpp>\n#endif\n\n#include <sys/wait.h>\n#include <unistd.h>\n\n#include <chrono>\n#include <thread>\n\n#include \"util/sleeper_thread.hpp\"\n\nnamespace waybar::util {\nSafeSignal<bool>& prepare_for_sleep() {\n  static SafeSignal<bool> signal;\n  return signal;\n}\n}  // namespace waybar::util\n\nnamespace {\nint run_in_subprocess(int (*task)()) {\n  const auto pid = fork();\n  if (pid < 0) {\n    return -1;\n  }\n  if (pid == 0) {\n    alarm(5);\n    _exit(task());\n  }\n\n  int status = -1;\n  if (waitpid(pid, &status, 0) != pid) {\n    return -1;\n  }\n  if (!WIFEXITED(status)) {\n    return -1;\n  }\n  return WEXITSTATUS(status);\n}\n\nint run_reassignment_regression() {\n  waybar::util::SleeperThread thread;\n  thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); };\n  thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(1)); };\n  return 0;\n}\n\nint run_control_flag_stress() {\n  for (int i = 0; i < 200; ++i) {\n    waybar::util::SleeperThread thread;\n    thread = [&thread] { thread.sleep_for(std::chrono::milliseconds(1)); };\n\n    std::thread waker([&thread] {\n      for (int j = 0; j < 100; ++j) {\n        thread.wake_up();\n        std::this_thread::yield();\n      }\n    });\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(2));\n    thread.stop();\n    waker.join();\n    if (thread.isRunning()) {\n      return 1;\n    }\n  }\n  return 0;\n}\n}  // namespace\n\nTEST_CASE(\"SleeperThread reassignment does not terminate process\", \"[util][sleeper_thread]\") {\n  REQUIRE(run_in_subprocess(run_reassignment_regression) == 0);\n}\n\nTEST_CASE(\"SleeperThread control flags are stable under concurrent wake and stop\",\n          \"[util][sleeper_thread]\") {\n  REQUIRE(run_in_subprocess(run_control_flag_stress) == 0);\n}\n"
  },
  {
    "path": "tsan.supp",
    "content": "# Suppress common thread issues in dependencies, these are often non-fixable or not an issue.\n# Use it like this (when in repo root): TSAN_OPTIONS=\"suppressions=./tsan.supp\" ./build/waybar\nrace:libfontconfig.so\nrace:libglib-2.0.so\nrace:libpango-1.0.so\nrace:libc.so.6\nrace:libgio-2.0.so\n"
  }
]