--- /dev/null
+---
+Language: Cpp
+Standard: c++17
+BasedOnStyle: Google
+ColumnLimit: 120
+NamespaceIndentation: Inner
+AlignAfterOpenBracket: DontAlign
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+BreakConstructorInitializersBeforeComma: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+SpacesBeforeTrailingComments: 1
+---
--- /dev/null
+---
+Checks: '
+ -*,
+ performance-*,
+ readability-*,
+ clang-analyzer-alpha.core*,
+ clang-analyzer-alpha.security*,
+ clang-analyzer-alpha.unix.cstring*,
+ clang-analyzer-core.uninitialized*,
+ clang-analyzer-cplusplus.*,
+ clang-analyzer-nullability*,
+ clang-analyzer-unix*,
+ cppcoreguidelines*,
+ modernize-use-*,
+ modernize-*,
+ -modernize-raw-string-literal,
+ -modernize-use-bool-literals,
+ -modernize-use-trailing-return-type,
+ -readability-identifier-length,
+ -readability-implicit-bool-cast,
+ -readability-else-after-return,
+ -readability-named-parameter,
+ -readability-implicit-bool-conversion,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+ -cppcoreguidelines-pro-type-vararg,
+ -cppcoreguidelines-pro-type-reinterpret-cast,
+ -cppcoreguidelines-pro-type-union-access,
+ -cppcoreguidelines-pro-type-cstyle-cast,
+ -cppcoreguidelines-pro-bounds-constant-array-index,
+ -cppcoreguidelines-owning-memory,
+ '
+
+CheckOptions:
+ - key: modernize-loop-convert.NamingStyle
+ value: lower_case
+ - key: readability-identifier-naming.ClassCase
+ value: lower_case
+ - key: readability-identifier-naming.ClassConstantCase
+ value: UPPER_CASE
+ - key: readability-identifier-naming.ClassMethodCase
+ value: lower_case
+ - key: readability-identifier-naming.MemberCase
+ value: lower_case
+ - key: readability-identifier-naming.ProtectedMemberPrefix
+ value: 'm_'
+ - key: readability-identifier-naming.PrivateMemberPrefix
+ value: 'm_'
+HeaderFilterRegex: ''
+WarningsAsErrors: ''
+AnalyzeTemporaryDtors: false
+...
--- /dev/null
+root = true
+
+[*]
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+
+[*.py]
+indent_size = 4
+
+[Makefile]
+indent_style = tab
+indent_size = 2
--- /dev/null
+github: polybar
+open_collective: polybar
--- /dev/null
+name: 🐞 Bug Report
+description: Create a report for something that misbehaves
+title: "[Bug]: "
+labels: ["bug", "needs confirmation"]
+body:
+ - type: checkboxes
+ id: checklist
+ attributes:
+ label: Checklist
+ description: Please carefully go through this checklist and check each option.
+ options:
+ - label: I have read the appropriate section in the [contributing guidelines](https://github.com/polybar/polybar/blob/master/CONTRIBUTING.md)
+ required: true
+ - label: I believe this issue is a problem with polybar itself and not a misconfiguration on my part
+ required: true
+ - label: I have searched for other open and closed [issues](https://github.com/polybar/polybar/issues?q=is%3Aissue) that may have already reported this problem
+ required: true
+ - label: I have checked the [known issues](https://github.com/polybar/polybar/wiki/Known-Issues) page for this problem.
+ required: true
+ - label: I have followed the [debugging guide](https://github.com/polybar/polybar/wiki/Debugging-your-Config) to narrow down the problem to a minimal config.
+ required: true
+ - type: textarea
+ id: reproduce
+ attributes:
+ label: Steps to reproduce
+ description: Any steps to take and commands to run to reproduce this issue.
+ placeholder: |
+ 1. `polybar -c ... bar`
+ 2. ...
+ validations:
+ required: true
+ - type: textarea
+ id: config
+ attributes:
+ label: Minimal config
+ description: A minimal but **complete** config with which the problem occurs.
+ render: dosini
+ placeholder: |
+ [bar/example]
+ ...
+
+ [module/...]
+ ...
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Polybar log
+ description: Post everything polybar prints to the terminal when you run it and the issue occurs. If possible, run polybar with a higher log level (e.g. `trace` or `info`).
+ render: text
+ placeholder: |
+ notice: Parsing config file: ...
+ ...
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ description: A clear and concise description of what you expected to happen
+ validations:
+ required: true
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual behavior
+ description: What actually happens
+ validations:
+ required: true
+ - type: input
+ id: wm
+ attributes:
+ label: Window Manager and Version
+ placeholder: ex. i3-gaps 4.19.1
+ validations:
+ required: true
+ - type: input
+ id: distro
+ attributes:
+ label: Linux Distribution
+ placeholder: ex. Ubuntu 21.04
+ validations:
+ required: true
+ - type: textarea
+ id: version
+ attributes:
+ label: Polybar version
+ description: Output of `polybar -vvv`
+ render: text
+ placeholder: |
+ polybar 3.5.7
+
+ Features: +alsa +curl +i3 +mpd +network(libnl) +pulseaudio +xkeyboard
+
+ X extensions: +randr (+monitors) +composite +xkb +xrm +xcursor
+
+ Build type: Release
+ Compiler: /usr/bin/c++
+ Compiler flags: -D_FORTIFY_SOURCE=2 -march=x86-64 -mtune=generic -O2 -pipe -fno-plt -O3 -DNDEBUG -Wall -Wextra -Wpedantic -Wsuggest-override
+ Linker flags: -Wall -Wextra -Wpedantic -Wsuggest-override -Wall -Wextra -Wpedantic -Wsuggest-override
+ validations:
+ required: true
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional Context / Screenshots
+ description: If applicable, add screenshots and additional context to explain your problem
+ validations:
+ required: false
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report! :heart:
--- /dev/null
+blank_issues_enabled: false
+contact_links:
+ - name: 🙋 Ask a question
+ url: https://github.com/polybar/polybar/blob/master/SUPPORT.md
+ about: Have a look at our support resources and channels
+ - name: 💡 Feature request
+ url: https://github.com/polybar/polybar/discussions/categories/ideas
+ about: Suggest your idea over in Discussions
+ - name: 🛠️ Build Issues
+ url: https://github.com/polybar/polybar/discussions/categories/build-support
+ about: Get support when building polybar from source
--- /dev/null
+<!-- Please read our contributing guide before opening a PR: https://github.com/polybar/polybar/blob/master/CONTRIBUTING.md -->
+
+## What type of PR is this? (check all applicable)
+
+* [ ] Refactor
+* [ ] Feature
+* [ ] Bug Fix
+* [ ] Optimization
+* [ ] Documentation Update
+* [ ] Other: *Replace this with a description of the type of this PR*
+
+## Description
+<!--
+ Document user-facing changes in this PR (for example: new config options, changed behavior).
+
+ You can also motivate design decisions here.
+-->
+
+## Related Issues & Documents
+<!-- For example: Fixes #1234, Closes #6789 -->
+
+## Documentation (check all applicable)
+
+* [ ] This PR requires changes to the Wiki documentation (describe the changes)
+* [ ] This PR requires changes to the documentation inside the git repo (please add them to the PR).
+* [ ] Does not require documentation changes
--- /dev/null
+coverage:
+ status:
+ project:
+ default:
+ # Coverage can drop by 0.1% without failing the status
+ threshold: 0.1
+ patch:
+ default:
+ # Patches don't need test coverage
+ # TODO remove once we have proper testing infrastructure and documentation
+ target: 0
+
+ignore:
+ - "tests/**/*"
+ - "lib/**/*"
+
+comment:
+ require_changes: true
+
+github_checks:
+ annotations: false
--- /dev/null
+name: CI
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: 'ref'
+ required: false
+ push:
+ pull_request:
+
+jobs:
+ docs:
+ runs-on: ubuntu-22.04
+ env:
+ COLOR: "ON"
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.inputs.ref }}
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y python3-sphinx
+ - name: Build Documentation
+ run: |
+ mkdir -p build
+ cd build
+ cmake -DDISABLE_ALL=ON -DBUILD_DOC=ON -DSPHINX_FLAGS="-W" ..
+ make doc
+
+ ipc:
+ runs-on: ubuntu-22.04
+ env:
+ COLOR: "ON"
+ steps:
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libxcb-composite0-dev \
+ libxcb-ewmh-dev \
+ libxcb-icccm4-dev \
+ libxcb-image0-dev \
+ libxcb-randr0-dev \
+ libxcb-util0-dev \
+ libxcb1-dev \
+ libcairo2-dev \
+ python3-xcbgen \
+ libuv1-dev \
+ xcb-proto
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+ ref: ${{ github.event.inputs.ref }}
+ - name: Build polybar-msg
+ run: |
+ mkdir -p build
+ cd build
+ cmake -DDISABLE_ALL=ON -DBUILD_POLYBAR_MSG=ON ..
+ make polybar-msg
+
+ build:
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ cxx: [g++, clang++]
+ polybar_build_type: ["full"]
+ build_type: ["Release"]
+ include:
+ - cxx: g++
+ polybar_build_type: "tests"
+ build_type: "Coverage"
+ - cxx: g++
+ polybar_build_type: "minimal"
+ build_type: "Release"
+ env:
+ CXX: ${{ matrix.cxx }}
+ BUILD_TYPE: ${{ matrix.build_type }}
+ POLYBAR_BUILD_TYPE: ${{ matrix.polybar_build_type }}
+ POLYBAR_DIR: ${{ github.workspace }}
+ BUILD_DIR: "${{ github.workspace}}/build"
+ MAKEFLAGS: "-j4"
+ COLOR: "ON"
+ steps:
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libxcb-composite0-dev \
+ libxcb-ewmh-dev \
+ libxcb-icccm4-dev \
+ libxcb-image0-dev \
+ libxcb-randr0-dev \
+ libxcb-util0-dev \
+ libxcb1-dev \
+ libcairo2-dev \
+ python3-xcbgen \
+ libuv1-dev \
+ xcb-proto
+
+ if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then
+ sudo apt-get install -y \
+ libxcb-xkb-dev \
+ libxcb-cursor-dev \
+ libxcb-xrm-dev \
+ i3-wm \
+ libcurl4-openssl-dev \
+ libjsoncpp-dev \
+ libasound2-dev \
+ libpulse-dev \
+ libnl-genl-3-dev \
+ libmpdclient-dev
+ fi
+
+ if [ "$POLYBAR_BUILD_TYPE" = "tests" ]; then
+ sudo apt-get install -y lcov
+ fi
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+ ref: ${{ github.event.inputs.ref }}
+ - name: Summary
+ run: ./common/ci/summary.sh
+ - name: Configure
+ run: ./common/ci/configure.sh
+ - name: Build
+ run: |
+ cd "$BUILD_DIR"
+ make
+ - name: Collect initial coverage
+ if: ${{ matrix.polybar_build_type == 'tests' }}
+ run: |
+ lcov --capture --initial --no-external --directory . -o cov_base.info
+ - name: Tests
+ if: ${{ matrix.polybar_build_type == 'tests' }}
+ run: |
+ cd "$BUILD_DIR"
+ make check
+ - name: Collect coverage
+ if: ${{ matrix.polybar_build_type == 'tests' }}
+ run: |
+ lcov --capture --no-external --directory . -o cov_tests.info
+ lcov --add-tracefile cov_base.info --add-tracefile cov_tests.info -o cov_total.info
+ lcov --remove cov_total.info "${PWD}/build/*" "${PWD}/tests/*" "${PWD}/lib/*" -o cov.info
+ - name: Upload Coverage
+ if: ${{ matrix.polybar_build_type == 'tests' }}
+ uses: codecov/codecov-action@v3
+ with:
+ flags: unittests
+ files: ./cov.info
+ fail_ci_if_error: true
+ - name: Upload Logs
+ if: failure()
+ uses: actions/upload-artifact@v2
+ with:
+ name: cmake
+ path: |
+ build/CMakeFiles/CMakeError.log
+ build/CMakeFiles/CMakeOutput.log
+ retention-days: 5
--- /dev/null
+# Workflow For Releases
+#
+# Automatically creates and uploads a complete release archive for the given
+# release.
+name: Release Workflow
+
+# Is triggered when a new release is published or by hand
+# If triggered by hand, the release tag that this should target has to be
+# specified.
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: 'Release Tag'
+ required: true
+
+jobs:
+ upload:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Get Version
+ if: ${{ github.event.inputs.tag }} != ''
+ run: |
+ if [ ${{ github.event_name }} == 'workflow_dispatch' ]; then
+ echo "Manual Release Triggered"
+ RELEASE_TAG=${{ github.event.inputs.tag }}
+ else
+ echo "Automatic Release Triggered"
+ RELEASE_TAG=${GITHUB_REF#refs/tags/}
+ fi
+ echo "Publishing Version $RELEASE_TAG"
+ {
+ echo "RELEASE_TAG=$RELEASE_TAG"
+ echo "POLYBAR_DIR=polybar-$RELEASE_TAG"
+ echo "POLYBAR_ARCHIVE=polybar-$RELEASE_TAG.tar.gz"
+ } >> "$GITHUB_ENV"
+
+ # Checks out the target tag
+ - uses: actions/checkout@v2
+ with:
+ ref: ${{ env.RELEASE_TAG }}
+ submodules: true
+ path: ${{ env.POLYBAR_DIR }}
+
+ - name: Create Release Archive
+ run: |
+ find "$DIR" -type d -name ".git" -exec rm -rf {} \+
+ tar czf "$ARCHIVE" "$DIR"
+ echo "SHA256SUM=$(sha256sum "$ARCHIVE" | cut -d ' ' -f 1)" >> "$GITHUB_ENV"
+ env:
+ DIR: ${{ env.POLYBAR_DIR }}
+ ARCHIVE: ${{ env.POLYBAR_ARCHIVE }}
+
+ - name: Get Upload URL
+ id: get_upload_url
+ uses: actions/github-script@v3
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const tag = '${{ env.RELEASE_TAG }}';
+ console.log(`Getting Upload URL for '${tag}'`);
+ const release = await github.repos.getReleaseByTag({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ tag: tag
+ });
+ core.exportVariable('UPLOAD_URL', release.data.upload_url);
+ core.exportVariable('RELEASE_ID', release.data.id);
+ core.exportVariable('RELEASE_BODY', release.data.body);
+
+ - name: Upload Release Archive
+ id: upload_archive
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ env.UPLOAD_URL }}
+ asset_path: "./${{ env.POLYBAR_ARCHIVE }}"
+ asset_name: ${{ env.POLYBAR_ARCHIVE }}
+ asset_content_type: application/gzip
+
+ # Adds a download section to the beginning of the release body
+ - name: Update Release Body
+ uses: actions/github-script@v3
+ env:
+ # Existing release body, fetched in the get_upload_url step.
+ RELEASE_BODY: ${{ env.RELEASE_BODY }}
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const fname = '${{ env.POLYBAR_ARCHIVE }}'
+ const url = '${{ steps.upload_archive.outputs.browser_download_url }}'
+ const hash = '${{ env.SHA256SUM }}'
+ let body = "## Download\n\n"
+ body += `[${fname}](${url}) (**sha256**: \`${hash}\`)\n\n`
+ body += process.env.RELEASE_BODY;
+
+ const release = await github.repos.updateRelease({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ release_id: '${{ env.RELEASE_ID}}',
+ body: body
+ });
--- /dev/null
+/build
+/tags
+/compile_commands.json
+*.bak
+*.pyc
+*.swp
+*.tmp
+.tags
+*.user
+
+# clangd
+/.cache
+
+polybar-*.tar
+*.cache
+
+.venv
--- /dev/null
+[submodule "lib/i3ipcpp"]
+ path = lib/i3ipcpp
+ url = https://github.com/polybar/i3ipcpp
+ branch = master
+[submodule "lib/xpp"]
+ path = lib/xpp
+ url = https://github.com/polybar/xpp
+ branch = master
--- /dev/null
+---
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3"
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: doc/conf.py
+ fail_on_warning: true
+
+python:
+ install:
+ - requirements: doc/requirements.txt
--- /dev/null
+{
+ xft/fontconfig
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ fun:FcPatternCreate
+ fun:FcFontRenderPrepare
+ fun:FcFontMatch
+ fun:XftFontMatch
+ fun:XftFontOpenName
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+}
+{
+ xft/fontconfig
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ obj:/usr/lib/libfontconfig.so.*
+ obj:/usr/lib/libfontconfig.so.*
+ fun:FcPatternAddDouble
+ obj:/usr/lib/libXft.so.*
+ obj:/usr/lib/libXft.so.*
+ obj:/usr/lib/libXft.so.*
+ obj:/usr/lib/libXft.so.*
+ fun:XftDefaultHasRender
+ fun:XftDefaultSubstitute
+ fun:XftFontMatch
+ fun:XftFontOpenName
+}
+{
+ xft/fontconfig
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ fun:XftFontCheckGlyph
+ fun:XftGlyphRender
+ fun:XftDrawGlyphs
+ fun:XftDrawString16
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+}
+{
+ xft/fontconfig
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:realloc
+ obj:/usr/lib/libfontconfig.so.*
+ obj:/usr/lib/libfontconfig.so.*
+ fun:FcFontRenderPrepare
+ fun:FcFontMatch
+ fun:XftFontMatch
+ fun:XftFontOpenName
+ ...
+ ...
+ ...
+ ...
+ ...
+}
+{
+ xresource manager
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:realloc
+ obj:/usr/lib/libX11.so.*
+ obj:/usr/lib/libX11.so.*
+ obj:/usr/lib/libX11.so.*
+ fun:_XlcCreateLC
+ fun:_XlcDefaultLoader
+ fun:_XOpenLC
+ fun:_XrmInitParseInfo
+ obj:/usr/lib/libX11.so.*
+ fun:XrmGetStringDatabase
+ ...
+ ...
+}
+{
+ xft conditional jump
+ Memcheck:Cond
+ obj:/usr/lib/libfreetype.so.*
+ obj:/usr/lib/libfreetype.so.*
+ fun:FT_Outline_Decompose
+ obj:/usr/lib/libfreetype.so.*
+ obj:/usr/lib/libfreetype.so.*
+ obj:/usr/lib/libfreetype.so.*
+ obj:/usr/lib/libfreetype.so.*
+ obj:/usr/lib/libfreetype.so.*
+ fun:XftFontLoadGlyphs
+ fun:XftGlyphExtents
+ ...
+ ...
+}
--- /dev/null
+# Changelog
+
+All notable changes to this project will be documented in this file.
+Each release should have the following subsections, if entries exist, in the
+given order: `Breaking`, `Build`, `Deprecated`, `Removed`, `Added`, `Changed`,
+`Fixed`, `Security`.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [3.7.0] - 2023-11-05
+### Breaking
+- `custom/script`:
+ - No longer hides the module if the `exec` command failed and did not change the output from the previous run ([`#2636`](https://github.com/polybar/polybar/issues/2636)). Somewhat similar original behaviour can be imitated with `format-fail`, if necessary.
+ - If the `exec` command produced no output and exited with a non-zero exit code the module is no longer completely empty, but just has an empty `%output%` token. If you relied on this behavior to hide the module under certain circumstances, make sure the script exits with an exit code of zero. ([`#2857`](https://github.com/polybar/polybar/discussions/2857), [`#2861`](https://github.com/polybar/polybar/pull/2861))
+
+### Build
+- Respect `CMAKE_INSTALL_PREFIX` when installing default config ([`#2770`](https://github.com/polybar/polybar/pull/2770), [`#2917`](https://github.com/polybar/polybar/pull/2917))
+- Change default `CMAKE_INSTALL_PREFIX` to `/usr`. Installations with default flags will now go into `/usr` instead of `/usr/local` ([`#2917`](https://github.com/polybar/polybar/pull/2917))
+- Bump C++ version to C++17 ([`#2847`](https://github.com/polybar/polybar/pull/2847))
+
+### Deprecated
+- `custom/text`: The `content` setting and all its properties are deprecated in favor of `format` with the same functionality. ([`#2676`](https://github.com/polybar/polybar/pull/2676))
+- tray: All tray-related settings in the bar section are deprecated. They are replaced by the new tray module ([`#3002`](https://github.com/polybar/polybar/pull/3002))
+ - `tray-position`, `tray-detached`, `tray-maxsize`, `tray-scale`, `tray-transparent`, `tray-background`, `tray-foreground`, `tray-padding`, `tray-offset-x`, `tray-offset-y`
+
+### Added
+- A tray module with type `internal/tray` for positioning the tray like a module ([`#2689`](https://github.com/polybar/polybar/issues/2689))
+- `internal/temperature`: `%temperature-k%` token displays the temperature in degrees Kelvin ([`#2774`](https://github.com/polybar/polybar/discussions/2774), [`#2784`](https://github.com/polybar/polybar/pull/2784))
+- `internal/pulseaudio`: `reverse-scroll` option ([`#2664`](https://github.com/polybar/polybar/pull/2664))
+- `custom/script`: Repeat interval for script failure (`interval-fail`) and `exec-if` (`interval-if`) ([`#943`](https://github.com/polybar/polybar/issues/943), [`#2606`](https://github.com/polybar/polybar/issues/2606), [`#2630`](https://github.com/polybar/polybar/pull/2630))
+- `custom/ipc`:
+ - Added support for `<label>` in `format` ([`#2841`](https://github.com/polybar/polybar/pull/2841)) by [@madhavpcm](https://github.com/madhavpcm).
+ - Added support for `format-i` for each defined `hook-i` ([`#2775`](https://github.com/polybar/polybar/issues/2775), [`#2810`](https://github.com/polybar/polybar/pull/2810)) by [@madhavpcm](https://github.com/madhavpcm).
+- `custom/text`: Loads the `format` setting, which supports the `<label>` tag, if the deprecated `content` is not defined ([`#1331`](https://github.com/polybar/polybar/issues/1331), [`#2673`](https://github.com/polybar/polybar/pull/2673), [`#2676`](https://github.com/polybar/polybar/pull/2676))
+- `internal/backlight`:
+ - `scroll-interval` option ([`#2696`](https://github.com/polybar/polybar/issues/2696), [`#2700`](https://github.com/polybar/polybar/pull/2700))
+ - `poll-interval` setting controls how often the module is updated (in case it does not happen when the brightness changes) ([`#2835`](https://github.com/polybar/polybar/issues/2835), [`#3028`](https://github.com/polybar/polybar/pull/3028))
+- `internal/temperature`: Added `zone-type` setting ([`#2572`](https://github.com/polybar/polybar/issues/2572), [`#2752`](https://github.com/polybar/polybar/pull/2752)) by [@xphoniex](https://github.com/xphoniex)
+- `internal/xwindow`: `%class%` and `%instance%` tokens, which show the contents of the `WM_CLASS` property of the active window ([`#2830`](https://github.com/polybar/polybar/pull/2830))
+- Added `enable-struts` option in bar section to enable/disable struts ([`#2769`](https://github.com/polybar/polybar/issues/2769), [`#2844`](https://github.com/polybar/polybar/pull/2844)) by [@VanillaViking](https://github.com/VanillaViking).
+- `wm-restack`:
+ - `bottom`: lowers polybar to the bottom of the window stack (same as the previous behavior of `generic`) ([`#2961`](https://github.com/polybar/polybar/pull/2961))
+ - `ewmh`: Tries to use the `_NET_SUPPORTING_WM_CHECK` hint to position the bar ([`#2961`](https://github.com/polybar/polybar/pull/2961))
+- `internal/xworkspaces`: `group-by-monitor` setting to decide whether `_NET_DESKTOP_VIEWPORT` should be used to group workspaces by monitor; ([`#2603`](https://github.com/polybar/polybar/issues/2603), [`#2926`](https://github.com/polybar/polybar/pull/2926)) by [@slotThe](https://github.com/slotThe/).
+
+### Changed
+- `custom/script`:
+ - No longer produces a completely empty module if the `exec` command failed. It only produces an empty module if the script had a zero exit code. ([`#2857`](https://github.com/polybar/polybar/discussions/2857), [`#2861`](https://github.com/polybar/polybar/pull/2861))
+ - Bumped the script polling interval (not related to the `interval` setting) to decrease wakeups. Polybar may take slightly longer to shut down. [`#2879`](https://github.com/polybar/polybar/pull/2879)
+- `internal/fs`: Use `/` as a fallback if no mountpoints are specified ([`#2572`](https://github.com/polybar/polybar/issues/2572), [`#2705`](https://github.com/polybar/polybar/pull/2705))
+- `internal/backlight`:
+ - Detect backlight if none specified ([`#2572`](https://github.com/polybar/polybar/issues/2572), [`#2728`](https://github.com/polybar/polybar/pull/2728))
+ - `use-actual-brightness` now always defaults to `true` (even for `amdgpu` backlights) ([`#2835`](https://github.com/polybar/polybar/issues/2835), [`2839`](https://github.com/polybar/polybar/pull/2839))
+- Providing a negative min-width to a token adds right-padding ([`#2789`](https://github.com/polybar/polybar/issues/2789), [`#2801`](https://github.com/polybar/polybar/pull/2801)) by [@VanillaViking](https://github.com/VanillaViking).
+- Changed fuzzy match option on i3 and bspwm modules to find longest match instead of the first match ([`#2831`](https://github.com/polybar/polybar/pull/2831), [`#2829`](https://github.com/polybar/polybar/issues/2829)) by [@Ron0Studios](https://github.com/ron0studios/).
+- `wm-restack`
+ - `generic`: Is now a best effort combination of other restacking strategies. First tries `ewmh` and then the `bottom` strategy ([`#2961`](https://github.com/polybar/polybar/pull/2961))
+ - `bspwm`: Will restack above the topmost bspwm root window instead of the root window associated with the monitor polybar is on ([`#3019`](https://github.com/polybar/polybar/pull/3019))
+
+### Fixed
+- Waiting for double click interval on modules that don't have a double click action ([`#2663`](https://github.com/polybar/polybar/issues/2663), [`#2695`](https://github.com/polybar/polybar/pull/2695))
+- renderer:
+ - Small gaps when rendering emojis ([`#2785`](https://github.com/polybar/polybar/issues/2785), [`#2802`](https://github.com/polybar/polybar/pull/2802))
+ - Crash when using pseudo-transparency with certain wallpapers ([`#2798`](https://github.com/polybar/polybar/issues/2798), [`#2813`](https://github.com/polybar/polybar/pull/2813))
+ - Crash when invalid UTF-8 text is encountered ([`#2091`](https://github.com/polybar/polybar/issues/2091), [`#2958`](https://github.com/polybar/polybar/pull/2958))
+- config:
+ - Error reporting for deprecated config values ([`#2724`](https://github.com/polybar/polybar/issues/2724))
+ - Also monitor include-files for changes when --reload is set ([`#675`](https://github.com/polybar/polybar/issues/675), [`#2759`](https://github.com/polybar/polybar/pull/2759))
+- `internal/xwindow`: module does not crash when a tag is not provided in format ([`#2826`](https://github.com/polybar/polybar/issues/2826), [`#2833`](https://github.com/polybar/polybar/pull/2833)) by [@VanillaViking](https://github.com/VanillaViking)
+- `internal/i3`: module errors when i3 has negative gaps ([`#2888`](https://github.com/polybar/polybar/issues/2888), [`#2889`](https://github.com/polybar/polybar/pull/2889))
+- `internal/backlight`: Fix module being one step behind every update ([`#2835`](https://github.com/polybar/polybar/issues/2835), [`#3028`](https://github.com/polybar/polybar/pull/3028))
+- `wm-restack = bspwm`: bar may become unclickable if there are overlapping monitors ([`#2873`](https://github.com/polybar/polybar/issues/2873), [`#2961`](https://github.com/polybar/polybar/pull/2961))
+
+## [3.6.3] - 2022-05-04
+### Fixed
+- `custom/script`: Output clearing when `exec-if` fails ([`#2674`](https://github.com/polybar/polybar/issues/2674))
+- `internal/battery`: `poll-interval` not working ([`#2649`](https://github.com/polybar/polybar/issues/2649), [`#2677`](https://github.com/polybar/polybar/pull/2677))
+- ipc: Polybar failing to open IPC channel after another user already ran polybar, if `XDG_RUNTIME_DIR` is not set ([`#2683`](https://github.com/polybar/polybar/issues/2683), [`#2684`](https://github.com/polybar/polybar/pull/2684))
+- No overlines/underlines being drawn when using offsets ([`#2685`](https://github.com/polybar/polybar/pull/2685))
+- Update struts (`_NET_WM_STRUT_PARTIAL`) when hiding the bar ([`#2702`](https://github.com/polybar/polybar/pull/2702))
+- `internal/pulseaudio`: Hanging during startup ([`#2707`](https://github.com/polybar/polybar/issues/2707), [`#2709`](https://github.com/polybar/polybar/pull/2709))
+- `internal/xworkspaces`: Updates of `_NET_DESKTOP_VIEWPORT` being ignored ([`#2693`](https://github.com/polybar/polybar/issues/2693), [`#2698`](https://github.com/polybar/polybar/pull/2698))
+
+## [3.6.2] - 2022-04-03
+### Fixed
+- `format-offset` being ignored ([`#2643`](https://github.com/polybar/polybar/pull/2643))
+- Negative struts (`margin-bottom`, `margin-top`) being ignored ([`#2642`](https://github.com/polybar/polybar/issues/2642), [`#2644`](https://github.com/polybar/polybar/pull/2644))
+- Positioning in awesomeWM ([`#2651`](https://github.com/polybar/polybar/pull/2651))
+- `internal/xworkspaces`: The module sometimes crashed polybar when windows were closed. ([`#2655`](https://github.com/polybar/polybar/pull/2655))
+- Mouseover error when only one cursor is defined ([`#2656`](https://github.com/polybar/polybar/pull/2656))
+- `custom/script`: Timing inconsistencies ([`#2650`](https://github.com/polybar/polybar/issues/2650), first described at [`#2630`](https://github.com/polybar/polybar/pull/2630))
+
+## [3.6.1] - 2022-03-05
+### Build
+- Fixed compiler warning in Clang 13 ([`#2613`](https://github.com/polybar/polybar/pull/2613))
+- Fixed compiler error in GCC 12 ([`#2616`](https://github.com/polybar/polybar/pull/2616), [`#2614`](https://github.com/polybar/polybar/issues/2614))
+- Fixed installation of docs when some are not generated (man, html...) ([`#2612`](https://github.com/polybar/polybar/pull/2612))
+- Fix `LDFLAGS` not being respected ([`#2619`](https://github.com/polybar/polybar/pull/2619))
+
+### Fixed
+- `tray-offset-x`, `tray-offset-y`, `offset-x`, and `offset-y` were mistakenly capped below at 0 ([`#2620`](https://github.com/polybar/polybar/pull/2620))
+- `custom/script`: Polybar shutdown being stalled by hanging script ([`#2621`](https://github.com/polybar/polybar/pull/2621))
+- `polybar-msg`: Wrong hint when using deprecated `hook` ([`#2624`](https://github.com/polybar/polybar/pull/2624))
+
+## [3.6.0] - 2022-03-01
+### Breaking
+- We added the backslash escape character (\\) for configuration values. This means that the literal backslash character now has special meaning in configuration files, therefore if you want to use it in a value as a literal backslash, you need to escape it with the backslash escape character. The parser logs an error if any unescaped backslashes are found in a value. This affects you only if you are using two consecutive backslashes in a config value, which will now be interpreted as a single literal backslash. ([`#2354`](https://github.com/polybar/polybar/issues/2354))
+- We rewrote our formatting tag parser. This shouldn't break anything, if you experience any problems, please let us know. The new parser now gives errors for certain invalid tags where the old parser would just silently ignore them. Adding extra text to the end of a valid tag now produces an error. For example, tags like `%{T-a}`, `%{T2abc}`, `%{rfoo}`, and others will now start producing errors. This does not affect you unless you are producing your own invalid formatting tags (for example in a script).
+- For security reasons, the named pipe at `/tmp/polybar_mqueue.<PID>` had its permission bits changed from `666` to `600` to prevent sending ipc messages to polybar processes running under a different user.
+- Also for security reasons, the `polybar-msg` command will now only send messages to polybar processes running under the same user. See the [IPC documentation](https://polybar.readthedocs.io/user/ipc.html) for what exactly this means.
+
+### Build
+- New dependency: [libuv](https://github.com/libuv/libuv). At least version 1.3 is required.
+- Bump the minimum cmake version to 3.5
+- The `BUILD_IPC_MSG` option has been renamed to `BUILD_POLYBAR_MSG`
+- Building the documentation is now enabled by default and not just when `sphinx-build` is found.
+- Users can control exactly which targets should be available with the following cmake options (together with their default value):
+ - `BUILD_POLYBAR=ON` - Builds the `polybar` executable
+ - `BUILD_POLYBAR_MSG=ON` - Builds the `polybar-msg` executable
+ - `BUILD_TESTS=OFF` - Builds the test suite
+ - `BUILD_DOC=ON` - Builds the documentation
+ - `BUILD_DOC_HTML=BUILD_DOC` - Builds the html documentation (depends on `BUILD_DOC`)
+ - `BUILD_DOC_MAN=BUILD_DOC` - Builds the manpages (depends on `BUILD_DOC`)
+ - `BUILD_CONFIG=ON` - Generates the default config
+ - `BUILD_SHELL=ON` - Generates shell completion files
+ - `DISABLE_ALL=OFF` - Disables all above targets by default. Individual targets can still be enabled explicitly.
+- The documentation can no longer be built by directly configuring the `doc` directory.
+- The `POLYBAR_FLAGS` cmake variable can be used to pass extra C++ compiler flags.
+- The sample config file has been removed.
+- Polybar now ships a default config that is installed to `/etc/polybar/config.ini`, it lives in `doc/config.ini`. It will also be placed in the `examples` directory in the documentation folder. ([`#2405`](https://github.com/polybar/polybar/issues/2405))
+- The `userconfig` target has been removed, you can no longer use `make userconfig`. As an alternative, you can copy the default config from `/etc/polybar/config.ini`.
+- The `DEBUG_SHADED` cmake variable and its associated functionality has been removed.
+
+### Deprecated
+- `[settings]`: `throttle-output` and `throttle-output-for` have been removed. The new event loop already does a similar thing where it coalesces update triggers if they happen directly after one another, leading to only a single bar update.
+- When not specifying the config file with `--config`, naming your config file `config` is deprecated. Rename your config file to `config.ini`.
+- Directly writing ipc messages to `/tmp/polybar_mqueue.<PID>` is deprecated, users should always use `polybar-msg`. As a consequence the message format used for IPC is deprecated as well.
+- `polybar-msg hook` is deprecated in favor of using the hook action. `polybar-msg` will tell you the correct command to use.
+
+### Added
+- Support `px` and `pt` units everyhwere where before only a number of spaces or pixels could be specified. ([`#2578`](https://github.com/polybar/polybar/pull/2578), [`#1651`](https://github.com/polybar/polybar/issues/1651), [`#951`](https://github.com/polybar/polybar/issues/951))
+- `internal/alsa`: Right and middle click settings. ([`#2566`](https://github.com/polybar/polybar/issues/2566), [`#2573`](https://github.com/polybar/polybar/pull/2573))
+- `internal/network`:
+ - New token `%mac%` shows MAC address of selected interface ([`#2568`](https://github.com/polybar/polybar/issues/2568), [`#2569`](https://github.com/polybar/polybar/pull/2569))
+ - New token `%netspeed%` that provides the total speed of the internet (up + down speed) ([`#2590`](https://github.com/polybar/polybar/pull/2590), [`#1083`](https://github.com/polybar/polybar/issues/1083))
+ - `speed-unit = B/s` can be used to customize how network speeds are displayed. ([`#2068`](https://github.com/polybar/polybar/pull/2068))
+ - `interface-type` may be used in place of `interface` to automatically select a network interface ([`#2025`](https://github.com/polybar/polybar/pull/2025), [`#339`](https://github.com/polybar/polybar/issues/339))
+- Polybar can now read config files from stdin: `polybar -c /dev/stdin`. ([`#2545`](https://github.com/polybar/polybar/pull/2545))
+- `custom/script`:
+ - Setting environment variables using `env-*` config option. ([`#2090`](https://github.com/polybar/polybar/issues/2090), [`#2512`](https://github.com/polybar/polybar/pull/2512))
+ - Add formatting for script failure (`format-fail`, `label-fail`) ([`#2588`](https://github.com/polybar/polybar/issues/2588), [`#2596`](https://github.com/polybar/polybar/pull/2596))
+- Support for ramp weights. ([`#1750`](https://github.com/polybar/polybar/issues/1750), [`#2505`](https://github.com/polybar/polybar/pull/2505))
+- `internal/memory`: New tokens `%used%`, `%free%`, `%total%`, `%swap_total%`, `%swap_free%`, and `%swap_used%` that automatically switch between MiB and GiB when below or above 1GiB. ([`#2472`](https://github.com/polybar/polybar/issues/2472), [`#2488`](https://github.com/polybar/polybar/pull/2488))
+- `internal/i3`: `show-urgent` option to always show urgent windows when `pin-workspace` is active ([`#2374`](https://github.com/polybar/polybar/issues/2374), [`#2378`](https://github.com/polybar/polybar/pull/2378))
+- `internal/xworkspaces`:
+ - `reverse-scroll` can be used to reverse the scroll direction when cycling through desktops. ([`#2365`](https://github.com/polybar/polybar/pull/2365))
+ - `%nwin%` can be used to display the number of open windows per workspace ([`#604`](https://github.com/polybar/polybar/issues/604), [`#2329`](https://github.com/polybar/polybar/pull/2329))
+- Initial support for the backslash escape character (\\) in configs. ([`#2354`](https://github.com/polybar/polybar/issues/2354), [`#2361`](https://github.com/polybar/polybar/pull/2361))
+- Warn states for the cpu, memory, fs, and battery modules. ([`#570`](https://github.com/polybar/polybar/issues/570), [`#956`](https://github.com/polybar/polybar/issues/956), [`#1871`](https://github.com/polybar/polybar/issues/1871), [`#2141`](https://github.com/polybar/polybar/issues/2141), [`#2199`](https://github.com/polybar/polybar/pull/2199))
+ - `internal/battery`: `format-low`, `label-low`, `animation-low`, `low-at = 10`.
+ - `internal/cpu`: `format-warn`, `label-warn`, `warn-percentage = 80`
+ - `internal/fs`: `format-warn`, `label-warn`, `warn-percentage = 90`
+ - `internal/memory`: `format-warn`, `label-warn`, `warn-percentage = 90`
+- `radius` now affects the bar border as well ([`#1566`](https://github.com/polybar/polybar/issues/1566), [`#2359`](https://github.com/polybar/polybar/pull/2359))
+- Per-corner radius with `radius-{bottom,top}-{left,right}` ([`#2294`](https://github.com/polybar/polybar/issues/2294), [`#2297`](https://github.com/polybar/polybar/pull/2297))
+- `internal/xkeyboard`:
+ - `%variant%` token to display the keyboard layout variant ([`#316`](https://github.com/polybar/polybar/issues/316), [`#2163`](https://github.com/polybar/polybar/pull/2163))
+ - Allow matching of variants in `layout-icon` ([`#2414`](https://github.com/polybar/polybar/issues/2414), [`#2521`](https://github.com/polybar/polybar/pull/2521))
+- Config option to hide a certain module (`hidden = false`) ([`#2108`](https://github.com/polybar/polybar/issues/2108), [`#2342`](https://github.com/polybar/polybar/pull/2342))
+- Actions to control visibility of modules (`module_toggle`, `module_show`, and `module_hide`) ([`#2108`](https://github.com/polybar/polybar/issues/2108), [`#2426`](https://github.com/polybar/polybar/pull/2426))
+- `internal/backlight`: `use-actual-brightness` option to use the `actual_brightness` file to get the brightness ([`#2380`](https://github.com/polybar/polybar/pull/2380))
+- `wm-restack = generic` option that lowers polybar to the bottom of the window stack. Fixes the issue where the bar is being drawn on top of fullscreen windows in xmonad. ([`#2205`](https://github.com/polybar/polybar/issues/2205), [`#2404`](https://github.com/polybar/polybar/pull/2404))
+- `internal/bspwm`: `occupied-scroll = true` option allows scrolling through occupied desktops only. ([`#2427`](https://github.com/polybar/polybar/issues/2427), [`#2428`](https://github.com/polybar/polybar/pull/2428))
+- `custom/ipc`:
+ - `send` action to send arbitrary strings to be displayed in the module. ([`#2455`](https://github.com/polybar/polybar/issues/2455), [`#2463`](https://github.com/polybar/polybar/pull/2463))
+ - `hook`, `next`, `prev`, `reset` actions to control the module through actions instead of the deprecated hook messages ([`#2464`](https://github.com/polybar/polybar/issues/2464), [`#2528`](https://github.com/polybar/polybar/pull/2528))
+- Added `double-click-interval` setting to the bar section to control the time interval in which a double-click is recognized. Defaults to 400 (ms) ([`#1441`](https://github.com/polybar/polybar/issues/1441), [`#2510`](https://github.com/polybar/polybar/pull/2510))
+- Added a new `tray-foreground` setting to give hints to tray icons about what color they should be. ([`#2235`](https://github.com/polybar/polybar/issues/2235), [`#2552`](https://github.com/polybar/polybar/pull/2552))
+- `polybar-msg`:
+ - For module actions, you can now also specify the module name, action name, and optional data as separate arguments. ([`#2539`](https://github.com/polybar/polybar/pull/2539))
+ - Added man page: `man 1 polybar-msg` ([`#2539`](https://github.com/polybar/polybar/pull/2539))
+
+### Changed
+- Polybar now also reads `config.ini` when searching for config files. ([`#2323`](https://github.com/polybar/polybar/issues/2323), [`#2324`](https://github.com/polybar/polybar/pull/2324))
+- Polybar additionally searches in `XDG_CONFIG_DIRS/polybar/config.ini` (or `/etc/xdg/polybar/config.ini` if it is not set) and `/etc/polybar/config.ini` for config files. ([`#2016`](https://github.com/polybar/polybar/issues/2016), [`#2511`](https://github.com/polybar/polybar/pull/2511))
+- We rewrote polybar's main event loop. This shouldn't change any behavior for the user, but be on the lookout for X events, click events, or ipc messages not arriving and the bar not shutting down/restarting properly and let us know if you find any issues. ([`#2384`](https://github.com/polybar/polybar/pull/2384))
+- Slight changes to the value ranges the different ramp levels are responsible for in the cpu, memory, fs, and battery modules. The first level is used for everything at and below the start of the value range and the last level for everything at and above the end of the value range. The other levels are evenly distributed over the value range as before. The value range is bounded by the new warning thresholds. ([`#2199`](https://github.com/polybar/polybar/pull/2199))
+- `custom/script`: `interval` now defaults to 0 if `tail = true` as per the documentation. ([`#2240`](https://github.com/polybar/polybar/pull/2240))
+- `internal/network`: Increased precision for upload and download speeds: 0 decimal places for KB/s (as before), 1 for MB/s and 2 for GB/s. ([`#2054`](https://github.com/polybar/polybar/pull/2054))
+- Clicks arriving in close succession, no longer get dropped. Before polybar would drop any click that arrived within 5ms of the previous one. ([`#2510`](https://github.com/polybar/polybar/pull/2510))
+- Increased the double click interval from 150ms to 400ms. ([`#2510`](https://github.com/polybar/polybar/pull/2510))
+- Stop ignoring actions if they arrive while the previous one hasn't been processed yet. ([`#2469`](https://github.com/polybar/polybar/issues/2469), [`#2517`](https://github.com/polybar/polybar/pull/2517))
+- Polybar can now be run without passing the bar name as argument given that the configuration file only defines one bar ([`#2525`](https://github.com/polybar/polybar/issues/2525), [`#2526`](https://github.com/polybar/polybar/pull/2526))
+- `include-directory` and `include-file` now support relative paths. The paths are relative to the folder of the file where those directives appear. ([`#2523`](https://github.com/polybar/polybar/issues/2523), [`#2535`](https://github.com/polybar/polybar/issues/2535))
+- `custom/ipc`: Empty output strings are no longer formatted. This prevents extraneous spaces and separators from appearing in the bar when the output of an ipc module is empty. ([`#2549`](https://github.com/polybar/polybar/pull/2549))
+
+### Fixed
+- Broken positioning in Openbox when the bar is hidden and shown again ([`#2021`](https://github.com/polybar/polybar/issues/2021), [`#2600`](https://github.com/polybar/polybar/pull/2600))
+- Handling of action blocks that contain negative offsets ([`#1814`](https://github.com/polybar/polybar/issues/1814), [`#2601`](https://github.com/polybar/polybar/pull/2601))
+- `polybar -m` used to show both physical outputs and RandR monitors, even if the outputs were covered by monitors. ([`#2481`](https://github.com/polybar/polybar/issues/2481), [`#2485`](https://github.com/polybar/polybar/pull/2485))
+- Parser error if click command contained `}` ([`#2040`](https://github.com/polybar/polybar/issues/2040), [`#2303`](https://github.com/polybar/polybar/pull/2303))
+- Some modules stop updating when system time moves backwards. ([`#857`](https://github.com/polybar/polybar/issues/857), [`#1932`](https://github.com/polybar/polybar/issues/1932), [`#2559`](https://github.com/polybar/polybar/pull/2559))
+- `custom/script`: Concurrency issues with fast-updating tailed scripts. ([`#1978`](https://github.com/polybar/polybar/issues/1978), [`#2518`](https://github.com/polybar/polybar/pull/2518))
+- `internal/alsa`: Slight imprecision when calculating percentages. This caused the volume reported to be off by one. ([`#2399`](https://github.com/polybar/polybar/issues/2399), [`#2401`](https://github.com/polybar/polybar/pull/2401))
+- `internal/backlight`: With amdgpu backlights, the brightness indicator was slightly behind. ([`#2367`](https://github.com/polybar/polybar/issues/2367), [`#2380`](https://github.com/polybar/polybar/pull/2380))
+- `internal/bspwm`: Warning message regarding T@ ([`#2371`](https://github.com/polybar/polybar/issues/2371), [`#2439`](https://github.com/polybar/polybar/pull/2439))
+- `internal/xkeyboard`: Trailing space after the layout label when indicators are empty and made sure right amount of spacing is added between the indicator labels ([`#2292`](https://github.com/polybar/polybar/issues/2292), [`#2306`](https://github.com/polybar/polybar/pull/2306))
+- `internal/xworkspaces`:
+ - Broken scroll-wrapping and order of workspaces when scrolling ([`#2491`](https://github.com/polybar/polybar/issues/2491), [`#2492`](https://github.com/polybar/polybar/pull/2492))
+ - Module would error if WM was not full started up. ([`#1915`](https://github.com/polybar/polybar/issues/1915), [`#2429`](https://github.com/polybar/polybar/pull/2429))
+ - Make the urgent hint persistent ([`#1081`](https://github.com/polybar/polybar/issues/1081), [`#2340`](https://github.com/polybar/polybar/pull/2340))
+ - Crash when the WM sets -1 for `_NET_WM_DESKTOP` ([`#2352`](https://github.com/polybar/polybar/issues/2352), [`#2353`](https://github.com/polybar/polybar/issues/2353))
+- `internal/network`: The module now properly supports 'altnames' for interfaces. ([`#2540`](https://github.com/polybar/polybar/pull/2540))
+- `internal/battery`: More accurate battery state ([`#2563`](https://github.com/polybar/polybar/issues/2563), [`#2556`](https://github.com/polybar/polybar/pull/2556))
+- Offset tag does not respect current background color ([`#2578`](https://github.com/polybar/polybar/pull/2578), [`#1700`](https://github.com/polybar/polybar/issues/1700))
+- Crash when negative margin or padding was specified ([`#2578`](https://github.com/polybar/polybar/pull/2578), [`#1265`](https://github.com/polybar/polybar/issues/1265))
+
+## [3.5.7] - 2021-09-21
+### Fixed
+- The tray mistakenly removed tray icons that did not support XEMBED
+ ([`#2479`](https://github.com/polybar/polybar/issues/2479),
+ [`#2442`](https://github.com/polybar/polybar/issues/2442))
+- `custom/ipc`: Only the first appearance of the `%pid%` token was replaced
+ ([`#2500`](https://github.com/polybar/polybar/issues/2500))
+
+## [3.5.6] - 2021-05-24
+### Build
+- Support building documentation on sphinx 4.0 ([`#2424`](https://github.com/polybar/polybar/issues/2424))
+### Fixed
+- Tray icons sometimes appears outside of bar ([`#2430`](https://github.com/polybar/polybar/issues/2430), [`#1679`](https://github.com/polybar/polybar/issues/1679))
+- Crash in the i3 module ([`#2416`](https://github.com/polybar/polybar/issues/2416))
+
+## [3.5.5] - 2021-03-01
+### Build
+- Support older python sphinx versions again ([`#2356`](https://github.com/polybar/polybar/issues/2356))
+
+## [3.5.4] - 2021-01-07
+### Fixed
+- Wrong text displayed if module text ends with `}` ([`#2331`](https://github.com/polybar/polybar/issues/2331))
+
+## [3.5.3] - 2020-12-23
+### Build
+- Don't use `git` when building documentation ([`#2309`](https://github.com/polybar/polybar/issues/2309))
+### Fixed
+- Empty color values are no longer treated as invalid and no longer produce an error.
+
+[Unreleased]: https://github.com/polybar/polybar/compare/3.7.0...HEAD
+[3.7.0]: https://github.com/polybar/polybar/releases/tag/3.7.0
+[3.6.3]: https://github.com/polybar/polybar/releases/tag/3.6.3
+[3.6.2]: https://github.com/polybar/polybar/releases/tag/3.6.2
+[3.6.1]: https://github.com/polybar/polybar/releases/tag/3.6.1
+[3.6.0]: https://github.com/polybar/polybar/releases/tag/3.6.0
+[3.5.7]: https://github.com/polybar/polybar/releases/tag/3.5.7
+[3.5.6]: https://github.com/polybar/polybar/releases/tag/3.5.6
+[3.5.5]: https://github.com/polybar/polybar/releases/tag/3.5.5
+[3.5.4]: https://github.com/polybar/polybar/releases/tag/3.5.4
+[3.5.3]: https://github.com/polybar/polybar/releases/tag/3.5.3
--- /dev/null
+#
+# Build configuration
+#
+cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
+project(polybar CXX)
+
+# Extract version information from version.txt. The first line that looks like
+# a version string is used, so the file supports comments
+file(STRINGS version.txt version_txt REGEX "^[0-9]+\\.[0-9]+\\.[0-9]+.*$" LIMIT_COUNT 1)
+
+# If we are in a git repo we can get the version information from git describe
+execute_process(COMMAND git describe --tags --dirty=-dev
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ RESULT_VARIABLE git_result
+ OUTPUT_VARIABLE git_describe
+ OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
+
+if(git_result EQUAL "0")
+ set(APP_VERSION "${git_describe}")
+else()
+ message(STATUS "Could not detect version with git, falling back to built-in version information.")
+ set(APP_VERSION "${version_txt}")
+endif()
+
+# Set the default installation prefix to /usr
+# Otherwise the default value is /usr/local which causes the default config
+# file to be installed to /usr/local/etc, with /usr, cmake has special handling
+# for this.
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Project-default installation prefix" FORCE)
+endif()
+
+list(APPEND CMAKE_MODULE_PATH
+ ${PROJECT_SOURCE_DIR}/cmake
+ ${PROJECT_SOURCE_DIR}/cmake/common
+ ${PROJECT_SOURCE_DIR}/cmake/modules)
+
+include(GNUInstallDirs)
+include(utils)
+include(01-core)
+include(02-opts)
+include(04-targets)
+
+if(BUILD_DOC)
+ add_subdirectory(doc)
+endif()
+
+if (BUILD_SHELL)
+ add_subdirectory(contrib/bash)
+ add_subdirectory(contrib/zsh)
+endif()
+
+# Setup everything that uses a C++ compiler (polybar, polybar-msg, tests)
+if(HAS_CXX_COMPILATION)
+ include(cxx)
+ if(BUILD_LIBPOLY)
+ include(libpoly)
+ add_subdirectory(lib)
+ endif()
+ add_subdirectory(include)
+ add_subdirectory(src bin)
+endif()
+
+# We need to enable testing in the root folder so that 'ctest' and 'make test'
+# can be run in the build directory
+if(BUILD_TESTS)
+ enable_testing()
+ add_subdirectory(tests)
+endif()
+
+if(BUILD_CONFIG)
+ install(FILES ${CMAKE_SOURCE_DIR}/doc/config.ini
+ DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${PROJECT_NAME}
+ COMPONENT config)
+endif()
+
+include(05-summary)
--- /dev/null
+# Contributing
+
+First of all, thank you very much for considering contributing to polybar. You
+are awesome! :tada:
+
+**Table of Contents:**
+* [Bug Reports](#bug-reports)
+* [Pull Requests](#pull-requests)
+ + [Testing](#testing)
+ + [Changelog](#changelog)
+ + [Documentation](#documentation)
+ + [Style](#style)
+* [Donations](#donations)
+
+## Bug Reports
+
+Bugs should be reported at the polybar issue tracker, using the [bug report
+template](https://github.com/polybar/polybar/issues/new?template=bug_report.yml).
+Make sure you fill out all the required sections.
+
+Before opening a bug report, please search our [issue
+tracker](https://github.com/polybar/polybar/issues?q=is%3Aissue) and [known
+issues page](https://github.com/polybar/polybar/wiki/Known-Issues) for your
+problem to avoid duplicates.
+
+If your issue has already been reported but is already marked as fixed and the
+version of polybar you are using includes this supposed fix, feel free to open a
+new issue.
+
+You should also go through our [debugging
+guide](https://github.com/polybar/polybar/wiki/Debugging-your-Config) to confirm
+what you are experiencing is indeed a polybar bug and not an issue with your
+configuration.
+This will also help you narrow down the issue which, in turn, will help us
+resolve it, if it turns out to be a bug in polybar.
+
+If this bug was not present in a previous version of polybar and you know how
+to, doing a `git bisect` and providing us with the commit ID that introduced the
+issue would be immensely helpful.
+
+## Pull Requests
+
+If you want to start contributing to polybar, a good place to start are issues
+labeled with
+[help wanted](https://github.com/polybar/polybar/labels/help%20wanted)
+or
+[good first issue](https://github.com/polybar/polybar/labels/good%20first%20issue).
+
+Except for small changes, PRs should always address an already open and accepted
+issue.
+Otherwise you run the risk of spending time implementing something and then the
+PR being rejected because the feature you implemented was not actually something
+we want in polybar.
+
+Issues with any of the following labels are generally safe to start working on,
+unless they also have the `needs confirmation` label or someone else has
+already claimed them:
+
+* [bug](https://github.com/polybar/polybar/labels/bug)
+* [confirmed](https://github.com/polybar/polybar/labels/confirmed)
+* [good first issue](https://github.com/polybar/polybar/labels/good%20first%20issue)
+* [help wanted](https://github.com/polybar/polybar/labels/help%20wanted)
+* [hacktoberfest](https://github.com/polybar/polybar/labels/Hacktoberfest) (can also be worked on outside of October :wink:)
+
+For anything else, it's a good idea to first comment under the issue to ask
+whether it is something that can/should be worked on right now.
+This is especially true for issues labeled with `feature` (and none of the
+labels listed above), here a feature may depend on some other things being
+implemented first or it may need to be split into many smaller features, because
+it is too big otherwise.
+In particular, this means that you should not open a feature request and
+immediately start working on that feature, unless you are very sure it will be
+accepted or accept the risk of it being rejected.
+
+Things like documentation changes or refactorings, don't necessarily need an
+issue associated with them.
+These changes are less likely to be rejected since they don't change the
+behavior of polybar.
+Nevertheless, for bigger changes or when in doubt, open an issue and ask whether
+such changes would be desirable.
+
+To claim an issue, comment under it to let others know that you are working on
+it.
+
+Feel free to ask for feedback about your changes at any time.
+Especially when implementing features, this can be very useful because it allows
+us to make sure you are going in the direction we had envisioned for that
+feature and you don't lose time on something that ultimately has to be
+rewritten.
+In that case, a [draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
+is a useful tool.
+
+When creating a PR, please fill out the PR template.
+
+### Testing
+
+Your PR must pass all existing tests.
+If possible, you should also add tests for the things you write.
+However, this is not always possible, for example when working on modules.
+But at least isolated components should be tested.
+
+See the [testing
+page](https://polybar.readthedocs.io/en/latest/dev/testing.html) in the
+documentation.
+Also don't hesitate to ask for help, testing isn't that mature in polybar yet
+and some things may be harder/impossible to test right now.
+
+### Changelog
+
+We use the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format for
+keeping track of changes in a release.
+
+If your PR introduces notable changes to polybar, please add them the correct
+subsection in the `Unreleased` section in the `CHANGELOG.md` file at the root
+of this repository.
+Notable changes are any user-visible changes, like bug fixes, new config
+options, changes to the build, etc., but not, for example, code cleanup that
+doesn't change polybar's behavior or minor documentation changes.
+One thing that also should not be added to the changelog are bugfixes for
+unreleased features.
+
+An entry in the changelog should include a link to the issue(s) that the PR
+addresses, to the PR itself, and to your username:
+
+```
+- A short description of the change
+ ([`#XYZ`](https://github.com/polybar/polybar/issues/XYZ),
+ [`#UVW`](https://github.com/polybar/polybar/pull/UVW))
+ by [@yourname](https://github.com/yourname).
+```
+
+You will first need to open the PR before you can link to it though :wink:.
+
+
+If you are unsure whether something is a notable change, just add it to the
+changelog and we can determine whether it is a notable change when reviewing.
+
+### Documentation
+
+Right now, documentation for polybar lives in two places: The GitHub wiki and
+the git repo itself.
+
+Ultimately, most of the documentation is supposed to live in the repo itself.
+
+For now, if your PR requires documentation changes in the repo, those changes
+need to be in the PR as well.
+
+Changes on the wiki should not be made right away because the wiki should
+reflect the currently released version and not the development version.
+In that case, outline the documentation changes that need to be made (for
+example, which new config options are available).
+If your PR would introduce a lot of new documentation on the wiki, let us know
+and we can decide if we want to put some of the documentation directly into the
+repo.
+
+### Style
+
+Please read our [style
+guide](https://polybar.readthedocs.io/en/latest/dev/style-guide.html).
+
+## Donations
+
+Donations support the sustained development of polybar.
+We accept donations through [our open collective page](
+https://opencollective.com/polybar).
+This can be either a one time or a recurring donation.
+
+Our [blog post](https://polybar.github.io/2020/12/21/Polybar-Funding.html) lays
+out the why and how behind our decision to accept donations.
--- /dev/null
+The MIT License (MIT)
+Copyright (c) 2016 Michael Carlberg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+<p align="center">
+ <img src="doc/_static/banner.png#gh-light-mode-only" alt="Polybar">
+ <img src="doc/_static/banner-dark-mode.png#gh-dark-mode-only" alt="Polybar">
+</p>
+
+<p align="center">
+A fast and easy-to-use tool for creating status bars.
+</p>
+
+<p align="center">
+<a href="https://github.com/polybar/polybar/releases"><img src="https://img.shields.io/github/release/polybar/polybar.svg"></a>
+<a href="https://github.com/polybar/polybar/releases"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/polybar/polybar/total" /></a>
+<a href="https://github.com/polybar/polybar/actions?query=workflow%3ACI"><img src="https://github.com/polybar/polybar/workflows/CI/badge.svg"></a>
+<a href="https://github.com/polybar/polybar/actions?query=workflow%3A%22Release+Workflow%22"><img src="https://github.com/polybar/polybar/workflows/Release%20Workflow/badge.svg?branch=master"></a>
+<a href="https://polybar.readthedocs.io"><img src="https://readthedocs.org/projects/polybar/badge/?version=latest"></a>
+<a href="https://gitter.im/polybar/polybar"><img src="https://badges.gitter.im/polybar/polybar.svg"></a>
+<a href="https://codecov.io/gh/polybar/polybar/branch/master"><img src="https://codecov.io/gh/polybar/polybar/branch/master/graph/badge.svg"></a>
+<a href="https://github.com/polybar/polybar/blob/master/LICENSE"><img src="https://img.shields.io/github/license/polybar/polybar.svg"></a>
+<a href="https://www.codetriage.com/polybar/polybar"><img src="https://www.codetriage.com/polybar/polybar/badges/users.svg"></a>
+<a href="https://opencollective.com/polybar"><img src="https://opencollective.com/polybar/tiers/badge.svg"></a>
+</p>
+
+**[Documentation](https://github.com/polybar/polybar/wiki/) | [Installation](#installation) | [Support](SUPPORT.md) | [Donate](#donations)**
+
+**Polybar** aims to help users build beautiful and highly customizable status bars
+for their desktop environment, without the need of having a black belt in shell scripting.
+
+
+
+## Table of Contents
+
+* [Introduction](#introduction)
+* [Getting Help](#getting-help)
+* [Contributing](#contributing)
+* [Getting started](#getting-started)
+ * [Installation](#installation)
+ * [First Steps](#first-steps)
+* [Community](#community)
+* [Contributors](#contributors)
+* [Donations](#donations)
+ * [Sponsors](#sponsors)
+ * [Backers](#backers)
+* [License](#license)
+* [Signatures](#signatures)
+
+## Introduction
+
+The main purpose of **Polybar** is to help users create awesome status bars.
+It has built-in functionality to display information about the most commonly used services.
+Some of the services included so far:
+
+- Systray icons
+- Window title
+- Playback controls and status display for [MPD](https://www.musicpd.org/) using [libmpdclient](https://www.musicpd.org/libs/libmpdclient/)
+- [ALSA](https://www.alsa-project.org/main/index.php/Main_Page) and [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) volume controls
+- Workspace and desktop panel for [bspwm](https://github.com/baskerville/bspwm) and [i3](https://github.com/i3/i3)
+- Workspace module for [EWMH compliant](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html#idm140130320786080) window managers
+- Keyboard layout and indicator status
+- CPU and memory load indicator
+- Battery display
+- Network connection details
+- Backlight level
+- Date and time label
+- Time-based shell script execution
+- Command output tailing
+- User-defined menu tree
+- Inter-process messaging
+- And more...
+
+[See the wiki for more details](https://github.com/polybar/polybar/wiki).
+
+## Getting Help
+
+If you find yourself stuck, have a look at our [Support](SUPPORT.md) page for resources where you can find help.
+
+## Contributing
+
+Read our [contributing guidelines](CONTRIBUTING.md) for how to get started with contributing to polybar.
+
+## Getting started
+
+### Installation
+
+<a href="https://repology.org/metapackage/polybar">
+ <img src="https://repology.org/badge/vertical-allrepos/polybar.svg" alt="Packaging status" align="right">
+</a>
+
+Polybar is already available in the package manager for many repositories.
+We list some of the more prominent ones here.
+Also click the [image on the
+right](https://repology.org/project/polybar/versions) to see a more complete
+list of available polybar packages.
+
+If you are using **Debian** (bullseye/11/stable) or later, you can install [polybar](https://tracker.debian.org/pkg/polybar)
+using `sudo apt install polybar`. Newer releases of polybar are sometimes provided in the [backports](https://wiki.debian.org/Backports)
+repository for stable users, you need to enable [backports](https://wiki.debian.org/Backports) and then install using
+`sudo apt -t bullseye-backports install polybar`.
+
+If you are using **Ubuntu** 20.10 (Groovy Gorilla) or later, you can install polybar
+using `sudo apt install polybar`.
+
+If you are using **Arch Linux**, you can install
+[polybar](https://archlinux.org/packages/community/x86_64/polybar/) to get the
+latest stable release using `sudo pacman -S polybar`. The latest unstable
+changes are also available in the
+[`polybar-git`](https://aur.archlinux.org/packages/polybar-git) package in the
+AUR.
+
+If you are using **Manjaro**, you can install [polybar](https://software.manjaro.org/package/polybar) to get the latest stable release using `sudo pacman -S polybar`.
+
+If you are using **Void Linux**, you can install [polybar](https://github.com/void-linux/void-packages/blob/master/srcpkgs/polybar/template) using `xbps-install -S polybar`.
+
+If you are using **NixOS**, polybar is available in both the stable and unstable channels and can be installed with the command `nix-env -iA nixos.polybar`.
+
+If you are using **Slackware**, polybar is available from the [SlackBuilds](https://slackbuilds.org/repository/14.2/desktop/polybar/) repository.
+
+If you are using **Source Mage GNU/Linux**, polybar spell is available in test grimoire and can be installed via `cast polybar`.
+
+If you are using **openSUSE Leap** or **openSUSE Tumbleweed**, polybar is available from the
+[official
+repositories](https://build.opensuse.org/package/show/X11:Utilities/polybar)
+and can be installed via `zypper install polybar`.
+The package is available for openSUSE Leap 15.3 and above.
+
+If you are using **FreeBSD**, [polybar](https://www.freshports.org/x11/polybar) can be installed using `pkg install polybar`. Make sure you are using the `latest` package branch.
+
+If you are using **Gentoo**, both release and git-master versions are available in the [main](https://packages.gentoo.org/packages/x11-misc/polybar) repository.
+
+If you are using **Fedora**, you can install [polybar](https://src.fedoraproject.org/rpms/polybar) using `sudo dnf install polybar`.
+
+If you can't find your distro here, you will have to [build from source](https://github.com/polybar/polybar/wiki/Compiling).
+
+### First Steps
+[See the wiki for details on how to run and configure polybar](https://github.com/polybar/polybar/wiki).
+
+## Community
+Want to get in touch?
+
+* Visit our [Discussion page](https://github.com/polybar/polybar/discussions)
+* Join our Gitter room at [`gitter.im/polybar/polybar`](https://gitter.im/polybar/polybar)
+* We have our own subreddit at [`r/polybar`](https://www.reddit.com/r/polybar)
+* Chat with us in the `#polybar` IRC channel on the [`irc.libera.chat:6697`](https://libera.chat/) server
+
+## Contributors
+
+### Maintainers
+* Patrick Ziegler [**@patrick96**](https://github.com/patrick96)
+
+### Owner
+* Michael Carlberg [**@jaagr**](https://github.com/jaagr/)
+
+### Former Maintainers
+* [**@Lomadriel**](https://github.com/Lomadriel)
+* [**@NBonaparte**](https://github.com/NBonaparte)
+* Chase Geigle [**@skystrife**](https://github.com/skystrife)
+
+### Logo Design by
+* [**@Tobaloidee**](https://github.com/Tobaloidee)
+
+
+### [All Contributors](https://github.com/polybar/polybar/graphs/contributors)
+
+## Donations
+
+Polybar accepts donations through [open collective](https://opencollective.com/polybar).
+
+[Become a backer](https://opencollective.com/polybar) and support polybar!
+### Sponsors
+
+<a href="https://opencollective.com/polybar/sponsor/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/0/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/1/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/2/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/3/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/4/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/5/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/6/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/7/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/8/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/9/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/9/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/10/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/10/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/11/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/11/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/12/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/12/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/13/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/13/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/14/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/14/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/15/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/15/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/16/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/16/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/17/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/17/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/18/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/18/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/19/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/19/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/20/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/20/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/21/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/21/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/22/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/22/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/23/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/23/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/24/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/24/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/25/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/25/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/26/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/26/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/27/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/27/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/28/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/28/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/sponsor/29/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/sponsor/29/avatar.svg?requireActive=false"></a>
+
+### Backers
+
+<a href="https://opencollective.com/polybar/backer/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/0/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/1/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/2/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/3/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/4/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/5/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/6/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/7/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/8/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/9/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/9/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/10/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/10/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/11/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/11/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/12/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/12/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/13/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/13/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/14/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/14/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/15/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/15/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/16/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/16/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/17/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/17/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/18/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/18/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/19/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/19/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/20/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/20/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/21/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/21/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/22/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/22/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/23/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/23/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/24/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/24/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/25/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/25/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/26/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/26/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/27/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/27/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/28/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/28/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/polybar/backer/29/website?requireActive=false" target="_blank"><img src="https://opencollective.com/polybar/backer/29/avatar.svg?requireActive=false"></a>
+
+## License
+
+Polybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/polybar/polybar/blob/master/LICENSE).
+
+## Signatures
+
+Release archives and tags are signed by a maintainer using GPG. Currently
+everything is signed by [Patrick Ziegler](https://www.patrickziegler.ch/gpg)
+with fingerprint `1D5791352D51A228D4DDDBA4521E5E03AEBCA1A7`
--- /dev/null
+# Getting Help
+
+If you need help or troubleshooting tips or just have a question:
+
+* If applicable, go through our [debugging guide](https://github.com/polybar/polybar/wiki/Debugging-your-Config).
+* Read the [Known Issues page](https://github.com/polybar/polybar/wiki/Known-Issues), maybe others had the same issue before.
+* Read the [Wiki page](https://github.com/polybar/polybar/wiki) for the thing you have problems with.
+* Ask your question on [GitHub Discussions](https://github.com/polybar/polybar/discussions)
+* Join our Gitter room at [`gitter.im/polybar/polybar`](https://gitter.im/polybar/polybar)
+* Ask in our reddit community at [`r/polybar`](https://www.reddit.com/r/polybar)
+* Join the official IRC channel `#polybar` on the [`irc.libera.chat:6697`](https://libera.chat/) network. This is IRC, you will need to be connected to receive answers.
+* Ask on [Unix & Linux StackExchange](https://unix.stackexchange.com/). Though not all questions may be suited over there, make sure you're [on topic](https://unix.stackexchange.com/help/on-topic).
+
+**Do not** use the GitHub issue tracker to ask for help or if you have questions, it is meant for bug reports.
+Issues will be closed and you will be referred to the above resources.
+
+## Asking Quality Questions
+
+Spending some time to precisely frame your question will save a lot of time.
+You will better understand your problem and may be able to solve it yourself
+and other will be able to better understand what you are asking.
+Here are some tips:
+
+* Be explicit and precise:
+ * What are you trying to achieve?
+ * What problems have you encountered while trying to achieve this?
+ * What is stopping you from overcoming these problems?
+* If a problem is difficult to describe, screenshots can help. Do not make
+ screenshots of your config file or error messages, copy-paste them as text.
+* Provide as much context as possible. In most cases this includes at least the following:
+ * Window Manager
+ * Polybar version
+ * Relevant portions of your config file. If you are not sure what is relevant, provide the whole thing.
+ * How you start polybar
--- /dev/null
+#!/usr/bin/env bash
+
+readonly SELF=${0##*/}
+declare -rA COLORS=(
+ [RED]=$'\033[0;31m'
+ [GREEN]=$'\033[0;32m'
+ [BLUE]=$'\033[0;34m'
+ [PURPLE]=$'\033[0;35m'
+ [CYAN]=$'\033[0;36m'
+ [WHITE]=$'\033[0;37m'
+ [YELLOW]=$'\033[0;33m'
+ [BOLD]=$'\033[1m'
+ [OFF]=$'\033[0m'
+)
+
+usage() {
+ echo "
+ Builds and installs polybar.
+
+ ${COLORS[GREEN]}${COLORS[BOLD]}Usage:${COLORS[OFF]}
+ ${COLORS[CYAN]}${SELF}${COLORS[OFF]} [options]
+
+ ${COLORS[GREEN]}${COLORS[BOLD]}Options:${COLORS[OFF]}
+ ${COLORS[GREEN]}-3, --i3${COLORS[OFF]}
+ Include support for internal/i3 (requires i3); disabled by default.
+ ${COLORS[GREEN]}-a, --alsa${COLORS[OFF]}
+ Include support for internal/alsa (requires alsalib); disabled by default.
+ ${COLORS[GREEN]}-p, --pulseaudio${COLORS[OFF]}
+ Include support for internal/pulseaudio (requires libpulse); disabled by default.
+ ${COLORS[GREEN]}-n, --network${COLORS[OFF]}
+ Include support for internal/network (requires libnl/libiw); disabled by default.
+ ${COLORS[GREEN]}-m, --mpd${COLORS[OFF]}
+ Include support for internal/mpd (requires libmpdclient); disabled by default.
+ ${COLORS[GREEN]}-c, --curl${COLORS[OFF]}
+ Include support for internal/github (requires libcurl); disabled by default.
+ ${COLORS[GREEN]}-i, --ipc${COLORS[OFF]}
+ Build polybar-msg used to send ipc messages; disabled by default.
+ ${COLORS[GREEN]}--all-features${COLORS[OFF]}
+ Enable all abovementioned features;
+ equal to -3 -a -p -n -m -c -i
+ ${COLORS[GREEN]}-g, --gcc${COLORS[OFF]}
+ Use GCC even if Clang is installed; disabled by default.
+ ${COLORS[GREEN]}-j, --jobs${COLORS[OFF]}
+ Use make -j to use make jobs with $(nproc) jobs; disabled by default.
+ ${COLORS[GREEN]}-f${COLORS[OFF]}
+ Remove existing build dir; disabled by default.
+ ${COLORS[GREEN]}-I, --no-install${COLORS[OFF]}
+ Do not execute 'sudo make install'; enabled by default.
+ ${COLORS[GREEN]}-A, --auto${COLORS[OFF]}
+ Automatic, non-interactive installation; disabled by default.
+ When set, script defaults options not explicitly set.
+ ${COLORS[GREEN]}-h, --help${COLORS[OFF]}
+ Displays this help.
+"
+}
+
+msg_err() {
+ echo -e "${COLORS[RED]}${COLORS[BOLD]}** ${COLORS[OFF]}$*\n"
+ exit 1
+}
+
+msg() {
+ echo -e "${COLORS[GREEN]}${COLORS[BOLD]}** ${COLORS[OFF]}$*\n"
+}
+
+install() {
+ local p
+
+ if [[ "$AUTO" == ON ]]; then
+ [[ -z "$INSTALL" ]] && INSTALL="ON"
+ fi
+
+ if [[ -z "$INSTALL" ]]; then
+ read -r -p "$(msg "Execute 'sudo make install'? [Y/n] ")" -n 1 p && echo
+ [[ "${p^^}" != "N" ]] && INSTALL="ON" || INSTALL="OFF"
+ fi
+
+ if [[ "$INSTALL" == ON ]]; then
+ sudo make install || msg_err "Failed to install executables..."
+ fi
+}
+
+set_build_opts() {
+ local p
+
+ msg "Setting build options"
+
+ if [[ "$AUTO" == ON ]]; then
+ [[ -z "$USE_GCC" ]] && USE_GCC="OFF"
+ [[ -z "$ENABLE_I3" ]] && ENABLE_I3="OFF"
+ [[ -z "$ENABLE_ALSA" ]] && ENABLE_ALSA="OFF"
+ [[ -z "$ENABLE_PULSEAUDIO" ]] && ENABLE_PULSEAUDIO="OFF"
+ [[ -z "$ENABLE_NETWORK" ]] && ENABLE_NETWORK="OFF"
+ [[ -z "$ENABLE_MPD" ]] && ENABLE_MPD="OFF"
+ [[ -z "$ENABLE_CURL" ]] && ENABLE_CURL="OFF"
+ [[ -z "$ENABLE_IPC_MSG" ]] && ENABLE_IPC_MSG="OFF"
+ [[ -z "$JOB_COUNT" ]] && JOB_COUNT=1
+ fi
+
+ if [[ -z "$USE_GCC" ]]; then
+ read -r -p "$(msg "Use GCC even if Clang is installed ----------------------------- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && USE_GCC="OFF" || USE_GCC="ON"
+ fi
+
+ if [[ -z "$ENABLE_I3" ]]; then
+ read -r -p "$(msg "Include support for \"internal/i3\" (requires i3) ---------------- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_I3="OFF" || ENABLE_I3="ON"
+ fi
+
+ if [[ -z "$ENABLE_ALSA" ]]; then
+ read -r -p "$(msg "Include support for \"internal/alsa\" (requires alsalib) --------- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_ALSA="OFF" || ENABLE_ALSA="ON"
+ fi
+
+ if [[ -z "$ENABLE_PULSEAUDIO" ]]; then
+ read -r -p "$(msg "Include support for \"internal/pulseaudio\" (requires libpulse) -- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_PULSEAUDIO="OFF" || ENABLE_PULSEAUDIO="ON"
+ fi
+
+ if [[ -z "$ENABLE_NETWORK" ]]; then
+ read -r -p "$(msg "Include support for \"internal/network\" (requires libnl/libiw) -- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_NETWORK="OFF" || ENABLE_NETWORK="ON"
+ fi
+
+ if [[ -z "$ENABLE_MPD" ]]; then
+ read -r -p "$(msg "Include support for \"internal/mpd\" (requires libmpdclient) ----- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_MPD="OFF" || ENABLE_MPD="ON"
+ fi
+
+ if [[ -z "$ENABLE_CURL" ]]; then
+ read -r -p "$(msg "Include support for \"internal/github\" (requires libcurl) ------- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_CURL="OFF" || ENABLE_CURL="ON"
+ fi
+
+ if [[ -z "$ENABLE_IPC_MSG" ]]; then
+ read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages ------------------ [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && ENABLE_IPC_MSG="OFF" || ENABLE_IPC_MSG="ON"
+ fi
+
+ if [[ -z "$JOB_COUNT" ]]; then
+ read -r -p "$(msg "Parallelize the build using make -j$(nproc) --------------------------- [y/N]: ")" -n 1 p && echo
+ [[ "${p^^}" != "Y" ]] && JOB_COUNT=1 || JOB_COUNT=$(nproc)
+ fi
+
+
+ CXX="c++"
+
+ if [[ "$USE_GCC" == OFF ]]; then
+ if command -v clang++ >/dev/null; then
+ msg "Using compiler: clang++/clang"
+ CXX="clang++"
+ elif command -v g++ >/dev/null; then
+ msg "Using compiler: g++/gcc"
+ CXX="g++"
+ fi
+ else
+ CXX="g++"
+ fi
+}
+
+main() {
+ [[ -d ./.git ]] && {
+ msg "Fetching submodules"
+ git submodule update --init --recursive || msg_err "Failed to clone submodules"
+ }
+
+ [[ -d ./build ]] && {
+ if [[ "$REMOVE_BUILD_DIR" == ON ]]; then
+ msg "Removing existing build dir (-f)"
+ rm -rf ./build >/dev/null || msg_err "Failed to remove existing build dir"
+ else
+ msg "A build dir already exists (pass -f to replace)"
+ fi
+ }
+
+ mkdir -p ./build || msg_err "Failed to create build dir"
+ cd ./build || msg_err "Failed to enter build dir"
+
+ set_build_opts
+
+ msg "Executing cmake command"
+ cmake \
+ -DCMAKE_CXX_COMPILER="${CXX}" \
+ -DENABLE_ALSA:BOOL="${ENABLE_ALSA}" \
+ -DENABLE_PULSEAUDIO:BOOL="${ENABLE_PULSEAUDIO}"\
+ -DENABLE_I3:BOOL="${ENABLE_I3}" \
+ -DENABLE_MPD:BOOL="${ENABLE_MPD}" \
+ -DENABLE_NETWORK:BOOL="${ENABLE_NETWORK}" \
+ -DENABLE_CURL:BOOL="${ENABLE_CURL}" \
+ -DBUILD_POLYBAR_MSG:BOOL="${ENABLE_IPC_MSG}" \
+ .. || msg_err "Failed to generate build... read output to get a hint of what went wrong"
+
+ msg "Building project"
+ if [ -z ${JOB_COUNT} ]; then
+ make || msg_err "Failed to build project"
+ else
+ make -j$JOB_COUNT || msg_err "Failed to build project"
+ fi
+ install
+ msg "Build complete!"
+
+ exit 0
+}
+
+
+#################
+###### Entry
+#################
+while [[ "$1" == -* ]]; do
+ case "$1" in
+ -3|--i3)
+ ENABLE_I3=ON; shift ;;
+ -a|--alsa)
+ ENABLE_ALSA=ON; shift ;;
+ -p|--pulseaudio)
+ ENABLE_PULSEAUDIO=ON; shift ;;
+ -n|--network)
+ ENABLE_NETWORK=ON; shift ;;
+ -m|--mpd)
+ ENABLE_MPD=ON; shift ;;
+ -c|--curl)
+ ENABLE_CURL=ON; shift ;;
+ -i|--ipc)
+ ENABLE_IPC_MSG=ON; shift ;;
+ --all-features)
+ ENABLE_I3=ON
+ ENABLE_ALSA=ON
+ ENABLE_PULSEAUDIO=ON
+ ENABLE_NETWORK=ON
+ ENABLE_MPD=ON
+ ENABLE_CURL=ON
+ ENABLE_IPC_MSG=ON
+ shift ;;
+
+ -g|--gcc)
+ USE_GCC=ON; shift ;;
+ -j|--jobs)
+ JOB_COUNT=$(nproc); shift ;;
+ -f)
+ REMOVE_BUILD_DIR=ON; shift ;;
+ -I|--no-install)
+ INSTALL=OFF; shift ;;
+ -A|--auto)
+ AUTO=ON; shift ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ --) shift; break ;;
+ *)
+ usage
+ [[ "$1" =~ ^-[0-9a-zA-Z]{2,}$ ]] && msg_err "don't combine options: ie do [-c -i] instead of [-ci]" || msg_err "unknown option [$1]"
+ ;;
+ esac
+done
+
+main
+
--- /dev/null
+#
+# Core setup
+#
+option(DISABLE_ALL "Set this to ON disable all targets. Individual targets can be enabled explicitly." OFF)
+
+# If all targets are disabled, we set the default value for options that are on
+# by default to OFF
+if (DISABLE_ALL)
+ set(DEFAULT_ON OFF)
+else()
+ set(DEFAULT_ON ON)
+endif()
+
+option(BUILD_POLYBAR "Build the main polybar executable" ${DEFAULT_ON})
+option(BUILD_POLYBAR_MSG "Build polybar-msg" ${DEFAULT_ON})
+option(BUILD_TESTS "Build testsuite" OFF)
+option(BUILD_DOC "Build documentation" ${DEFAULT_ON})
+option(BUILD_CONFIG "Generate default configuration" ${DEFAULT_ON})
+option(BUILD_SHELL "Generate shell completion files" ${DEFAULT_ON})
+
+include(CMakeDependentOption)
+CMAKE_DEPENDENT_OPTION(BUILD_DOC_HTML "Build HTML documentation" ON "BUILD_DOC" OFF)
+CMAKE_DEPENDENT_OPTION(BUILD_DOC_MAN "Build manpages" ON "BUILD_DOC" OFF)
+
+if (BUILD_POLYBAR OR BUILD_TESTS OR BUILD_POLYBAR_MSG)
+ set(BUILD_LIBPOLY ON)
+else()
+ set(BUILD_LIBPOLY OFF)
+endif()
+
+if (BUILD_POLYBAR OR BUILD_POLYBAR_MSG OR BUILD_TESTS)
+ set(HAS_CXX_COMPILATION ON)
+else()
+ set(HAS_CXX_COMPILATION OFF)
+endif()
+
+# Export compile commands used for custom targets
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Set default build type if not specified
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Release")
+ message_colored(STATUS "No build type specified; using ${CMAKE_BUILD_TYPE}" 33)
+endif()
+string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER)
--- /dev/null
+set(SETTING_ALSA_SOUNDCARD "default"
+ CACHE STRING "Name of the ALSA soundcard driver")
+set(SETTING_BSPWM_SOCKET_PATH "/tmp/bspwm_0_0-socket"
+ CACHE STRING "Path to bspwm socket")
+set(SETTING_BSPWM_STATUS_PREFIX "W"
+ CACHE STRING "Prefix prepended to the bspwm status line")
+set(SETTING_CONNECTION_TEST_IP "8.8.8.8"
+ CACHE STRING "Address to ping when testing network connection")
+set(SETTING_PATH_ADAPTER "/sys/class/power_supply/%adapter%"
+ CACHE STRING "Path to adapter")
+set(SETTING_PATH_BACKLIGHT "/sys/class/backlight/%card%"
+ CACHE STRING "Path to backlight sysfs folder")
+set(SETTING_PATH_BATTERY "/sys/class/power_supply/%battery%"
+ CACHE STRING "Path to battery")
+set(SETTING_PATH_CPU_INFO "/proc/stat"
+ CACHE STRING "Path to file containing cpu info")
+set(SETTING_PATH_MEMORY_INFO "/proc/meminfo"
+ CACHE STRING "Path to file containing memory info")
+set(SETTING_PATH_MESSAGING_FIFO "/tmp/polybar_mqueue.%pid%"
+ CACHE STRING "Path to file containing the current temperature")
+set(SETTING_PATH_TEMPERATURE_INFO "/sys/class/thermal/thermal_zone%zone%/temp"
+ CACHE STRING "Path to file containing the current temperature")
+set(SETTING_PATH_THERMAL_ZONE_WILDCARD "/sys/class/thermal/thermal_zone*"
+ CACHE STRING "Wildcard path to different thermal zones")
--- /dev/null
+#
+# Custom targets
+#
+
+# Target: uninstall {{{
+
+configure_file(
+ ${PROJECT_SOURCE_DIR}/cmake/templates/uninstall.cmake.in
+ ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake
+ ESCAPE_QUOTES @ONLY)
+
+add_custom_target(uninstall
+ COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake)
+
+# }}}
+
+# folders where the clang tools should operate
+set(CLANG_SEARCH_PATHS ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/tests)
+
+# Runs clang-format on all source files
+add_custom_target(
+ clangformat
+ COMMAND ${PROJECT_SOURCE_DIR}/common/file-runner.py
+ --dirs ${CLANG_SEARCH_PATHS}
+ -- clang-format -style=file -i --verbose
+ )
+
+# Dry-runs clang-format on all source files
+# Useful for CI since it will exit with an error code
+add_custom_target(
+ clangformat-dryrun
+ COMMAND ${PROJECT_SOURCE_DIR}/common/file-runner.py
+ --dirs ${CLANG_SEARCH_PATHS}
+ -- clang-format -style=file --dry-run -Werror --verbose
+ )
+
+# Target: codecheck (clang-tidy) {{{
+
+add_custom_target(codecheck)
+add_custom_command(TARGET codecheck
+ COMMAND ${PROJECT_SOURCE_DIR}/common/clang-tidy.sh
+ ${PROJECT_BINARY_DIR} ${CLANG_SEARCH_PATHS})
+
+# }}}
+# Target: codecheck-fix (clang-tidy + clang-format) {{{
+
+add_custom_target(codecheck-fix)
+add_custom_command(TARGET codecheck-fix
+ COMMAND ${PROJECT_SOURCE_DIR}/common/clang-tidy.sh
+ ${PROJECT_BINARY_DIR} -fix ${CLANG_SEARCH_PATHS})
+
+# }}}
+
+# Target: memcheck (valgrind) {{{
+
+add_custom_target(memcheck)
+add_custom_command(TARGET memcheck
+ COMMAND valgrind
+ --leak-check=summary
+ --suppressions=${PROJECT_SOURCE_DIR}/.valgrind-suppressions
+ ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}
+ example --config=${PROJECT_SOURCE_DIR}/doc/config)
+
+add_custom_target(memcheck-full)
+add_custom_command(TARGET memcheck-full
+ COMMAND valgrind
+ --leak-check=full
+ --track-origins=yes
+ --track-fds=yes
+ --suppressions=${PROJECT_SOURCE_DIR}/.valgrind-suppressions
+ ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}
+ example --config=${PROJECT_SOURCE_DIR}/doc/config)
+
+# }}}
--- /dev/null
+#
+# Output build summary
+#
+message(STATUS " Build:")
+message_colored(STATUS " Version: ${APP_VERSION}" "32;1")
+message_colored(STATUS " Type: ${CMAKE_BUILD_TYPE}" "37;2")
+if (HAS_CXX_COMPILATION)
+ message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}} ${cxx_flags_str}" "37;2")
+ message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE_UPPER}} ${cxx_linker_flags_str}" "37;2")
+endif()
+if (BUILD_DOC)
+ message_colored(STATUS " sphinx-build: ${BIN_SPHINX} ${SPHINX_FLAGS}" "37;2")
+endif()
+
+message(STATUS " Install Paths:")
+message_colored(STATUS " PREFIX: ${CMAKE_INSTALL_PREFIX}" "32")
+message_colored(STATUS " BINDIR: ${CMAKE_INSTALL_FULL_BINDIR}" "32")
+message_colored(STATUS " DATADIR: ${CMAKE_INSTALL_FULL_DATADIR}" "32")
+message_colored(STATUS " DOCDIR: ${CMAKE_INSTALL_FULL_DOCDIR}" "32")
+message_colored(STATUS " MANDIR: ${CMAKE_INSTALL_FULL_MANDIR}" "32")
+message_colored(STATUS " SYSCONFDIR: ${CMAKE_INSTALL_FULL_SYSCONFDIR}" "32")
+
+message(STATUS " Targets:")
+colored_option(" polybar" BUILD_POLYBAR)
+colored_option(" polybar-msg" BUILD_POLYBAR_MSG)
+colored_option(" testsuite" BUILD_TESTS)
+colored_option(" documentation" BUILD_DOC)
+colored_option(" html" BUILD_DOC_HTML)
+colored_option(" man" BUILD_DOC_MAN)
+colored_option(" default config" BUILD_CONFIG)
+colored_option(" shell files" BUILD_SHELL)
+
+if (BUILD_LIBPOLY)
+ message(STATUS " Module support:")
+ colored_option(" alsa" ENABLE_ALSA ALSA_VERSION)
+ colored_option(" curl" ENABLE_CURL CURL_VERSION)
+ colored_option(" i3" ENABLE_I3)
+ colored_option(" mpd" ENABLE_MPD MPD_VERSION)
+ colored_option(" network (${WIRELESS_LIB})" ENABLE_NETWORK NETWORK_LIBRARY_VERSION)
+ colored_option(" pulseaudio" ENABLE_PULSEAUDIO PULSEAUDIO_VERSION)
+ colored_option(" xkeyboard" WITH_XKB Xcb_XKB_VERSION)
+
+ message(STATUS " X extensions:")
+ colored_option(" xcb-randr" Xcb_RANDR_FOUND Xcb_RANDR_VERSION)
+ colored_option(" xcb-randr (monitor support)" WITH_XRANDR_MONITORS Xcb_RANDR_VERSION)
+ colored_option(" xcb-composite" Xcb_COMPOSITE_FOUND Xcb_COMPOSITE_VERSION)
+ colored_option(" xcb-xkb" WITH_XKB Xcb_XKB_VERSION)
+ colored_option(" xcb-xrm" WITH_XRM Xcb_XRM_VERSION)
+ colored_option(" xcb-cursor" WITH_XCURSOR Xcb_CURSOR_VERSION)
+
+ message(STATUS " Log options:")
+ colored_option(" Trace logging" DEBUG_LOGGER)
+
+ if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG)
+ message(STATUS " Debug options:")
+ colored_option(" Trace logging (verbose)" DEBUG_LOGGER_VERBOSE)
+ colored_option(" Draw clickable areas" DEBUG_HINTS)
+ colored_option(" Print fc-match details" DEBUG_FONTCONFIG)
+ endif()
+endif()
--- /dev/null
+#
+# Collection of cmake utility functions
+#
+
+# message_colored {{{
+
+function(message_colored message_level text color)
+ string(ASCII 27 esc)
+ message(${message_level} "${esc}[${color}m${text}${esc}[0m")
+endfunction()
+
+# }}}
+# colored_option {{{
+
+function(colored_option text flag)
+ if(ARGC GREATER 2 AND NOT "${${ARGV2}}" STREQUAL "")
+ set(text "${text} (${${ARGV2}})")
+ endif()
+
+ if(${flag})
+ message_colored(STATUS "[X]${text}" "32;1")
+ else()
+ message_colored(STATUS "[ ]${text}" "37;2")
+ endif()
+endfunction()
+
+# }}}
+
+# find_package_impl {{{
+
+# Uses PkgConfig to search for pkg_config_name
+#
+# Defines the following variables:
+# ${find_pkg_name}_FOUND - True if the package has been found
+# ${find_pkg_name}_INCLUDE_DIR - <...>_INCLUDE_DIRS exported by pkg_check_modules
+# ${find_pkg_name}_INCLUDE_DIRS - Same as ${find_pkg_name}_INCLUDE_DIR
+# ${find_pkg_name}_LIBRARY - <...>_LIBRARIES exported by pkg_check_modules
+# ${find_pkg_name}_LIBRARIES - Same as ${find_pkg_name}_LIBRARY
+# ${find_pkg_name}_VERSION - <...>_VERSION exported by pkg_check_modules
+#
+macro(find_package_impl pkg_config_name find_pkg_name header_to_find)
+ find_package(PkgConfig REQUIRED)
+ include(FindPackageHandleStandardArgs)
+
+ pkg_check_modules(PC_${find_pkg_name} REQUIRED ${pkg_config_name})
+
+ if (NOT ${header_to_find} STREQUAL "")
+ find_path(PC_${find_pkg_name}_INCLUDE_DIRS_
+ NAMES "${header_to_find}"
+ HINTS "${PC_${find_pkg_name}_INCLUDE_DIRS}"
+ )
+ set(PC_${find_pkg_name}_INCLUDE_DIRS ${PC_${find_pkg_name}_INCLUDE_DIRS_})
+ endif()
+
+ set(${find_pkg_name}_INCLUDE_DIR ${PC_${find_pkg_name}_INCLUDE_DIRS})
+ set(${find_pkg_name}_INCLUDE_DIRS ${${find_pkg_name}_INCLUDE_DIR})
+ set(${find_pkg_name}_LIBRARY ${PC_${find_pkg_name}_LIBRARIES})
+ set(${find_pkg_name}_VERSION ${PC_${find_pkg_name}_VERSION})
+ set(${find_pkg_name}_LIBRARIES ${${find_pkg_name}_LIBRARY})
+
+ find_package_handle_standard_args(${find_pkg_name}
+ REQUIRED_VARS
+ ${find_pkg_name}_INCLUDE_DIRS
+ ${find_pkg_name}_LIBRARIES
+ VERSION_VAR
+ ${find_pkg_name}_VERSION
+ )
+
+ mark_as_advanced(${find_pkg_name}_INCLUDE_DIR ${find_pkg_name}_LIBRARY)
+endmacro()
+
+# }}}
+# create_imported_target {{{
+function(create_imported_target library_name includes libraries)
+ add_library(${library_name} INTERFACE IMPORTED)
+ set_target_properties(${library_name} PROPERTIES
+ INTERFACE_LINK_LIBRARIES "${libraries}"
+ INTERFACE_INCLUDE_DIRECTORIES "${includes}"
+ )
+endfunction()
+# }}}
+# checklib {{{
+
+function(checklib flag type pkg)
+ if(NOT DEFINED ${flag})
+ if(${type} STREQUAL "cmake")
+ find_package(${pkg} QUIET)
+ set(${flag} ${${pkg}_FOUND} CACHE BOOL "")
+ elseif(${type} STREQUAL "pkg-config")
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(PKG_${flag} QUIET ${pkg})
+ set(${flag} ${PKG_${flag}_FOUND} CACHE BOOL "")
+ elseif(${type} STREQUAL "binary")
+ find_program(BIN_${flag} ${pkg})
+ set(${flag} ${BIN_${flag}} CACHE BOOL "")
+ else()
+ message(FATAL_ERROR "Invalid lookup type '${type}'")
+ endif()
+ mark_as_advanced(${flag})
+ endif()
+endfunction()
+
+function(get_include_dirs output)
+ get_filename_component(generated_sources_dir ${CMAKE_BINARY_DIR}/generated-sources ABSOLUTE)
+ get_filename_component(include_dir ${CMAKE_SOURCE_DIR}/include ABSOLUTE)
+
+ set(${output} ${include_dir} ${generated_sources_dir} PARENT_SCOPE)
+endfunction()
+
+function(get_sources_dirs output)
+ get_filename_component(src_dir ${CMAKE_SOURCE_DIR}/src ABSOLUTE)
+
+ set(${output} ${src_dir} PARENT_SCOPE)
+endfunction()
+
+# }}}
--- /dev/null
+option(ENABLE_CCACHE "Enable ccache support" ON)
+if(ENABLE_CCACHE)
+ find_program(BIN_CCACHE ccache)
+ mark_as_advanced(BIN_CCACHE)
+
+ if(NOT BIN_CCACHE)
+ message_colored(STATUS "Couldn't locate ccache, disabling ccache..." "33")
+ else()
+ # Enable only if the binary is found
+ message_colored(STATUS "Using compiler cache ${BIN_CCACHE}" "32")
+ set(CMAKE_CXX_COMPILER_LAUNCHER ${BIN_CCACHE} CACHE STRING "")
+ endif()
+endif()
+
+option(CXXLIB_CLANG "Link against libc++" OFF)
+option(CXXLIB_GCC "Link against stdlibc++" OFF)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+
+set(POLYBAR_FLAGS "" CACHE STRING "C++ compiler flags used for compiling polybar")
+
+list(APPEND cxx_base -Wall -Wextra -Wpedantic -Wdeprecated-copy-dtor)
+list(APPEND cxx_debug -DDEBUG -g2 -Og)
+list(APPEND cxx_minsizerel "")
+list(APPEND cxx_sanitize ${cxx_debug} -O0 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls)
+list(APPEND cxx_coverage ${cxx_debug} --coverage)
+
+list(APPEND cxx_linker_base "")
+list(APPEND cxx_linker_minsizerel "")
+
+# Compiler flags
+include(CheckCXXCompilerFlag)
+check_cxx_compiler_flag("-Wsuggest-override" HAS_SUGGEST_OVERRIDE)
+if (HAS_SUGGEST_OVERRIDE)
+ list(APPEND cxx_base -Wsuggest-override)
+endif()
+unset(HAS_SUGGEST_OVERRIDE)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ # Need dprintf() for FreeBSD 11.1 and older
+ # libinotify uses c99 extension, so suppress this error
+ list(APPEND cxx_base -D_WITH_DPRINTF -Wno-c99-extensions)
+ # Ensures that libraries from dependencies in LOCALBASE are used
+ list(APPEND cxx_linker_base -L/usr/local/lib)
+endif()
+
+# Check compiler
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+ list(APPEND cxx_base -Wno-error=parentheses-equality -Wno-zero-length-array)
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "3.4.0")
+ message_colored(FATAL_ERROR "Compiler not supported (Requires clang-3.4+ or gcc-5.1+)" 31)
+ else()
+ message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 32)
+ endif()
+elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
+ list(APPEND cxx_minsizerel -fdata-sections -ffunction-sections -flto)
+ list(APPEND cxx_linker_minsizerel -Wl,--gc-sections)
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.1.0")
+ message_colored(FATAL_ERROR "Compiler not supported (Requires clang-3.4+ or gcc-5.1+)" 31)
+ else()
+ message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 32)
+ endif()
+else()
+ message_colored(WARNING "Using unsupported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} !" 31)
+endif()
+
+# Set compiler and linker flags for preferred C++ library
+if(CXXLIB_CLANG)
+ message_colored(STATUS "Linking against libc++" 32)
+ list(APPEND cxx_base -stdlib=libc++)
+ list(APPEND cxx_linker_base -lc++ -lc++abi)
+elseif(CXXLIB_GCC)
+ message_colored(STATUS "Linking against libstdc++" 32)
+ list(APPEND cxx_linker_base -lstdc++)
+endif()
+
+SET(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_COVERAGE}")
+SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${CMAKE_EXE_LINKER_FLAGS_COVERAGE}")
+SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} ${CMAKE_SHARED_LINKER_FLAGS_COVERAGE}")
+
+list(APPEND cxx_flags ${cxx_base})
+list(APPEND cxx_linker_flags ${cxx_linker_base})
+
+if (CMAKE_BUILD_TYPE_UPPER STREQUAL "DEBUG")
+ list(APPEND cxx_flags ${cxx_debug})
+elseif (CMAKE_BUILD_TYPE_UPPER STREQUAL "MINSIZEREL")
+ list(APPEND cxx_flags ${cxx_minsizerel})
+ list(APPEND cxx_linker_flags ${cxx_linker_minsizerel})
+elseif (CMAKE_BUILD_TYPE_UPPER STREQUAL "SANITIZE")
+ list(APPEND cxx_flags ${cxx_sanitize})
+elseif (CMAKE_BUILD_TYPE_UPPER STREQUAL "COVERAGE")
+ list(APPEND cxx_flags ${cxx_coverage})
+endif()
+
+string(REPLACE " " ";" polybar_flags_list "${POLYBAR_FLAGS}")
+list(APPEND cxx_flags ${polybar_flags_list})
+
+list(APPEND cxx_linker_flags ${cxx_flags})
+
+string(REPLACE ";" " " cxx_flags_str "${cxx_flags}")
+string(REPLACE ";" " " cxx_linker_flags_str "${cxx_linker_flags}")
+
+# TODO use target_link_options once min cmake version is >= 3.13
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${cxx_linker_flags_str}")
--- /dev/null
+# Sets up options and dependencies for libpoly
+
+
+# Automatically enable all optional dependencies that are available on the machine
+checklib(ENABLE_ALSA "pkg-config" alsa)
+checklib(ENABLE_CURL "pkg-config" libcurl)
+checklib(ENABLE_I3 "binary" i3)
+checklib(ENABLE_MPD "pkg-config" libmpdclient)
+checklib(WITH_LIBNL "pkg-config" libnl-genl-3.0)
+if(WITH_LIBNL)
+ checklib(ENABLE_NETWORK "pkg-config" libnl-genl-3.0)
+ set(WIRELESS_LIB "libnl")
+else()
+ checklib(ENABLE_NETWORK "cmake" Libiw)
+ set(WIRELESS_LIB "wireless-tools")
+endif()
+checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse)
+checklib(WITH_XKB "pkg-config" xcb-xkb)
+checklib(WITH_XRM "pkg-config" xcb-xrm)
+checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12")
+checklib(WITH_XCURSOR "pkg-config" "xcb-cursor")
+
+option(ENABLE_ALSA "Enable alsa support" ON)
+option(ENABLE_CURL "Enable curl support" ON)
+option(ENABLE_I3 "Enable i3 support" ON)
+option(ENABLE_MPD "Enable mpd support" ON)
+option(WITH_LIBNL "Use netlink interface for wireless" ON)
+option(ENABLE_NETWORK "Enable network support" ON)
+option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON)
+option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON)
+
+option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON)
+option(WITH_XKB "xcb-xkb support" ON)
+option(WITH_XRM "xcb-xrm support" ON)
+option(WITH_XCURSOR "xcb-cursor support" ON)
+
+option(DEBUG_LOGGER "Trace logging" ON)
+
+if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG)
+ option(DEBUG_LOGGER_VERBOSE "Trace logging (verbose)" OFF)
+ option(DEBUG_HINTS "Debug clickable areas" OFF)
+ option(DEBUG_WHITESPACE "Debug whitespace" OFF)
+ option(DEBUG_FONTCONFIG "Debug fontconfig" OFF)
+endif()
+
+# Load all packages for enabled components
+
+find_package(Threads REQUIRED)
+find_package(CairoFC REQUIRED)
+
+find_package(LibUV 1.3.0 REQUIRED)
+
+if (ENABLE_ALSA)
+ find_package(ALSA REQUIRED)
+endif()
+
+if (ENABLE_CURL)
+ find_package(CURL REQUIRED)
+endif()
+
+if (ENABLE_MPD)
+ find_package(LibMPDClient REQUIRED)
+ set(MPD_VERSION ${LibMPDClient_VERSION})
+endif()
+
+if (ENABLE_NETWORK)
+ if(WITH_LIBNL)
+ find_package(LibNlGenl3 REQUIRED)
+ set(NETWORK_LIBRARY_VERSION ${LibNlGenl3_VERSION})
+ else()
+ find_package(Libiw REQUIRED)
+ endif()
+endif()
+
+if (ENABLE_PULSEAUDIO)
+ find_package(LibPulse REQUIRED)
+ set(PULSEAUDIO_VERSION ${LibPulse_VERSION})
+endif()
+
+# xcomposite is required
+list(APPEND XORG_EXTENSIONS COMPOSITE)
+if (WITH_XKB)
+ list(APPEND XORG_EXTENSIONS XKB)
+endif()
+if (WITH_XCURSOR)
+ list(APPEND XORG_EXTENSIONS CURSOR)
+endif()
+if (WITH_XRM)
+ list(APPEND XORG_EXTENSIONS XRM)
+endif()
+
+# Set min xrandr version required
+if (WITH_XRANDR_MONITORS)
+ set(XRANDR_VERSION "1.12")
+else ()
+ set(XRANDR_VERSION "")
+endif()
+
+# Randr is required. Searches for randr only because we may do a version check
+find_package(Xcb ${XRANDR_VERSION} REQUIRED COMPONENTS RANDR)
+find_package(Xcb REQUIRED COMPONENTS ${XORG_EXTENSIONS})
+
+# FreeBSD Support
+if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ find_package(LibInotify REQUIRED)
+endif()
--- /dev/null
+# This module defines an imported target `ALSA::ALSA` if alsa is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# ALSA_FOUND
+# ALSA_INCLUDE_DIR
+# ALSA_INCLUDE_DIRS
+# ALSA_LIBRARY
+# ALSA_LIBRARIES
+# ALSA_VERSION
+find_package_impl("alsa" "ALSA" "alsa/asoundlib.h")
+
+if(ALSA_FOUND AND NOT TARGET ALSA::ALSA)
+ create_imported_target("ALSA::ALSA" "${ALSA_INCLUDE_DIR}" "${ALSA_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `CURL::libcurl` if libcurl is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# CURL_FOUND
+# CURL_INCLUDE_DIR
+# CURL_INCLUDE_DIRS
+# CURL_LIBRARY
+# CURL_LIBRARIES
+# CURL_VERSION
+find_package_impl("libcurl" "CURL" "curl/curl.h")
+
+if(CURL_FOUND AND NOT TARGET CURL::libcurl)
+ create_imported_target("CURL::libcurl" "${CURL_INCLUDE_DIR}" "${CURL_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `Cairo::CairoFC` if cairo-fc is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# CairoFC_FOUND
+# CairoFC_INCLUDE_DIR
+# CairoFC_INCLUDE_DIRS
+# CairoFC_LIBRARY
+# CairoFC_LIBRARIES
+# CairoFC_VERSION
+find_package_impl("cairo-fc" "CairoFC" "")
+
+if(CairoFC_FOUND AND NOT TARGET Cairo::CairoFC)
+ create_imported_target("Cairo::CairoFC" "${CairoFC_INCLUDE_DIR}" "${CairoFC_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `LibInotify::LibInotify` if libinotify is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# LibInotify_FOUND
+# LibInotify_INCLUDE_DIR
+# LibInotify_INCLUDE_DIRS
+# LibInotify_LIBRARY
+# LibInotify_LIBRARIES
+# LibInotify_VERSION
+find_package_impl("libinotify" "LibInotify" "")
+
+if(LibInotify_FOUND AND NOT TARGET LibInotify::LibInotify)
+ create_imported_target("LibInotify::LibInotify" "${LibInotify_INCLUDE_DIR}" "${LibInotify_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `LibMPDClient::LibMPDClient` if libmpdclient is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# LibMPDClient_FOUND
+# LibMPDClient_INCLUDE_DIR
+# LibMPDClient_INCLUDE_DIRS
+# LibMPDClient_LIBRARY
+# LibMPDClient_LIBRARIES
+# LibMPDClient_VERSION
+find_package_impl("libmpdclient" "LibMPDClient" "mpd/player.h")
+
+if(LibMPDClient_FOUND AND NOT TARGET LibMPDClient::LibMPDClient)
+ create_imported_target("LibMPDClient::LibMPDClient" "${LibMPDClient_INCLUDE_DIR}" "${LibMPDClient_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `LibNlGenl3::LibNlGenl3` if libnl-genl-3.0 is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# LibNlGenl3_FOUND
+# LibNlGenl3_INCLUDE_DIR
+# LibNlGenl3_INCLUDE_DIRS
+# LibNlGenl3_LIBRARY
+# LibNlGenl3_LIBRARIES
+# LibNlGenl3_VERSION
+find_package_impl("libnl-genl-3.0" "LibNlGenl3" "")
+
+if(LibNlGenl3_FOUND AND NOT TARGET LibNlGenl3::LibNlGenl3)
+ create_imported_target("LibNlGenl3::LibNlGenl3" "${LibNlGenl3_INCLUDE_DIR}" "${LibNlGenl3_LIBRARY}")
+endif()
--- /dev/null
+# This module defines an imported target `LibPulse::LibPulse` if libpulse is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# LibPulse_FOUND
+# LibPulse_INCLUDE_DIR
+# LibPulse_INCLUDE_DIRS
+# LibPulse_LIBRARY
+# LibPulse_LIBRARIES
+# LibPulse_VERSION
+find_package_impl("libpulse" "LibPulse" "pulse/version.h")
+
+if(LibPulse_FOUND AND NOT TARGET LibPulse::LibPulse)
+ create_imported_target("LibPulse::LibPulse" "${LibPulse_INCLUDE_DIR}" "${LibPulse_LIBRARY}")
+endif()
--- /dev/null
+# This module defines
+# LibUV_FOUND
+# LibUV_INCLUDE_DIR
+# LibUV_INCLUDE_DIRS
+# LibUV_LIBRARY
+# LibUV_LIBRARIES
+# LibUV_VERSION
+
+find_package_impl("libuv" "LibUV" "uv.h")
+
+if(LibUV_FOUND AND NOT TARGET LibUV::LibUV)
+ create_imported_target("LibUV::LibUV" "${LibUV_INCLUDE_DIR}" "${LibUV_LIBRARY}")
+endif()
--- /dev/null
+# This module defines
+# LIBIW_FOUND - whether the libiw library was found
+# LIBIW_LIBRARIES - the libiw library
+# LIBIW_INCLUDE_DIR - the include path of the libiw library
+
+find_library(LIBIW_LIBRARY iw)
+
+if(LIBIW_LIBRARY)
+ set(LIBIW_LIBRARIES ${LIBIW_LIBRARY})
+endif(LIBIW_LIBRARY)
+
+find_path(LIBIW_INCLUDE_DIR NAMES iwlib.h)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Libiw DEFAULT_MSG LIBIW_LIBRARY LIBIW_INCLUDE_DIR)
+
+mark_as_advanced(LIBIW_INCLUDE_DIR LIBIW_LIBRARY)
+
+if(Libiw_FOUND AND NOT TARGET Libiw::Libiw)
+ create_imported_target("Libiw::Libiw" "${LIBIW_INCLUDE_DIR}" "${LIBIW_LIBRARIES}")
+endif()
--- /dev/null
+# Loads multiple XCB components
+# Version checks will be made against all requested components
+#
+# For each component ${comp} it does the following:
+#
+# Defines an imported target `Xcb::${comp}` if xcb-${comp} is found
+#
+# Defines the following Variables (see find_package_impl for more info):
+# Xcb_${comp}_FOUND
+# Xcb_${comp}_INCLUDE_DIR
+# Xcb_${comp}_INCLUDE_DIRS
+# Xcb_${comp}_LIBRARY
+# Xcb_${comp}_LIBRARIES
+# Xcb_${comp}_VERSION
+
+# This script only supports the following components of XCB
+set(XCB_known_components
+ XCB
+ RANDR
+ COMPOSITE
+ XKB
+ XRM
+ CURSOR)
+
+# Deducing header from the name of the component
+foreach(_comp ${XCB_known_components})
+ string(TOLOWER "${_comp}" _lc_comp)
+ set(XCB_${_comp}_pkg_config "xcb-${_lc_comp}")
+ set(XCB_${_comp}_header "xcb/${_lc_comp}.h")
+endforeach()
+# Exception cases
+set(XCB_XRM_header "xcb/xcb_xrm.h")
+set(XCB_CURSOR_header "xcb/xcb_cursor.h")
+
+foreach(_comp ${Xcb_FIND_COMPONENTS})
+ if (NOT ${_comp} IN_LIST XCB_known_components)
+ message(FATAL_ERROR "Unknow component \"${_comp}\" of XCB")
+ endif()
+
+ # Forward the different find options set for FindXcb to the individual
+ # components. This is required because find_package_handle_standard_args in
+ # find_package_impl uses these variables for version checks and other things.
+ set(Xcb_${_comp}_FIND_VERSION ${Xcb_FIND_VERSION})
+ set(Xcb_${_comp}_FIND_QUIETLY ${Xcb_FIND_QUIETLY})
+ set(Xcb_${_comp}_FIND_REQUIRED ${Xcb_FIND_REQUIRED})
+
+ # Bypass developer warning that the first argument to
+ # find_package_handle_standard_args (Xcb_...) does not match the name of the
+ # calling package (Xcb)
+ # https://cmake.org/cmake/help/v3.17/module/FindPackageHandleStandardArgs.html
+ set(FPHSA_NAME_MISMATCHED TRUE)
+ find_package_impl(${XCB_${_comp}_pkg_config} "Xcb_${_comp}" "${XCB_${_comp}_header}")
+
+ if(Xcb_${_comp}_FOUND AND NOT TARGET Xcb::${_comp})
+ create_imported_target("Xcb::${_comp}" "${Xcb_${_comp}_INCLUDE_DIRS}" "${Xcb_${_comp}_LIBRARIES}")
+ elseif(NOT Xcb_${_comp}_FOUND AND Xcb_FIND_REQUIRED)
+ message(FATAL_ERROR "Xcb: Required component \"${_comp}\" was not found")
+ endif()
+endforeach()
--- /dev/null
+set(INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+if(NOT EXISTS ${INSTALL_MANIFEST})
+ message(FATAL_ERROR "Cannot find install manifest: ${INSTALL_MANIFEST}")
+endif()
+
+file(READ ${INSTALL_MANIFEST} files)
+string(REGEX REPLACE "\n" ";" files "${files}")
+list(REVERSE files)
+
+foreach(file ${files})
+ message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+ if(EXISTS "$ENV{DESTDIR}${file}")
+ execute_process(COMMAND "@CMAKE_COMMAND@"
+ -E remove "$ENV{DESTDIR}${file}"
+ OUTPUT_VARIABLE rm_out
+ RESULT_VARIABLE rm_retval)
+ if(NOT ${rm_retval} EQUAL 0)
+ message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
+ endif()
+ else()
+ message(STATUS "File $ENV{DESTDIR}${file} does not exist")
+ endif()
+endforeach()
--- /dev/null
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+if [ -d "$BUILD_DIR" ]; then
+ rm -Rf "$BUILD_DIR"
+fi
+
+mkdir -p "${BUILD_DIR}"
+cd "${BUILD_DIR}"
+
+if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then
+ ENABLE_PULSEAUDIO=ON
+ ENABLE_NETWORK=ON
+ ENABLE_MPD=ON
+ ENABLE_CURL=ON
+ ENABLE_ALSA=ON
+ ENABLE_I3=ON
+ WITH_XRM=ON
+ WITH_XKB=ON
+ WITH_XRANDR_MONITORS=ON
+ WITH_XCURSOR=ON
+fi
+
+if [ "$POLYBAR_BUILD_TYPE" = "tests" ]; then
+ BUILD_TESTS=ON
+fi
+
+cmake \
+ -DCMAKE_CXX_COMPILER="${CXX}" \
+ -DPOLYBAR_FLAGS="${CXXFLAGS} -Werror" \
+ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
+ -DBUILD_TESTS:BOOL="${BUILD_TESTS:-OFF}" \
+ -DBUILD_DOC:BOOL=OFF \
+ -DENABLE_PULSEAUDIO="${ENABLE_PULSEAUDIO:-OFF}" \
+ -DENABLE_NETWORK="${ENABLE_NETWORK:-OFF}" \
+ -DENABLE_MPD="${ENABLE_MPD:-OFF}" \
+ -DENABLE_CURL="${ENABLE_CURL:-OFF}" \
+ -DENABLE_ALSA="${ENABLE_ALSA:-OFF}" \
+ -DENABLE_I3="${ENABLE_I3:-OFF}" \
+ -DWITH_XRM="${WITH_XRM:-OFF}" \
+ -DWITH_XKB="${WITH_XKB:-OFF}" \
+ -DWITH_XRANDR_MONITORS="${WITH_XRANDR_MONITORS:-OFF}" \
+ -DWITH_XCURSOR="${WITH_XCURSOR:-OFF}" \
+ ..
--- /dev/null
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+set -x
+
+"${CXX}" --version
+cmake --version
+
+set +x
+
+echo "PATH=${PATH}"
+echo "CXX=${CXX}"
+echo "CXXFLAGS=${CXXFLAGS}"
+echo "LDFLAGS=${LDFLAGS}"
+echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"
+echo "MAKEFLAGS=${MAKEFLAGS}"
+echo "POLYBAR_BUILD_TYPE=${POLYBAR_BUILD_TYPE}"
+echo "CMAKE_BUILD_TYPE=${BUILD_TYPE}"
--- /dev/null
+#!/bin/sh
+
+main() {
+ if [ $# -lt 2 ]; then
+ echo "$0 build_path [-fix] DIR..." 1>&2
+ exit 1
+ fi
+
+ args="-p $1"; shift
+
+ if [ "$1" = "-fix" ]; then
+ args="${args} -fix"; shift
+ fi
+
+ # Search paths
+ search="${*:-.}"
+
+ echo "$0 in $search"
+
+ # shellcheck disable=2086
+ find $search -iname "*.cpp" \
+ -exec printf "\\033[32;1m** \\033[0mProcessing %s\\n" {} \; \
+ -exec clang-tidy $args {} \;
+}
+
+main "$@"
--- /dev/null
+#!/usr/bin/env python3
+
+from pathlib import Path
+import sys
+import os
+import argparse
+import subprocess
+
+EXTENSIONS = set('.' + ext for ext in ['c', 'h', 'cpp', 'hpp', 'inl'])
+
+
+def get_files(dirs):
+ """
+ Generator which yields all files in the given directories with any of the
+ EXTENSIONS.
+ """
+ for dir in dirs:
+ for root, _, files in os.walk(dir):
+ for file in files:
+ path = Path(os.path.join(root, file))
+ if path.suffix in EXTENSIONS:
+ yield path
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="""
+ Run command on all C/C++ source files in the given directories
+ """)
+ parser.add_argument('--dirs', type=Path, nargs='+',
+ help='Directories to search in')
+ parser.add_argument('command', nargs='+',
+ help='Command to which to pass found files')
+ args = parser.parse_args()
+
+ all_files = list(str(file) for file in get_files(args.dirs))
+
+ if not all_files:
+ print("No files found")
+ sys.exit(1)
+
+ result = subprocess.run(args.command + all_files)
+ print(f'Formatted {len(all_files)} files')
+
+ if result.returncode != 0:
+ sys.exit(result.returncode)
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#
+# Bash completion template
+#
+install(FILES polybar
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions
+ COMPONENT tools)
--- /dev/null
+_polybar_default_file() {
+ local suffix=/polybar/config.ini
+
+ local home_path=${XDG_CONFIG_HOME:-$HOME/.config}${suffix}
+ local etc_xdg_path=${XDG_CONFIG_DIRS:-/etc/xdg}${suffix}
+ local etc_path=/etc${suffix}
+
+ if [ -r "$home_path" ]; then
+ echo "$home_path"
+ elif [ -r "$etc_xdg_path" ]; then
+ echo "$etc_xdg_path"
+ elif [ -r "$etc_path" ]; then
+ echo "$etc_path"
+ fi
+}
+
+_polybar_config_file() {
+ for ((i = 0; i < COMP_CWORD; i++)); do
+ case ${COMP_WORDS[i]} in
+ --config)
+ echo "${COMP_WORDS[i + 2]}"
+ return
+ ;;
+ -c)
+ echo "${COMP_WORDS[i + 1]}"
+ return
+ ;;
+ esac
+ done
+
+ _polybar_default_file
+}
+
+_polybar_bars() {
+ local config_file=$(_polybar_config_file)
+
+ if [ -r "$config_file" ]; then
+ sed -nE 's|[[:space:]]*\[bar/([^\]+)\][[:space:]]*$|\1|p' "$config_file"
+ fi
+}
+
+_polybar() {
+ local options='-h --help
+ -v --version
+ -l --log=
+ -q --quiet
+ -c --config=
+ -r --reload
+ -d --dump=
+ -m --list-monitors
+ -M --list-all-monitors
+ -w --print-wmname
+ -s --stdout
+ -p --png='
+
+ local log_levels='error
+ warning
+ notice
+ info
+ trace'
+
+ COMPREPLY=()
+
+ local cur=${COMP_WORDS[COMP_CWORD]}
+ case "$cur" in
+ -*)
+ COMPREPLY=( $(compgen -W "$options" -- "$cur") )
+ ;;
+ *)
+ local prev=${COMP_WORDS[COMP_CWORD - 1]}
+ if [ "$prev" = "=" ]; then
+ prev=${COMP_WORDS[COMP_CWORD - 2]}
+ fi
+
+ case "$prev" in
+ -l|--log)
+ COMPREPLY=( $(compgen -W "$log_levels" -- "$cur") )
+ return 0
+ ;;
+ -c|--config)
+ COMPREPLY=( $(compgen -f "$cur") )
+ return 0
+ ;;
+ -p|--png)
+ COMPREPLY=( $(compgen -f -X "!*.png" "$cur") )
+ return 0
+ ;;
+ -d|--dump)
+ return 0
+ ;;
+ *)
+ COMPREPLY=( $(compgen -W "$options $(_polybar_bars)" -- "$cur") )
+ ;;
+ esac
+ esac
+
+ for ((i = 0; i < ${#COMPREPLY[@]}; i++)); do
+ case ${COMPREPLY[i]} in
+ --*=) ;;
+ -*) COMPREPLY[i]+=" "
+ esac
+ done
+
+ return 0
+}
+
+complete -o filenames -o noquote -o nospace -F _polybar polybar
--- /dev/null
+# Maintainer: Patrick Ziegler <p.ziegler96@gmail.com>
+_pkgname=polybar
+pkgname="${_pkgname}-git"
+pkgver=3.6.3
+pkgrel=1
+pkgdesc="A fast and easy-to-use status bar"
+# aarch64 is not officially supported by polybar, it is only listed here for convenience
+arch=("i686" "x86_64" "aarch64")
+url="https://github.com/polybar/polybar"
+license=("MIT")
+depends=("libuv" "cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm"
+ "xcb-util-cursor" "alsa-lib" "libpulse" "libmpdclient" "libnl"
+ "jsoncpp" "curl")
+optdepends=("i3-wm: i3 module support")
+makedepends=("cmake" "git" "python" "pkg-config" "python-sphinx"
+ "python-packaging" "i3-wm")
+backup=("etc/polybar/config.ini")
+provides=("polybar")
+conflicts=("polybar")
+source=("${_pkgname}::git+${url}.git")
+sha256sums=("SKIP")
+
+pkgver() {
+ git -C "${_pkgname}" describe --long --tags | sed "s/-/.r/;s/-/./g"
+}
+
+prepare() {
+ git -C "${_pkgname}" submodule update --init --recursive
+ mkdir -p "${_pkgname}/build"
+}
+
+build() {
+ cd "${_pkgname}/build" || exit 1
+ # Force cmake to use system python (to detect xcbgen)
+ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ..
+ cmake --build .
+}
+
+package() {
+ cmake --build "${_pkgname}/build" --target install -- DESTDIR="${pkgdir}"
+ install -Dm644 "${_pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${_pkgname}/LICENSE"
+}
--- /dev/null
+"
+" Get the filename of the swap file
+"
+func! ft#cpphpp#GetFilename()
+ let ext = expand('%:e')
+ let root = expand('%:p:r')
+ if (ext == 'cpp')
+ return fnameescape(substitute(root, '\(src/.*/\)\?src/', '\1include/', '') . '.hpp')
+ elseif (ext == 'hpp')
+ return fnameescape(substitute(root, '\(include/.*/\)\?include/', '\1src/', '') . '.cpp')
+ endif
+endfunc
+
+"
+" Swap between source/header using given cmd
+"
+func! ft#cpphpp#Swap(cmd)
+ execute a:cmd . ' ' . ft#cpphpp#GetFilename()
+endfunc
--- /dev/null
+" Swap between source/header
+nnoremap <silent> <leader>af :call ft#cpphpp#Swap('edit')<cr>
+nnoremap <silent> <leader>as :call ft#cpphpp#Swap('new')<cr>
+nnoremap <silent> <leader>av :call ft#cpphpp#Swap('vnew')<cr>
+
+" Code formatting using clang-format
+set formatprg=/usr/bin/clang-format
+nmap <f1> :ClangFormat<cr>
--- /dev/null
+"
+" Enables syntax folding for the configuration file.
+" Removes the need to clutter the file with fold markers.
+"
+" Put the file in $VIM/after/syntax/dosini.vim
+"
+syn region dosiniSection start="^\[" end="\(\n\+\[\)\@=" contains=dosiniLabel,dosiniHeader,dosiniComment keepend fold
+setlocal foldmethod=syntax
+
+" Uncomment to start with folds open
+"setlocal foldlevel=20
--- /dev/null
+#
+# Zsh completion template
+#
+install(FILES _polybar _polybar_msg
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions
+ COMPONENT tools)
--- /dev/null
+#compdef polybar
+#
+# Completion for polybar (https://github.com/polybar/polybar)
+# jaagr <c@rlberg.se>
+#
+_polybar() {
+ local L='-l --log'
+ local Q='-q --quiet'
+ local C='-c --config'
+ local R='-r --reload'
+ local D='-d --dump'
+ local M='-m --list-monitors'
+ local MM='-M --list-all-monitors'
+ local W='-w --print-wmname'
+ local S='-s --stdout'
+
+ _arguments -n : \
+ '(-)'{-h,--help}'[Display help text and exit]' \
+ '(-)'{-v,--version}'[Display build details and exit]' \
+ "($L $Q)"{-l,--log=}'[Set the logging verbosity (default: notice)]:verbosity level:(error warning notice info trace)' \
+ "($L $Q)"{-q,--quiet}'[Be quiet (will override -l)]' \
+ "($C)"{-c,--config=}'[Path to the configuration file]:configuration file:_files' \
+ "($R)"{-r,--reload}'[Reload when the configuration has been modified]' \
+ "($D $R $M $W $S)"{-d,--dump=}'[Print parameter value in bar section and exit]:parameter name' \
+ "($MM $M $D $R $W $S)"{-m,--list-monitors}'[Print list of available monitors (Excluding cloned monitors) and exit]' \
+ "($MM $M $D $R $W $S)"{-M,--list-all-monitors}'[Print list of all available monitors (Including cloned monitors) and exit]' \
+ "($W $R $D $M $S)"{-w,--print-wmname}'[Print the generated WM_NAME and exit]' \
+ "($S)"{-s,--stdout}'[Output data to stdout instead of drawing the X window]' \
+ '::bar name:_polybar_list_names'
+}
+
+(( $+functions[_polybar_default_file] )) || _polybar_default_file() {
+ local suffix=/polybar/config.ini
+
+ local home_path=${XDG_CONFIG_HOME:-$HOME/.config}${suffix}
+ local etc_xdg_path=${XDG_CONFIG_DIRS:-/etc/xdg}${suffix}
+ local etc_path=/etc${suffix}
+
+ if [ -r "$home_path" ]; then
+ echo "$home_path"
+ elif [ -r "$etc_xdg_path" ]; then
+ echo "$etc_xdg_path"
+ elif [ -r "$etc_path" ]; then
+ echo "$etc_path"
+ fi
+}
+
+(( $+functions[_polybar_list_names] )) || _polybar_list_names() {
+ local conf
+ if (( $+opt_args[-c] )); then
+ conf=${(e)opt_args[-c]}
+ elif (( $+opt_args[--config] )); then
+ conf=${(e)opt_args[--config]}
+ else
+ conf=$(_polybar_default_file)
+ fi
+ local names; names=(${(f)"$(sed -nE 's|[[:space:]]*\[bar/([^\]+)\][[:space:]]*$|\1|p' ${conf} 2>/dev/null)"})
+ _describe -t names 'configuration name' names
+}
+
+_polybar "$@"
--- /dev/null
+#compdef polybar-msg
+#
+# Completion for polybar-msg (https://github.com/polybar/polybar)
+# jaagr <c@rlberg.se>
+#
+_polybar_msg() {
+ integer ret=1
+
+ _arguments -n : \
+ '-p[Process id of target instance]:process id:_polybar_msg_pids' \
+ '(-p)1:message type:(action cmd hook)' \
+ '*:: :->args'
+
+ case $state in
+ args)
+ case $words[1] in
+ hook) _arguments ':module name:' ':hook index:'; ret=0 ;;
+ action) _arguments ':action payload:'; ret=0 ;;
+ cmd) _arguments ':command payload:(show hide toggle restart quit)'; ret=0 ;;
+ esac
+ ;;
+ esac
+
+ return $ret
+}
+
+(( $+functions[_polybar_msg_pids] )) || _polybar_msg_pids() {
+ local pids; pids=(${(f)"$(ls -1 /tmp/polybar_mqueue.* | egrep -o '[0-9]+$')"})
+ _describe -t pids 'process id of target instance' pids
+}
+
+_polybar_msg "$@"
--- /dev/null
+set(SPHINX_BUILD "sphinx-build" CACHE STRING "Name/Path of the sphinx-build executable to use.")
+set(SPHINX_FLAGS "" CACHE STRING "Flags to pass to sphinx-build")
+
+find_program(BIN_SPHINX "${SPHINX_BUILD}")
+
+if(NOT BIN_SPHINX)
+ message(FATAL_ERROR "sphinx-build executable '${SPHINX_BUILD}' not found.")
+endif()
+
+separate_arguments(sphinx_flags UNIX_COMMAND "${SPHINX_FLAGS}")
+
+set(doc_path "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# Configures conf.py in the current folder and puts it in the build folder
+configure_file(conf.py conf.py @ONLY)
+
+# We want to run `sphinx-build` with the following builders
+if (BUILD_DOC_HTML)
+ list(APPEND doc_builders "html")
+endif()
+
+if (BUILD_DOC_MAN)
+ list(APPEND doc_builders "man")
+endif()
+
+# Name of all documentation targets
+set(doc_targets "")
+
+foreach(builder ${doc_builders})
+ set(doc_target "doc_${builder}")
+ set(builder_log "builder-${builder}.log")
+ add_custom_target(${doc_target}
+ COMMAND ${BIN_SPHINX}
+ -b ${builder}
+ # conf.py dir
+ -c "${CMAKE_CURRENT_BINARY_DIR}"
+ -d "${CMAKE_CURRENT_BINARY_DIR}/doctrees"
+ -n
+ ${sphinx_flags}
+ # Documentation source file dir
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ # Output dir
+ "${CMAKE_CURRENT_BINARY_DIR}/${builder}" > ${builder_log}
+ COMMENT "sphinx-build ${builder}: see doc/${builder_log}")
+
+ list(APPEND doc_targets ${doc_target})
+endforeach()
+
+# Dummy target that depends on all documentation targets
+add_custom_target(doc ALL DEPENDS ${doc_targets})
+
+if (BUILD_DOC_HTML)
+ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
+ DESTINATION ${CMAKE_INSTALL_DOCDIR}
+ COMPONENT doc
+ PATTERN ".buildinfo" EXCLUDE)
+endif()
+
+install(FILES ${CMAKE_SOURCE_DIR}/CHANGELOG.md
+ DESTINATION ${CMAKE_INSTALL_DOCDIR}
+ COMPONENT doc)
+
+install(FILES ${CMAKE_CURRENT_LIST_DIR}/config.ini
+ DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples
+ COMPONENT doc)
+
+if (BUILD_DOC_MAN)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/polybar.1
+ DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
+ COMPONENT doc)
+
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/polybar-msg.1
+ DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
+ COMPONENT doc)
+
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/polybar.5
+ DESTINATION ${CMAKE_INSTALL_MANDIR}/man5
+ COMPONENT doc)
+endif()
+
--- /dev/null
+Polybar Manual
+==============
+
+The official polybar documentation lives here.
+
+The html documentation and man pages are built automatically when you build with cmake (cmake creates the custom
+target `doc`).
+
+## Preview Locally
+The documentation uses [Sphinx](https://www.sphinx-doc.org/en/stable/) to generate the documentation, so you will need to
+have that installed.
+
+If you build polybar normally while having Sphinx installed during configuration, the documentation will be enabled and
+built as well. Building the documentation can be disabled by passing `-DBUILD_DOC=OFF` to `cmake`.
+
+Once configured, all of the documentation can be generated with `make doc` or use `make doc_html` or `make doc_man` to
+only generate the html documentation or the man pages respectively.
+
+The HTML documentation is in `doc/html/index.html` in your build directory and the man pages are in `doc/man`.
--- /dev/null
+{% extends "!layout.html" -%}
+{# Refer to https://github.com/readthedocs/sphinx_rtd_theme/blob/master/sphinx_rtd_theme/layout.html #}
+
+{%- block document %}
+{#
+ Adds a warning message on the 'latest' version.
+ The warning is only added on readthedocs, if the version is 'latest'.
+ For the 'dev' folder, no warning is shown since the 'latest' version is
+ usually the most up-to-date.
+#}
+{% if READTHEDOCS and polybar_is_latest and not pagename.startswith('dev/') %}
+<div class="admonition important">
+ <p class="admonition-title">Development Version</p>
+ <p>
+ This is the <code class="docutils literal notranslate"><span class="pre">latest</span></code>
+ (unstable) version of this documentation, which may document features
+ not available in or compatible with released stable versions of polybar.
+ </p>
+ <p>
+ See the <a href="https://polybar.readthedocs.io/{{ pagename }}.html">stable version</a> of this documentation page instead.
+ </p>
+</div>
+{% endif %}
+
+{{ super() }}
+{%- endblock %}
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# https://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+from pathlib import Path
+import datetime
+import sphinx
+import packaging.version
+
+from sphinx.util.docfields import Field
+from sphinx.locale import _
+
+
+def get_version(root_path):
+ """
+ Reads the polybar version from the version.txt at the root of the repo.
+ """
+ path = Path(root_path) / "version.txt"
+ with open(path, "r") as f:
+ for line in f.readlines():
+ if not line.startswith("#"):
+ # NB: we can't parse it yet since sphinx could import
+ # pkg_resources later on and it could patch packaging.version
+ return line
+
+ raise RuntimeError("No version found in {}".format(path))
+
+
+sphinx_version = packaging.version.parse(sphinx.__version__)
+
+# -- Project information -----------------------------------------------------
+
+
+project = 'Polybar User Manual'
+copyright = '2016-{}, Michael Carlberg & contributors'.format(
+ datetime.datetime.now().year
+)
+author = 'Polybar Team'
+
+# is whether we are on readthedocs.io
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+
+if on_rtd:
+ # On readthedocs, cmake isn't run so the version string isn't available
+ version = os.environ.get('READTHEDOCS_VERSION', None)
+else:
+ # The short X.Y version
+ version = '@APP_VERSION@'
+
+# The full version, including alpha/beta/rc tags
+release = version
+
+# Set path to documentation
+if on_rtd:
+ # On readthedocs conf.py is already in the doc folder
+ doc_path = '.'
+else:
+ # In all other builds conf.py is configured with cmake and put into the
+ # build folder.
+ doc_path = '@doc_path@'
+
+# The version from the version.txt file. Since we are not always first
+# configured by cmake, we don't necessarily have access to the current version
+# number
+version_txt = get_version(Path(doc_path).absolute().parent)
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "sphinx.ext.extlinks",
+]
+
+if on_rtd:
+ extensions += [
+ # The custom 404 page is only needed
+ "notfound.extension",
+ # The search extension works only on readthedocs
+ # See https://readthedocs-sphinx-search.readthedocs.io
+ "sphinx_search.extension",
+ ]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = [doc_path + '/_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+# language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+highlight_language = 'none'
+
+smartquotes = False
+
+# Quickly link to issues or PRs using :issue:`...` or :pull:`...`
+if sphinx_version >= packaging.version.parse("4.0.0"):
+ extlinks = {
+ "issue": ("https://github.com/polybar/polybar/issues/%s", "#%s"),
+ "pull": ("https://github.com/polybar/polybar/pull/%s", "PR #%s"),
+ }
+else:
+ # Versions before 4.0 (e.g. on readthedocs) do not support %s in the
+ # caption and simply append the value
+ extlinks = {
+ "issue": ("https://github.com/polybar/polybar/issues/%s", "#"),
+ "pull": ("https://github.com/polybar/polybar/pull/%s", "PR #"),
+ }
+
+extlinks_detect_hardcoded_links = True
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+html_theme_options = {}
+
+html_context = {
+ 'polybar_is_latest': version == 'latest',
+}
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+if on_rtd or os.environ.get('USE_RTD_THEME', '0') == '1':
+ html_theme = 'sphinx_rtd_theme'
+ html_theme_options['collapse_navigation'] = False
+ html_theme_options['style_external_links'] = True
+else:
+ html_theme = 'alabaster'
+ html_theme_options['description'] = version
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = [doc_path + '/_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'polybardoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'polybar.tex', 'polybar Documentation',
+ 'Polybar Team', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (
+ 'man/polybar.1', 'polybar',
+ 'A fast and easy-to-use tool status bar', [], 1
+ ),
+ (
+ 'man/polybar-msg.1', 'polybar-msg',
+ 'Send IPC messages to polybar', [], 1
+ ),
+ (
+ 'man/polybar.5', 'polybar',
+ 'configuration file for polybar(1)', [], 5
+ )
+]
+
+man_make_section_directory = False
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'polybar', 'polybar Documentation',
+ author, 'polybar', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The 'versionadded' and 'versionchanged' directives are overridden.
+suppress_warnings = ['app.add_directive']
+
+
+def setup(app):
+
+ # Adds a new directive for document a polybar config setting
+ # Inside goes the description of the option as well as custom roles to
+ # document the type, default value, etc:
+ # .. poly-setting:: NAME
+ #
+ # Description
+ # :type: ...
+ # :default: ...
+ app.add_object_type(
+ 'poly-setting',
+ 'poly-setting',
+ objname='configuration value',
+ indextemplate='pair: %s; configuration value',
+ doc_field_types=[
+ Field('type',
+ label=_("Type"),
+ names=['type'],
+ has_arg=False,
+ ),
+ Field('tags',
+ label=_("Available Tags"),
+ names=['tags'],
+ has_arg=False,
+ ),
+ Field('tokens',
+ label=_("Supported Tokens"),
+ names=['tokens'],
+ has_arg=False,
+ ),
+ Field('default',
+ label=_("Default Value"),
+ names=['default'],
+ has_arg=False,
+ ),
+ ]
+ )
+
+ try:
+ inject_version_directives(app)
+ except NameError:
+ # Function was not defined because sphinx version was too low
+ pass
+
+
+# It is not exactly clear in which version the VersionChange class was
+# introduced, but we know it is available in at least 1.8.5.
+# This feature is mainly needed for the online docs on readthedocs for the docs
+# built from master, documentation built for proper releases should not even
+# mention unreleased changes. Because of that it's not that important that this
+# is added to local builds.
+if sphinx_version >= packaging.version.parse("1.8.5"):
+
+ from typing import List
+ from docutils.nodes import Node
+ from sphinx.domains.changeset import VersionChange
+
+ def inject_version_directives(app):
+ app.add_directive('deprecated', VersionDirective)
+ app.add_directive('versionadded', VersionDirective)
+ app.add_directive('versionchanged', VersionDirective)
+
+ class VersionDirective(VersionChange):
+ """
+ Overwrites the Sphinx directive for versionchanged, versionadded, and
+ deprecated and adds an unreleased tag to versions that are not yet
+ released
+ """
+
+ def run(self) -> List[Node]:
+ directive_version = packaging.version.parse(self.arguments[0])
+ parsed_version_txt = packaging.version.parse(version_txt)
+
+ if directive_version > parsed_version_txt:
+ self.arguments[0] += " (unreleased)"
+
+ return super().run()
--- /dev/null
+;==========================================================
+;
+;
+; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗
+; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝
+; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗
+; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║
+; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
+;
+;
+; To learn more about how to configure Polybar
+; go to https://github.com/polybar/polybar
+;
+; The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+background = #282A2E
+background-alt = #373B41
+foreground = #C5C8C6
+primary = #F0C674
+secondary = #8ABEB7
+alert = #A54242
+disabled = #707880
+
+[bar/example]
+width = 100%
+height = 24pt
+radius = 6
+
+; dpi = 96
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3pt
+
+border-size = 4pt
+border-color = #00000000
+
+padding-left = 0
+padding-right = 1
+
+module-margin = 1
+
+separator = |
+separator-foreground = ${colors.disabled}
+
+font-0 = monospace;2
+
+modules-left = xworkspaces xwindow
+modules-right = filesystem pulseaudio xkeyboard memory cpu wlan eth date
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+enable-ipc = true
+
+; wm-restack = generic
+; wm-restack = bspwm
+; wm-restack = i3
+
+; override-redirect = true
+
+[module/systray]
+type = internal/tray
+
+format-margin = 8pt
+tray-spacing = 16pt
+
+[module/xworkspaces]
+type = internal/xworkspaces
+
+label-active = %name%
+label-active-background = ${colors.background-alt}
+label-active-underline= ${colors.primary}
+label-active-padding = 1
+
+label-occupied = %name%
+label-occupied-padding = 1
+
+label-urgent = %name%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 1
+
+label-empty = %name%
+label-empty-foreground = ${colors.disabled}
+label-empty-padding = 1
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:60:...%
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#F0C674}%mountpoint%%{F-} %percentage_used%%
+
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.disabled}
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume-prefix = "VOL "
+format-volume-prefix-foreground = ${colors.primary}
+format-volume = <label-volume>
+
+label-volume = %percentage%%
+
+label-muted = muted
+label-muted-foreground = ${colors.disabled}
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+label-layout = %layout%
+label-layout-foreground = ${colors.primary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-foreground = ${colors.background}
+label-indicator-background = ${colors.secondary}
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = "RAM "
+format-prefix-foreground = ${colors.primary}
+label = %percentage_used:2%%
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = "CPU "
+format-prefix-foreground = ${colors.primary}
+label = %percentage:2%%
+
+[network-base]
+type = internal/network
+interval = 5
+format-connected = <label-connected>
+format-disconnected = <label-disconnected>
+label-disconnected = %{F#F0C674}%ifname%%{F#707880} disconnected
+
+[module/wlan]
+inherit = network-base
+interface-type = wireless
+label-connected = %{F#F0C674}%ifname%%{F-} %essid% %local_ip%
+
+[module/eth]
+inherit = network-base
+interface-type = wired
+label-connected = %{F#F0C674}%ifname%%{F-} %local_ip%
+
+[module/date]
+type = internal/date
+interval = 1
+
+date = %H:%M
+date-alt = %Y-%m-%d %H:%M:%S
+
+label = %date%
+label-foreground = ${colors.primary}
+
+[settings]
+screenchange-reload = true
+pseudo-transparency = true
+
+; vim:ft=dosini
--- /dev/null
+Getting Started
+===============
+
+Setting up polybar for development is basically the same process as `compiling
+it from source <https://github.com/polybar/polybar/wiki/Compiling>`_.
+However, we recommend using the ``Debug`` or ``Sanitize`` cmake build type when
+configuring the project:
+
+.. code-block:: shell
+
+ cmake -DCMAKE_BUILD_TYPE=Debug ..
+ # Or
+ cmake -DCMAKE_BUILD_TYPE=Sanitize ..
+
+This will give you debug symbols in the executable and the ``Sanitize`` build
+type will also enable the ``AddressSanitizer`` and
+``UndefinedBehaviorSanitizer``, which can give you very useful information
+about crashes and undefined behavior at runtime.
+
+Editors
+-------
+
+Since this is a cmake project, most IDEs will have built-in support or a plugin
+to automatically setup this project.
+
+In addition, the ``cmake`` command creates a ``compile_commands.json`` file in
+the build folder, which can be used by many `language servers
+<https://microsoft.github.io/language-server-protocol/>`_.
+If you are using a C++ language server in your editor, it should be as easy as
+symlinking the ``compile_commands.json`` into the repo root directory:
+
+.. code-block:: shell
+
+ ln -s build/compile_commands.json .
+
+Distro-Specific Setup
+---------------------
+
+The wiki contains user-contributed `setup tips
+<https://github.com/polybar/polybar/wiki/Distro-Specific-Setup>`_ for some
+distros.
--- /dev/null
+Packaging Polybar
+=================
+
+Do you want to package polybar for a distro? Great! Read this page to get
+started.
+
+First Steps
+-----------
+
+Before you get started, have a look at the `Packaging Label
+<https://github.com/polybar/polybar/issues?q=label%3APackaging>`_ on our GitHub
+repo and `Repology <https://repology.org/project/polybar/versions>`_ to see if
+polybar is already packaged for that distro or if there are efforts to do so.
+
+Even if a package already exists, it might still make sense for you to package
+polybar in some cases. Some of these cases are:
+
+- The existing package is out-of-date and the packager is no longer able/willing
+ to continue maintaining the package (or they are simply not reachable
+ anymore).
+- The existing package exist in some non-official repository and you are able to
+ introduce the package into the official package repository for the
+ distro/package manager. For example if there is a PPA providing polybar for
+ Ubuntu and you can add polybar to the official Ubuntu repositories, please do
+ :)
+
+The list above is not exhaustive, if you are unsure, feel free to ask in a new
+GitHub issue or on `Gitter <https://gitter.im/polybar>`_. Please also ask if you
+run into any polybar related issues while packaging.
+
+Packaging
+---------
+
+If you haven't already, carefully read the `Compiling
+<https://github.com/polybar/polybar/wiki/Compiling>`_ wiki page to make sure you
+fully understand all the dependencies involved and how to build polybar
+manually.
+
+We can't really tell you how to create a package for your distro, you need to
+figure that out yourself. But we can give you some guidance on building polybar
+for a package
+
+Gathering the Source Code
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Unless you are creating a package that tracks the ``master`` branch, don't clone
+the git repository. We provide a tarball with all the required source code on
+our `Release Page <https://github.com/polybar/polybar/releases>`_, use that in
+your build.
+
+Configuring and Compiling
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. note::
+
+ Do not use the ``build.sh`` script for building polybar for your package. The
+ usage and flags of the script may change without notice and we don't consider
+ that a breaking change.
+
+You can mostly follow the instructions on the `wiki
+<https://github.com/polybar/polybar/wiki/Compiling#compiling>`_ for how to
+compile polybar, but there are some additional ``cmake`` arguments you might
+want to use:
+
+- ``-DCMAKE_BUILD_TYPE=Release``: As of writing this is already the default, but
+ use it just to be on the safe side.
+- ``-DCMAKE_INSTALL_PREFIX=/usr``: Without this all the polybar files will be
+ installed under ``/usr/local``. However, for packages it is often recommended
+ they directly install to ``/usr``. So this flag will install polybar to
+ ``/usr/bin/polybar`` instead of ``/usr/local/bin/polybar``. The packaging
+ guidelines for your distro may disagree with this, in that case be sure to
+ follow your distro's guidelines.
+
+Instead of ``sudo make install``, you will most likely want to use
+``DESTDIR=<dir> make install``. That way the files will be installed into
+``<dir>`` instead of your filesystem root.
+
+Finishing Up
+------------
+
+Finally, subscribe to our :issue:`GitHub thread for package maintainers <1971>`
+to get notified about new releases and changes to how polybar is built.
+If you want to, you can also open a PR to add your package to the `Getting
+Started <https://github.com/polybar/polybar#getting-started>`_ section of our
+README.
+
+Thank you very much for maintaining a polybar package! 🎉
--- /dev/null
+Release Workflow
+================
+
+We try to follow `Semantic Versioning <https://semver.org/>`_ in this project.
+Patch releases (e.g. ``3.3.X``) contain only bug fixes. Minor releases (e.g.
+``3.X.0``) can have backwards-compatible features. And major releases (
+``X.0.0``) can introduce incompatible changes.
+
+.. note::
+
+ This document replaces the "`Release Guidelines
+ <https://github.com/polybar/polybar/wiki/Release-Guidelines>`_" on the wiki
+ that we used between 3.2.0 and 3.4.3. Starting with 3.5.0, we will follow
+ the workflow described here to publish releases.
+
+Polybar uses the `OneFlow
+<https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow>`_
+branching model for publishing new releases and introducing hotfixes.
+
+The way we accept code from contributors does not change: Contributors fork
+polybar, commit their changes to a new branch and open a PR to get that branch
+merged.
+After reviewing and approving the changes, a maintainer "merges" the PR.
+"Merging" is done in the GitHub UI by either rebasing or squashing the
+changes.
+Regular merging is disabled because we do not want merge a merge commit for
+every PR.
+
+This document is mainly concerned with how to properly release a new version of
+polybar.
+For that reason this might not be of interest to you, if you are not a
+maintainer, but feel free to read on anyway.
+
+Drafting a new Release
+----------------------
+
+There a two processes for how to draft a new release. The process for major and
+minor versions is the same as they both are "regular" releases.
+Patch releases are triggered by bugfixes that cannot wait until the next regular
+release and have a slightly different workflow.
+
+Regular Releases (Major, Minor)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regular releases are created once we find that ``master`` is in a stable state
+and that there are enough new features to justify a new release.
+A release branch ``release/X.Y.0`` is branched off of a commit on ``master``
+that contains all the features we want in the release, this branch is pushed to
+the official repository.
+For example for version ``3.5.0`` the branch ``release/3.5.0`` would be created:
+
+.. code-block:: shell
+
+ git checkout -b release/3.5.0 <commit>
+
+The release branch should typically only exist for at most a few days.
+
+Hotfix Releases (Patch)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A hotfix release is created whenever we receive a fix for a bug that we believe
+should be released immediately instead of it only being part of the next regular
+release.
+Generally any bugfix qualifies, but it is up to the maintainers to decide
+whether a hotfix release should be created.
+
+The hotfix release branch ``hotfix/X.Y.Z`` is created by branching off at the
+previous release tag (``X.Y.Z-1``).
+For example, if the latest version is ``3.5.2``, the next hotfix will be on
+branch ``hotfix/3.5.3``:
+
+.. code-block:: shell
+
+ git checkout -b hotfix/3.5.3 3.5.2
+
+Since the PRs for such bugfixes are often not created by maintainers, they will
+often not be based on the latest release tag, but just be branched off
+``master`` because contributors don't necessarily know about this branching
+model and also may well not know whether a hotfix will be created for a certain
+bugfix.
+
+.. TODO create contributor page that describes where to branch off. And link to
+ that page.
+
+In case a PR containing a bugfix that is destined for a patch release is not
+branched off the previous release, a maintainer creates the proper release
+branch and cherry-picks the bugfix commits.
+
+.. note::
+
+ Alternatively, the contributor can also ``git rebase --onto`` to base the
+ branch off the previous release tag. However, in most cases it makes sense for
+ a maintainer to create the release branch since they will also need to create
+ a `Release PR`_ for it.
+
+Once the release branch is created and contains the right commits, the
+maintainer should follow `Publishing a new Release`_ to finish this patch
+release.
+
+If multiple bugfixes are submitted in close succession, they can all be
+cherry-picked onto the same patch release branch to not create many individual
+release with only a single fix.
+The maintainer can also decide to leave the release branch for this patch
+release open for a week in order to possibly combine multiple bugfixes into a
+single release.
+
+Publishing a new Release
+------------------------
+
+The process for publishing a release is the same for all release types. It goes
+as follows:
+
+* A `Release PR`_ is created for the release. This PR MUST NOT be merged in
+ GitHub's interface, it is only here for review, merging happens at the
+ commandline.
+* After approval, a signed git tag without message is created locally at the
+ tip of the release branch and pushed:
+
+.. code-block:: shell
+
+ git tag -m "" -s X.Y.Z <release-branch>
+ git push --tags
+
+* A `draft release`_ targetting the new tag is created in GitHub's release
+ publishing tools and published.
+* After the tag is created, the release branch is manually merged into
+ ``master``.
+ Here it is vitally important that the history of the release branch does not
+ change and so we use ``git merge``. We do it manually because using ``git
+ merge`` is disabled on PRs.
+
+.. code-block:: shell
+
+ git checkout master
+ git merge <release-branch>
+ git push origin
+
+* After the tag is created, the release branch can be deleted with ``git push
+ origin :<release-branch>``.
+* Work through the `After-Release Checklist`_.
+
+Here ``<release-branch>`` is either a ``release/X.Y.0`` branch or a
+``hotfix/X.Y.Z`` branch.
+
+Release PR
+~~~~~~~~~~
+
+The final state of the release branch is prepared as a draft PR on
+GitHub.
+That PR is not merged from the GitHub interface though.
+
+The release PR must do the following things:
+
+* Write any missing migration guides for:
+
+ * Deprecated or removed options
+ * New features that it might be worth migrating to
+* Have a release commit at its tip with the message ``Version X.Y.Z`` and the following changes
+
+ * Finalizes the `Changelog`_ for this release
+ * Updates the version number in ``version.txt``
+
+Changelog
+~~~~~~~~~
+
+The ``CHANGELOG.md`` file at the root of the repo should already contain all the
+changes for the upcoming release in a format based on
+`keep a changelog <https://keepachangelog.com/en/1.0.0/>`_.
+For each release those changes should be checked to make sure we did not miss
+anything.
+
+For all releases, a new section of the following form should be created below
+the ``Unreleased`` section:
+
+.. code-block:: md
+
+ ## [X.Y.Z] - YYYY-MM-DD
+
+In addition, the reference link for the release should be added and the
+reference link for the unreleased section should be updated at the bottom of the
+document:
+
+.. code-block:: md
+
+ [Unreleased]: https://github.com/polybar/polybar/compare/X.Y.Z...HEAD
+ [X.Y.Z]: https://github.com/polybar/polybar/releases/tag/X.Y.Z
+
+Since the release tag doesn't exist yet, both of these links will be invalid
+until the release is published.
+
+All changes from the ``Unreleased`` section that apply to this release should be
+moved into the new release section.
+For regular releases this is generally the entire ``Unreleased`` section, while
+for patch releases it will only be a few entries.
+
+The contents of the release section can be copied into the draft release in
+GitHub's release tool with a heading named ``## Changelog``.
+
+Since major releases generally break backwards compatibility in some way, their
+changelog should also prominently feature precisely what breaking changes were
+introduced. If suitable, maybe even separate documentation dedicated to the
+migration should be written.
+
+Draft Release
+~~~~~~~~~~~~~
+
+On `GitHub <https://github.com/polybar/polybar/releases/new>`_ a new release
+should be drafted.
+The release targets the git tag that was just pushed, the name of the release
+and the tag is simply the release number.
+
+The content of the release message should contain the changelog copied from
+``CHANGELOG.md`` under the heading ``## Changelog``.
+In addition using GitHub's "Auto-generate release notes" feature, the list of
+new contributors should be generated and put at the end of the release notes.
+The generated list of PRs can be removed.
+
+For minor and major releases, add a link to the migration guide directly under
+the ``## Changelog`` header:
+
+.. code-block:: markdown
+
+ **[Migration Guide](https://polybar.readthedocs.io/en/stable/migration/X.Y/index.html)**
+
+At the bottom, check the two boxes "Set as the latest release" and "Create a
+discussion for this release" (select the category "Announcements").
+
+After-Release Checklist
+~~~~~~~~~~~~~~~~~~~~~~~
+
+* Verify the release archive (see `Verify Release`_)
+* Update the Wiki
+
+ * Make sure all the new functionality is documented
+ * Mark deprecated features appropriately (see `Deprecations`_)
+ * Remove all "unreleased" notes (not for patch releases)
+* Inform packagers of new release in :issue:`1971`. Mention any dependency
+ changes and any changes to the build workflow. Also mention any new files are
+ created by the installation.
+* Create a PR that updates the AUR ``PKGBUILD`` file for the ``polybar-git``
+ package (push after the release archive is uploaded).
+* Close the `GitHub Milestone <https://github.com/polybar/polybar/milestones>`_
+ for the new release and move open issues (if any) to a later release.
+* Activate the version on `Read the Docs
+ <https://readthedocs.org/projects/polybar/versions/>`_ and deactivate all
+ previous versions for the same minor release (e.g. for 3.5.4, deactivate all
+ other 3.5.X versions).
+
+Verify Release
+~~~~~~~~~~~~~~
+
+Confirm that the release archive was added to the release.
+We have a GitHub action workflow called 'Release Workflow' that on every
+release automatically creates a release archive, uploads it to the release,
+and adds a 'Download' section to the release body.
+If this fails for some reason, it should be triggered manually.
+
+Afterwards, download the archive, verify its hash, and sign it:
+
+.. code-block:: shell
+
+ gpg --armor --detach-sign polybar-X.Y.Z.tar.gz
+
+Finally, upload the generated ``polybar-X.Y.Z.tar.gz.asc`` to the GitHub
+release.
+
+Deprecations
+~~~~~~~~~~~~
+
+If any publicly facing part of polybar is being deprecated, it should be marked
+as such in the code, through warnings/errors in the log, and by comments in the
+wiki. Every deprecated functionality is kept until the next major release and
+removed there, unless it has not been deprecated in a minor release before.
--- /dev/null
+Style Guide
+===========
+
+There is a ``.editorconfig`` and a ``.clang-format`` file in the project root
+that defines some basic guidelines, mainly relating to indentation.
+
+Code Formatting
+---------------
+
+We use ``clang-format`` for code formatting, the style rules are defined in
+``.clang-format``, before submitting a PR, make sure to run the following command
+on all the C++ files you changed:
+
+.. code-block:: shell
+
+ clang-format -style=file -i <FILES>
+
+**Note:** Depending on which file you change, this may produce a lot of changes
+because we have not run ``clang-format`` on all files in the project. This is
+fine.
+
+Indentation
+~~~~~~~~~~~
+
+Files use 2 spaces for indentation.
+
+Line Width
+~~~~~~~~~~
+
+Lines should not be longer than 120 characters, ``clang-format`` will enforce
+this when run. However, try to keep lines under 80 characters if it seems
+reasonable in the current situation.
+
+In some cases it makes sense to have lines longer than 80 characters for
+readability. But long lines can just the same be unreadable, for example if you
+have long if-conditions or use complex expressions as function parameters. Make
+sure you only use longer lines if keeping it under 80 would be less readable.
+
+Comments
+--------
+
+Use Doxygen ``/** */`` comments in front of functions, methods, types, members and
+classes:
+
+.. code-block:: cpp
+
+ /**
+ * @brief Generates a config object from a config file
+ *
+ * For modularity the parsing and storing of the config is separated
+ */
+ class config_parser {
+ ...
+ /**
+ * @brief Is used to resolve ${root...} references
+ */
+ string m_barname;
+ ...
+ }
+
+For all other comments use ``//`` for single-line and ``/* */`` for multi-line comments.
+
+Your comments should describe the intent and purpose of your code, not necessarily what it does.
+
+Header Files
+------------
+
+Header files should end in ``.hpp``.
+
+We use pragmas instead of include guards to guarantee header files are included
+only once:
+
+.. code-block:: cpp
+
+ #pragma once
--- /dev/null
+Testing
+=======
+
+Polybar uses `googletest <https://google.github.io/googletest/>`_ as its
+testing and mocking framework.
+Tests live in the ``tests/`` directory; they can be enabled during cmake with
+``-DBUILD_TESTS=ON`` and compiled with ``make all_unit_tests``.
+
+Each test gets its own executable in ``build/tests``, which can be executed to run
+a specific test.
+
+Running all tests is preferably done with the following command:
+
+.. code-block:: shell
+
+ make check
+
+This runs all available tests and prints the output in color for failed tests
+only.
+
+Adding New Tests
+----------------
+
+All new tests need to be added to the ``tests/CMakeLists.txt`` file. Have a look
+at the other unit tests in ``tests/unit_tests`` to see how to write tests for your
+code.
--- /dev/null
+Polybar Documentation
+=====================
+
+.. note:: This is still very much a work-in-progress. Most information is still
+ to be found on our `GitHub Wiki <https://github.com/polybar/polybar/wiki>`_.
+ We will migrate the wiki content step-by-step.
+
+
+Welcome to the official polybar documentation.
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Content:
+
+ user/actions
+ user/ipc
+ user/modules/index
+ user/fonts/index
+ user/default-config
+ migration/index
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Manual Pages:
+
+ man/polybar.1
+ man/polybar-msg.1
+ man/polybar.5
+
+.. toctree::
+ :maxdepth: 1
+ :caption: For Contributors:
+
+ dev/packaging
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Developer Documentation:
+
+ dev/getting-started
+ dev/style-guide
+ dev/testing
+ dev/release-workflow
+
+Getting Help
+============
+
+* `Polybar Wiki <https://github.com/polybar/polybar/wiki>`_
+* `Gitter <https://gitter.im/polybar/polybar>`_
+* `/r/polybar <https://reddit.com/r/polybar>`_ on reddit
+* ``#polybar`` on `irc.libera.chat:6697 <https://libera.chat/>`_
+
--- /dev/null
+polybar-msg(1)
+==============
+
+SYNOPSIS
+--------
+| **polybar-msg** [*OPTIONS*] **action** *action-string*
+| **polybar-msg** [*OPTIONS*] **action** *module* *action* [*data*]
+| **polybar-msg** [*OPTIONS*] **cmd** *command*
+
+DESCRIPTION
+-----------
+Polybar allows external control through *actions* and *commands*.
+Actions control individual modules and commands control the bar itself.
+
+The full IPC documentation is linked at the end of this document.
+
+The available actions depend on the target module.
+For actions, the payload is either a single action string or the module name,
+the action name, and the optional data string specified separately.
+
+In order for **polybar-msg** being able to send a message to a running
+**polybar** process, the bar must have IPC enabled and both **polybar-msg** and
+**polybar** must run under the same user.
+
+OPTIONS
+-------
+
+.. program:: polybar-msg
+
+.. option:: -h, --help
+
+ Display help text and exit
+
+.. option:: -p PID
+
+ Send message only to **polybar** process running under the given process ID.
+ If not specified, the message is sent to all running **polybar** processes.
+
+EXAMPLES
+--------
+
+**polybar-msg** **cmd** *quit*
+ Terminate all running **polybar** instances.
+
+**polybar-msg** **action** *mymodule* *module_hide*
+
+**polybar-msg** **action** "*#mymodule.module_hide*"
+ Hide the module named *mymodule*.
+ The first variant specifies the module and action names separately, the second uses an action string.
+
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
+| Contributors can be listed on GitHub.
+
+REPORTING BUGS
+--------------
+Report issues on GitHub <https://github.com/polybar/polybar>
+
+SEE ALSO
+--------
+.. only:: man
+
+ :manpage:`polybar`\(1),
+ :manpage:`polybar`\(5)
+
+ | IPC documentation: <https://polybar.rtfd.org/en/stable/user/ipc.html>
+
+
+.. only:: not man
+
+ :doc:`polybar.1`,
+ :doc:`polybar.5`
+
+ :doc:`/user/ipc`
--- /dev/null
+polybar(1)
+==========
+
+SYNOPSIS
+--------
+**polybar** [*OPTIONS*]... [*BAR*]
+
+DESCRIPTION
+-----------
+Polybar aims to help users build beautiful and highly customizable status bars for their desktop environment, without the need of having a black belt in shell scripting.
+If the *BAR* argument is not provided and the configuration file only contains one bar definition, polybar will display this bar.
+
+OPTIONS
+-------
+
+.. program:: polybar
+
+.. option:: -h, --help
+
+ Display help text and exit
+
+.. option:: -v, --version
+
+ Display build details and exit
+.. option:: -l, --log=LEVEL
+
+ | Set the logging verbosity (default: **notice**)
+ | *LEVEL* is one of: error, warning, notice, info, trace
+.. option:: -q, --quiet
+
+ Be quiet (will override -l)
+.. option:: -c, --config=FILE
+
+ Specify the path to the configuration file. By default, the configuration file is loaded from:
+
+ * ``$XDG_CONFIG_HOME/polybar/config``
+ * ``$XDG_CONFIG_HOME/polybar/config.ini``
+ * ``$HOME/.config/polybar/config``
+ * ``$HOME/.config/polybar/config.ini``
+ * ``$XDG_CONFIG_DIRS/polybar/config.ini``
+ * ``/etc/xdg/polybar/config.ini`` (only if ``XDG_CONFIG_DIRS`` is not set)
+ * ``/etc/polybar/config.ini``
+.. option:: -r, --reload
+
+ Reload the application when the config file has been modified
+.. option:: -d, --dump=PARAM
+
+ Print the value of the specified parameter *PARAM* in bar section and exit
+.. option:: -m, --list-monitors
+
+ | Print list of available monitors and exit.
+ | If some monitors are cloned, this will exclude all but one of them.
+ | If polybar was compiled with RandR monitor support, only monitors are listed and not physical outputs.
+.. option:: -M, --list-all-monitors
+
+ | Print list of all available monitors and exit.
+ | This includes cloned monitors as well as both physical outputs and RandR monitors (if supported).
+ | Only the names listed here can be used as monitor names in polybar.
+.. option:: -w, --print-wmname
+
+ Print the generated *WM_NAME* and exit
+.. option:: -s, --stdout
+
+ Output the data to stdout instead of drawing it to the X window
+.. option:: -p, --png=FILE
+
+ Save png snapshot to *FILE* after running for 3 seconds
+
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
+| Contributors can be listed on GitHub.
+
+REPORTING BUGS
+--------------
+Report issues on GitHub <https://github.com/polybar/polybar>
+
+SEE ALSO
+--------
+.. only:: man
+
+ :manpage:`polybar-msg`\(1),
+ :manpage:`polybar`\(5)
+
+
+.. only:: not man
+
+ :doc:`polybar-msg.1`,
+ :doc:`polybar.5`
+
+| Full documentation at: <https://github.com/polybar/polybar>
+| Project wiki: <https://github.com/polybar/polybar/wiki>
--- /dev/null
+.. highlight:: ini
+
+polybar(5)
+==========
+
+Description
+-----------
+
+The polybar configuration file defines the behavior and look of polybar. It uses
+a variant of the `INI <https://en.wikipedia.org/wiki/INI_file>`_ file format.
+The exact syntax is described below but first a small snippet to get familiar
+with the syntax:
+
+::
+
+ [section_name]
+ ; A comment
+ # Another comment
+
+ background = #ff992a
+ width = 90%
+ monitor = HDMI-0
+
+ screenchange-reload = false
+
+ ; Use double quotes if you want to keep the surrounding space.
+ text = " Some text "
+
+When started ``polybar`` will search for the config file in one of several
+places in the following order:
+
+* If the ``-c`` or ``--config`` command line argument is specified, it will use
+ the path given there.
+* ``$XDG_CONFIG_HOME/polybar/config``
+* ``$XDG_CONFIG_HOME/polybar/config.ini``
+* ``$HOME/.config/polybar/config``
+* ``$HOME/.config/polybar/config.ini``
+* ``$XDG_CONFIG_DIRS/polybar/config.ini``
+* ``/etc/xdg/polybar/config.ini`` (only if ``XDG_CONFIG_DIRS`` is not set)
+* ``/etc/polybar/config.ini``
+
+Syntax
+------
+
+The entire config is line-based so everything is constrained to a single line.
+This means there are no multiline values or other multiline constructs (except
+for sections).
+Each line has one of four types:
+
+* Empty
+* Comment
+* Section Header
+* Key
+
+Spaces at the beginning and end of each line will be ignored.
+
+.. note::
+
+ In this context "spaces" include the regular space character as well as the
+ tab character and any other character for which :manpage:`isspace(3)` returns
+ ``true`` (e.g. ``\r``).
+
+Any line that doesn't fit into one of these four types is a syntax error.
+
+.. note::
+
+ It is recommended that `section header` names and `key` names only use
+ alphanumeric characters as well as dashes (``-``), underscores (``_``) and
+ forward slashes (``/``).
+
+ In practice all characters are allowed except for spaces and any of these:
+ ``"'=;#[](){}:.$\%``
+
+Section Headers
+^^^^^^^^^^^^^^^
+
+Sections are used to group config options together. For example each module is
+defined in its own section.
+
+A section is defined by placing the name of the section in square brackets
+(``[`` and ``]``). For example:
+
+::
+
+ [module/wm]
+
+This declares a section with the name ``module/wm`` and all keys defined after
+this line will belong to that section until a new section is declared.
+
+.. warning::
+ The first non-empty and non-comment line in the main config file must be a
+ section header. It cannot be a key because that key would not belong to any
+ section.
+
+.. note::
+ The following section names are reserved and cannot be used inside the config:
+ ``self``, ``root``, and ``BAR``.
+
+Keys
+^^^^
+
+Keys are defined by assigning a value to a name like this:
+
+
+::
+
+ name = value
+
+This assigns ``value`` to the key ``name`` in whatever section this line is in.
+Key names need to be unique per section.
+If the value is enclosed by double-quotes (``"``), the quotes will be ignored.
+So the following still assigns ``value`` to ``name``:
+
+::
+
+ name = "value"
+
+Spaces around the equal sign are ignored, the following are all equivalent:
+
+::
+
+ name=value
+ name = value
+ name = value
+
+Because spaces at the beginning and end of the line are also ignored, if you
+want your value to begin and/or end with a space, the value needs to be enclosed
+in double-quotes:
+
+::
+
+ name = " value "
+
+Here the value of the ``name`` key has a leading and trailing whitespace.
+
+To treat characters with special meaning as literal characters, you need to
+prepend them with the backslash (``\``) escape character:
+
+::
+
+ name = "value\\value\\value"
+
+Value of this key ``name`` results in ``value\value\value``.
+
+.. note::
+
+ The only character with a special meaning right now is the backslash character
+ (``\``), which serves as the escape character.
+ More will be added in the future.
+
+Empty Lines & Comments
+^^^^^^^^^^^^^^^^^^^^^^
+
+Empty lines and comment lines are ignored when reading the config file, they do
+not affect polybar's behavior. Comment lines start with either the ``;`` or the
+``#`` character.
+
+.. note::
+
+ Inline comments are not supported. For example the following line does not end
+ with a comment, the value of ``name`` is actually set to ``value ; comment``:
+
+ ::
+
+ name = value ; comment
+
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
+| Contributors can be listed on GitHub.
+
+SEE ALSO
+--------
+
+.. only:: man
+
+ :manpage:`polybar`\(1),
+ :manpage:`polybar-msg`\(1)
+
+
+.. only:: not man
+
+ :doc:`polybar.1`,
+ :doc:`polybar-msg.1`
--- /dev/null
+Migrating From Version 3.6 to 3.7
+=================================
+
+Text Module (``custom/text``)
+-----------------------------
+
+Using ``content`` to specify the text of the module is deprecated in favor of
+using the same concepts as all other modules (formats and labels).
+
+For example, the following text module:
+
+.. code-block:: dosini
+
+ [module/text]
+ type = custom/text
+ content = Hello World
+ content-foreground = #ff0000
+
+Should now look like this:
+
+.. code-block:: dosini
+
+ [module/text]
+ type = custom/text
+ label = Hello World
+ label-foreground = #ff0000
+
+Because it is set to its default value, the ``format`` setting can also be
+completely left out.
+
+In general, all properties on ``content`` also apply the same on ``label``
+(e.g. ``background``, ``font``), except for ``offset``,
+``prefix``, ``suffix`` (and their sub-properties).
+Those three properties have to instead be applied to ``format`` (e.g.
+``format-offset``).
+
+System Tray
+-----------
+
+.. include:: tray.rst
--- /dev/null
+..
+ File included directly in other pages describing migrating to the new tray module
+
+Polybar version 3.7 introduced the new tray module and deprecated the legacy
+tray implementation which uses ``tray-position`` to position the tray.
+You should switch over to the tray module as soon as possible.
+
+The legacy tray was configured in the `bar section
+<https://github.com/polybar/polybar/wiki/Configuration#bar-settings>`_, the
+setting for the module live in that module's section of the config file.
+The settings in the bar section don't always directly correspond to an
+equivalent setting in the module section for the new tray module.
+
+The following lists how each old setting in the bar section should be migrated:
+
+``tray-position``
+ The tray is now positioned as a module and so its positioning is done by
+ placing it where you want it to appear in one of the three module lists
+ ``modules-left``, ``modules-center``, ``modules-right``.
+
+``tray-detached``
+ This setting does not have an equivalent, detaching the tray is no longer
+ possible.
+
+``tray-maxsize``
+ The :poly-setting:`tray-size` setting now determines the size of tray icons.
+
+``tray-transparent``
+ Was already deprecated and does not exist in the tray module.
+ Transparency is enabled automatically if a transparent background is used.
+
+``tray-background``
+ Also exists in the module section (see :poly-setting:`tray-background`). Now,
+ the setting only applies to the icons themselves and no longer to the space
+ around them.
+
+``tray-foreground``
+ Also exists in the module section with the same functionality (see
+ :poly-setting:`tray-foreground`).
+
+``tray-offset-x``, ``tray-offset-y``
+ Has no direct equivalent in the module settings. Horizontally, the tray can
+ be moved in the same way other module content can be moved; by reordering the
+ modules or using things like ``format-offset``, ``format-margin``, or
+ ``format-padding``.
+ The tray can't be moved vertically.
+
+ In any case, the tray can no longer be moved outside of the bar window.
+
+``tray-padding``
+ Spacing between tray icons works a bit different now and needs to be
+ completely reconfigured (see :poly-setting:`tray-padding` and
+ :poly-setting:`tray-spacing`).
+
+``tray-scale``
+ No longer exist. The size of the icons is solely determined by
+ :poly-setting:`tray-size`.
--- /dev/null
+Polybar Migration Guides
+========================
+
+Updating polybar to the newest version often requires updating your
+configuration files to use the newest features and replace outdated settings.
+
+Starting from version 3.7, we include a small guide here for how to migrate
+from the previous version.
+If you are upgrading over multiple versions (e.g. from 3.5 to 3.7), also read
+the migration guides for all versions in between.
+
+For migration guides before version 3.7, please look at our `release blog posts
+<https://polybar.github.io/blog/>`_.
+
+When upgrading make sure to run polybar from the terminal and look for errors,
+warnings, and deprecation messages.
+This can save you a lot of issues in the future when deprecated settings and
+features are removed.
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Guides
+
+ 3.7/index
--- /dev/null
+# Installing these packages is only really needed on readthedocs, building
+# locally works as long as sphinx is installed.
+# For local development, you may want to install some of these though.
+sphinx~=7.2.6
+sphinx-rtd-theme~=2.0.0rc2
+sphinx-notfound-page~=1.0.0
+readthedocs-sphinx-search~=0.3.1
--- /dev/null
+Actions
+=======
+
+.. versionadded:: 3.5.0
+
+.. contents:: Table of Contents
+ :local:
+
+"Actions" are used to trigger certain behavior in modules.
+For example, when you click on your volume module (pulseaudio or alsa), polybar
+internally sends an action to that module that tells it to mute/unmute the
+audio.
+
+These actions are not only used internally, but users can also send these
+actions to polybar through `Inter Process Communication
+<https://github.com/polybar/polybar/wiki/Inter-process-messaging>`_ (IPC) to
+trigger certain behavior in polybar modules.
+
+.. _action-string-format:
+
+Action String Format
+--------------------
+
+An action string follows the following format:
+
+::
+
+ #NAME.ACTION[.DATA]
+
+Where ``NAME`` is the name of the target module (not the type!) and ``ACTION``
+is the name of the action in that module. ``DATA`` is optional data attached to
+an action (for example to say which menu level should be opened).
+
+For example the
+`date module <https://github.com/polybar/polybar/wiki/Module:-date>`_ supports
+the ``toggle`` action to toggle between the regular and the alternative time and
+date format.
+If you have the following date module:
+
+.. code-block:: ini
+
+ [module/mydate]
+ type = internal/date
+ ...
+
+The action string for toggling between the date formats would look like this:
+
+::
+
+ #mydate.toggle
+
+Note that we use the name of the module (``mydate``) and not the type.
+
+As an example for an action string with additional data, take the menu module:
+
+.. code-block:: ini
+
+ [module/powermenu]
+ type = custom/menu
+ menu-0-0 = Poweroff
+ menu-0-0-exec = poweroff
+ menu-0-1 = Suspend
+ menu-0-1-exec = systemctl suspend
+
+The action name to open a certain menu level is ``open``, so to open level 0
+(`menu-0`), the action string additionally has the level attached to it:
+
+::
+
+ #powermenu.open.0
+
+Triggering Actions
+------------------
+
+Most modules already use action strings to trigger actions when you click on or
+scroll over a module.
+But in some cases you may want or need to manually send action strings to
+polybar to trigger a certain behavior.
+
+Everywhere where you can specify a command to run on click or scroll, you can
+also specify an action string.
+For example, in the bar section, you can specify a command that is triggered
+when you click anywhere on the bar (where there isn't another click action):
+
+.. code-block:: ini
+
+ [bar/mybar]
+ ...
+ click-left = #mydate.toggle
+ ...
+
+This will then trigger the ``toggle`` action on the ``mydate`` module when you
+click anywhere on the bar.
+
+Similarly, we can use action strings in ``%{A}``
+`formatting tags <https://github.com/polybar/polybar/wiki/Formatting#action-a>`_
+just as we would regular commands:
+
+::
+
+ %{A1:firefox:}%{A3:#mydate.toggle:}Opens firefox on left-click and toggles the
+ date on right-click %{A}%{A}
+
+Finally, polybar's `Inter Process Communication
+<https://github.com/polybar/polybar/wiki/Inter-process-messaging>`_ (IPC) can
+also be used to trigger actions:
+
+.. code-block:: bash
+
+ polybar-msg action "#mydate.toggle"
+
+.. note::
+
+ The quotes around the action string are necessary, otherwise your shell may
+ interpret the ``#`` as the beginning of the comment and ignore the rest of the
+ line.
+
+Available Actions
+-----------------
+
+The following modules have actions available. Most of them are already used by
+the module by default for click and scroll events.
+
+All Modules
+^^^^^^^^^^^
+
+These actions are available to all modules and are prefixed with ``module_``.
+
+:``module_show``, ``module_hide``:
+ Shows/Hides a module. The module is still running in the background when
+ hidden, it is just not drawn. The starting state can be configured with the
+ `hidden` configuration option.
+
+ .. versionadded:: 3.6.0
+
+:``module_toggle``:
+ Toggles the visibility of a module.
+
+ .. versionadded:: 3.6.0
+
+internal/date
+^^^^^^^^^^^^^
+
+:``toggle``:
+ Toggles the date/time format between ``date``/``time`` and
+ ``date-alt``/``time-alt``
+
+internal/alsa
+^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+ Increases/Decreases the volume by ``interval`` percentage points, where
+ ``interval`` is the config setting in the module. Volume changed like this
+ will never go above 100%.
+
+:``toggle``:
+ Toggles between muted and unmuted.
+
+internal/pulseaudio
+^^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+ Increases/Decreases the volume by ``interval`` percentage points, where
+ ``interval`` is the config setting in the module. Volume changed like this
+ will never go above ~153% (if ``use-ui-max`` is set to ``true``) or 100% (if
+ not).
+
+:``toggle``:
+ Toggles between muted and unmuted.
+
+internal/xbacklight
+^^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+ Increases/Decreases screen brightness 5 percentage points.
+
+internal/backlight
+^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+ Increases/Decreases screen brightness 5 percentage points.
+
+internal/xkeyboard
+^^^^^^^^^^^^^^^^^^
+
+:``switch``:
+ Cycles through configured keyboard layouts.
+
+internal/mpd
+^^^^^^^^^^^^
+
+:``play``: Starts playing the current song.
+:``pause``: Pauses the current song.
+:``stop``: Stops playing.
+:``prev``: Starts playing the previous song.
+:``next``: Starts playing the next song.
+:``repeat``: Toggles repeat mode.
+:``single``: Toggles single mode.
+:``random``: Toggles random mode.
+:``consume``: Toggles consume mode.
+:``seek``: *(Has Data)* Seeks inside the current song.
+
+ The data must be of the form ``[+-]N``, where ``N`` is a number
+ between 0 and 100.
+
+ If either ``+`` or ``-`` is used, it will seek forward or backward
+ from the current position by ``N%`` (relative to the length of the
+ song).
+ Otherwise it will seek to ``N%`` of the current song.
+
+internal/xworkspaces
+^^^^^^^^^^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+ The data is the index of the workspace that should be selected.
+:``next``: Switches to the next workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+
+internal/bspwm
+^^^^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+ The data has the form ``N+M``, where ``N`` is the index of the
+ monitor and ``M`` the index of the workspace on that monitor.
+ Both indices are 0-based and correspond to the position the monitor
+ and workspace appear in the output of ``bspc subscribe report``.
+:``next``: Switches to the next workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+
+
+internal/i3
+^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+ The data is the name of the workspace defined in the i3 config.
+:``next``: Switches to the next workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+ affected by the ``pin-workspaces`` setting.
+
+custom/menu
+^^^^^^^^^^^
+
+:``open``: *(Has Data)* Opens the given menu level
+
+ The data is a single number specifying which menu level should be
+ opened.
+:``close``: Closes the menu
+:``exec``: *(Has Data)* Executes the command at the given menu element.
+
+ The data has the form ``N-M`` and the action will execute the command
+ in ``menu-N-M-exec``.
+
+
+.. _actions-ipc:
+
+custom/ipc
+^^^^^^^^^^
+
+.. versionadded:: 3.6.0
+
+:``send``: *(Has Data)* Replace the contents of the module with the data passed in this action.
+:``hook``: *(Has Data)* Trigger the given hook.
+
+ The data is the 0-based index of the hook to trigger.
+:``next``: Switches to the next hook and wrap around when the last hook was displayed.
+:``prev``: Switches to the previous hook and wrap around when the first hook was displayed.
+:``reset``: Reset the module to its startup state: either empty or according to the ``initial`` setting.
+
+
+Deprecated Action Names
+-----------------------
+
+.. deprecated:: 3.5.0
+
+In earlier versions (< 3.5.0) action strings only included information about the
+module type.
+This meant in bars that contained multiple different modules of the same type,
+actions for these modules were sometimes processed by the wrong module with the
+same type.
+
+Since version 3.5.0, this no longer happens. However, this also means we had to
+change what actions are recognized by polybar modules.
+
+If you explicitly use any polybar action names in your config or any of your
+scripts, you are advised to change them, as they may stop working at some point
+in the future.
+For now polybar still supports the old action names, will convert them to the
+appropriate new action name, and will print a warning to help you find old
+action names in your config.
+
+If you use the `menu module
+<https://github.com/polybar/polybar/wiki/Module:-menu>`_, you most likely use
+old action names to open and close the menu (for example ``menu-open-1`` or
+``menu-close``).
+The ``i3wm-wsnext``, ``i3wm-wsprev``, ``bspwm-desknext``, and ``bspwm-deskprev``
+actions, to switch workspaces in i3 and bspwm, may also appear in your config.
+
+Migration
+^^^^^^^^^
+
+Updating your config to use the new action names is quite straightforward.
+
+For each action name, consult the table below to find the new action name.
+Afterwards build the complete action string as described in
+:ref:`action-string-format`.
+
+Please see :ref:`below <menu-example>` for an example of migrating a typical menu module.
+
++-------------------------+-----------------------+---------------+
+|Module Type |Deprecated Action Name |New Action Name|
++=========================+=======================+===============+
+|``internal/date`` |``datetoggle`` |``toggle`` |
++-------------------------+-----------------------+---------------+
+|``internal/alsa`` |``volup`` |``inc`` |
+| +-----------------------+---------------+
+| |``voldown`` |``dec`` |
+| +-----------------------+---------------+
+| |``volmute`` |``toggle`` |
++-------------------------+-----------------------+---------------+
+|``internal/pulseaudio`` |``pa_volup`` |``inc`` |
+| +-----------------------+---------------+
+| |``pa_voldown`` |``dec`` |
+| +-----------------------+---------------+
+| |``pa_volmute`` |``toggle`` |
++-------------------------+-----------------------+---------------+
+|``internal/xbacklight`` |``xbacklight+`` |``inc`` |
+| +-----------------------+---------------+
+| |``xbacklight-`` |``dec`` |
++-------------------------+-----------------------+---------------+
+|``internal/backlight`` |``backlight+`` |``inc`` |
+| +-----------------------+---------------+
+| |``backlight-`` |``dec`` |
++-------------------------+-----------------------+---------------+
+|``internal/xkeyboard`` |``xkeyboard/switch`` |``switch`` |
++-------------------------+-----------------------+---------------+
+|``internal/mpd`` |``mpdplay`` |``play`` |
+| +-----------------------+---------------+
+| |``mpdpause`` |``pause`` |
+| +-----------------------+---------------+
+| |``mpdstop`` |``stop`` |
+| +-----------------------+---------------+
+| |``mpdprev`` |``prev`` |
+| +-----------------------+---------------+
+| |``mpdnext`` |``next`` |
+| +-----------------------+---------------+
+| |``mpdrepeat`` |``repeat`` |
+| +-----------------------+---------------+
+| |``mpdsingle`` |``single`` |
+| +-----------------------+---------------+
+| |``mpdrandom`` |``random`` |
+| +-----------------------+---------------+
+| |``mpdconsume`` |``consume`` |
+| +-----------------------+---------------+
+| |``mpdseekN`` |``seek.N`` |
++-------------------------+-----------------------+---------------+
+|``internal/xworkspaces`` |``xworkspaces-focus=N``|``focus.N`` |
+| +-----------------------+---------------+
+| |``xworkspaces-next`` |``next`` |
+| +-----------------------+---------------+
+| |``xworkspaces-prev`` |``prev`` |
++-------------------------+-----------------------+---------------+
+|``internal/bspwm`` |``bspwm-deskfocusN`` |``focus.N`` |
+| +-----------------------+---------------+
+| |``bspwm-desknext`` |``next`` |
+| +-----------------------+---------------+
+| |``bspwm-deskprev`` |``prev`` |
++-------------------------+-----------------------+---------------+
+|``internal/i3`` |``i3wm-wsfocus-N`` |``focus.N`` |
+| +-----------------------+---------------+
+| |``i3-wsnext`` |``next`` |
+| +-----------------------+---------------+
+| |``i3-wsprev`` |``prev`` |
++-------------------------+-----------------------+---------------+
+|``custom/menu`` |``menu-open-N`` |``open.N`` |
+| +-----------------------+---------------+
+| |``menu-close`` |``close`` |
++-------------------------+-----------------------+---------------+
+
+.. note::
+
+ Some deprecated action names are suffixed with ``N``, this means that that
+ action has some additional data (represented by that ``N``), in the new
+ action names this data will appear in exactly the same way, after a period.
+
+.. _menu-example:
+
+Menu Module Example
+"""""""""""""""""""
+
+The menu module is the only module where we have to explicitly use actions for
+it to work. Because of this, almost everyone will need to update their menu
+module to use the new action format.
+
+Below you can see an example of a menu module:
+
+.. code-block:: ini
+
+ [module/apps]
+ type = custom/menu
+
+ label-open = Apps
+
+ menu-0-0 = Browsers
+ menu-0-0-exec = menu-open-1
+ menu-0-1 = Multimedia
+ menu-0-1-exec = menu-open-2
+
+ menu-1-0 = Firefox
+ menu-1-0-exec = firefox
+ menu-1-1 = Chromium
+ menu-1-1-exec = chromium
+
+ menu-2-0 = Gimp
+ menu-2-0-exec = gimp
+ menu-2-1 = Scrot
+ menu-2-1-exec = scrot
+
+This module uses two actions: ``menu-open-1`` and ``menu-open-2``.
+These are actions with data, the data specifies which level of the menu should
+be opened.
+
+Looking at the table, we see that the new action name for ``menu-open-N`` is
+``open.N``, where ``.N`` is the data attached to the action.
+Putting this together with the name of the module gives us ``#apps.open.1`` and
+``#apps.open.2`` as action strings.
+Since your menu module likely has a different name, your action strings will
+likely not use ``apps``, but the name of your module.
+
+.. code-block:: ini
+
+ [module/apps]
+ type = custom/menu
+
+ label-open = Apps
+
+ menu-0-0 = Browsers
+ menu-0-0-exec = #apps.open.1
+ menu-0-1 = Multimedia
+ menu-0-1-exec = #apps.open.2
+
+ menu-1-0 = Firefox
+ menu-1-0-exec = firefox
+ menu-1-1 = Chromium
+ menu-1-1-exec = chromium
+
+ menu-2-0 = Gimp
+ menu-2-0-exec = gimp
+ menu-2-1 = Scrot
+ menu-2-1-exec = scrot
--- /dev/null
+Default Configuration
+=====================
+
+.. versionadded:: 3.6.0
+
+Polybar's default configuration lives in ``/etc/polybar/config.ini`` and is
+loaded if no other configuration file can be found.
+
+
+.. image:: ../_static/default.png
+ :alt: Screenshot of default polybar configuration
+
+.. literalinclude:: ../config.ini
+ :language: ini
+ :linenos:
+ :caption: :download:`Download <../config.ini>`
--- /dev/null
+Fonts
+=====
+
+.. toctree::
+ :maxdepth: 1
+
+ Wiki <https://github.com/polybar/polybar/wiki/Fonts>
+ Nerd Fonts <nerd-fonts>
--- /dev/null
+Nerd Fonts
+==========
+
+`Nerd Fonts <https://www.nerdfonts.com/>`_ (`GitHub
+<https://github.com/ryanoasis/nerd-fonts/>`_) is a project that patches
+together a textual font with font icons (or glyphs) from other projects (e.g.
+`Font Awesome <https://github.com/FortAwesome/Font-Awesome>`_, `Material Design
+Icons <https://github.com/Templarian/MaterialDesign>`_, etc.) into a single
+font.
+
+In polybar, just using nerd fonts can lead to some issues:
+
+* Cut-off Characters
+* Overlapping
+* No Spacing
+
+These look something like this:
+
+.. image:: /_static/nerd-fonts/bad.png
+ :alt: Showcase of the three issues listed above.
+
+This behavior is intrinsic to Nerd Fonts and is described in more detail `here
+<https://github.com/ryanoasis/nerd-fonts/issues/442#issuecomment-1263358904>`_.
+Also see :issue:`991` for more information.
+
+**To resolve these issues, we recommend using Nerd Fonts like this:**
+
+The monospaced variants of the different Nerd Fonts (all characters have the
+same width) don't have this issue.
+However, then you often have the problem that the icons are too small and that
+their size cannot be set independently of the text.
+
+Due to that, we recommend using ``Symbols Nerd Font Mono`` (available for
+`download <https://github.com/ryanoasis/nerd-fonts/releases/>`_ as
+``NerdFontsSymbolsOnly.zip``).
+This font only contains the nerd font icons and no text.
+For the text, simply use any non-Nerd Font:
+
+.. code-block:: ini
+
+ font-0 = "Liberation Mono:size=20"
+ font-1 = "Symbols Nerd Font Mono:size=26"
+
+Now the icon sizes can be adjusted separately to get the best experience.
+This solves all three problems shown above:
+
+.. image:: /_static/nerd-fonts/good.png
+ :alt: The same config as in the previous screenshot but using ``Symbols Nerd
+ Font Mono`` for the font icons
+
+.. note::
+
+ In the overlap example, there is no space between the icon and text, that's
+ why they're so close together.
--- /dev/null
+Inter-process-messaging
+=======================
+
+Polybar supports controlling parts of the bar and its modules from the outside
+through inter-process-messaging (IPC).
+
+IPC is disabled by default and can be enabled by setting ``enable-ipc = true``
+in the bar section.
+
+By default polybar ships with the ``polybar-msg`` tool that is needed to send
+messages to polybar.
+
+.. note:: Starting with version 3.6.0, the underlying IPC mechanism has been
+ completely changed.
+
+ Writing directly to the named pipe to send IPC messages has been
+ deprecated, ``polybar-msg`` should be used exclusively
+ Everything you could do by directly writing to the named pipe, you
+ can also do using ``polybar-msg``.
+ In addition, hook messages are also deprecated; they are replaced by
+ actions on the :ref:`ipc module <actions-ipc>`.
+
+ Unless noted otherwise, everything in this guide is still valid for
+ older versions.
+
+Sending Messages
+----------------
+
+``polybar-msg`` can be called on the commandline like this:
+
+.. code-block:: shell
+
+ polybar-msg [-p <pid>] <type> <payload>
+
+If the ``-p`` argument is specified, the message is only sent to the running
+polybar instance with the given process ID.
+Otherwise, the message is sent to all running polybar processes that have IPC
+enabled.
+
+.. note:: IPC messages are only sent to polybar instances running under the
+ same user as ``polybar-msg`` is running as.
+
+ Concretely, ``polybar`` and ``polybar-msg`` use the
+ ``$XDG_RUNTIME_DIR`` environment variable in accordance with the `XDG
+ Base Directory Specification`_ to determine where to find the socket
+ to communicate.
+
+ If ``polybar`` and ``polybar-msg`` don't have the same value for
+ ``$XDG_RUNTIME_DIR``, they will likely not be able to communicate.
+ The variable may not be set if you use ``su`` or ``sudo`` to execute
+ ``polybar-msg`` as a different user, often a full user session is
+ required.
+
+ .. _XDG Base Directory Specification: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+
+The ``<type>`` argument is either :ref:`action <ipc-actions>` or
+:ref:`cmd <ipc-commands>`.
+The allowed values for ``<payload>`` depend on the type.
+
+Message Types
+-------------
+
+.. _ipc-commands:
+
+Commands
+^^^^^^^^
+
+Using ``cmd`` for ``<type>``, you can control certain aspects of the bar.
+
+Available values for ``<payload>`` are:
+
+* ``quit``: Terminates the bar
+* ``restart``: Restarts the bar in-place
+* ``hide``: Hides the bar
+* ``show``: Makes the bar visible again, if it was hidden
+* ``toggle``: Toggles between the hidden and visible state.
+
+.. _ipc-actions:
+
+Module Actions
+^^^^^^^^^^^^^^
+
+For the ``<type>`` ``action``, ``polybar-msg`` can execute
+:doc:`module actions <actions>` in the bar.
+
+An action consists of the name of the target module, the name of the action and an optional data string:
+
+::
+
+ #<modulename>.<actionname>[.<data>]
+
+More information about action strings and available actions can be found in
+:doc:`actions`
+
+For example, if you have a date module named ``date``, you can toggle between
+the regular and alternative label with:
+
+.. code-block:: shell
+
+ polybar-msg action "#date.toggle"
+
+As an example for an action with data, say you have a menu module named
+``powermenu``, you can open the menu level 0 using:
+
+.. code-block:: shell
+
+ polybar-msg action "#powermenu.open.0"
+
+
+.. note::
+
+ For convenience, ``polybar-msg`` also allows you to pass the module name,
+ action name, and data as separate arguments:
+
+ .. code-block:: shell
+
+ polybar-msg action date toggle
+ polybar-msg action powermenu open 0
+
+ .. versionadded:: 3.6.0
--- /dev/null
+.. highlight:: ini
+
+..
+ Substitutions for quickly referencing different config value types
+
+.. |type-format| replace:: `format <type-format_>`_
+.. |type-extent| replace:: `extent <type-extent_>`_
+.. |type-pwo| replace:: `percentage with offset <type-pwo_>`_
+.. |type-color| replace:: color
+
+.. _type-format: https://github.com/polybar/polybar/wiki/Formatting#formats
+.. _type-extent: https://github.com/polybar/polybar/wiki/Formatting#extent
+.. _type-pwo: https://github.com/polybar/polybar/wiki/Formatting#percentage-with-offset
--- /dev/null
+Modules
+=======
+
+.. toctree::
+ :maxdepth: 1
+
+ System Tray <tray>
+
+ Alsa <https://github.com/polybar/polybar/wiki/Module:-alsa>
+ Backlight <https://github.com/polybar/polybar/wiki/Module:-backlight>
+ Battery <https://github.com/polybar/polybar/wiki/Module:-battery>
+ bspwm <https://github.com/polybar/polybar/wiki/Module:-bspwm>
+ CPU <https://github.com/polybar/polybar/wiki/Module:-cpu>
+ Date and Time <https://github.com/polybar/polybar/wiki/Module:-date>
+ Filesystem <https://github.com/polybar/polybar/wiki/Module:-filesystem>
+ GitHub <https://github.com/polybar/polybar/wiki/Module:-github>
+ i3 <https://github.com/polybar/polybar/wiki/Module:-i3>
+ Inter-process-messaging (IPC) <https://github.com/polybar/polybar/wiki/Module:-ipc>
+ Memory <https://github.com/polybar/polybar/wiki/Module:-memory>
+ Menu <https://github.com/polybar/polybar/wiki/Module:-menu>
+ MPD <https://github.com/polybar/polybar/wiki/Module:-mpd>
+ Network <https://github.com/polybar/polybar/wiki/Module:-network>
+ PulseAudio <https://github.com/polybar/polybar/wiki/Module:-pulseaudio>
+ Script <https://github.com/polybar/polybar/wiki/Module:-script>
+ Temperature <https://github.com/polybar/polybar/wiki/Module:-temperature>
+ Text <https://github.com/polybar/polybar/wiki/Module:-text>
+ XBacklight <https://github.com/polybar/polybar/wiki/Module:-xbacklight>
+ XKeyboard <https://github.com/polybar/polybar/wiki/Module:-xkeyboard>
+ XWindow <https://github.com/polybar/polybar/wiki/Module:-xwindow>
+ XWorkspaces <https://github.com/polybar/polybar/wiki/Module:-xworkspaces>
+ User contributed modules <https://github.com/polybar/polybar/wiki/User-contributed-modules>
--- /dev/null
+.. include:: defs.rst
+
+Tray Module
+===========
+.. poly-setting:: type = internal/tray
+
+.. versionadded:: 3.7.0
+
+The tray module displays system tray application icons on the bar.
+
+This module is a bit different from the other modules.
+The tray icons (also called clients) are individual windows managed by their
+respective application (e.g. the Dropbox tray icon is created and managed by
+the Dropbox application).
+Polybar is only responsible for embedding the windows in the bar and
+positioning them correctly.
+
+.. note::
+
+ Only a single instance of this module can be active at the same time (across
+ all polybar instances).
+
+ The way the `system tray protocol <systray-spec_>`_ works, at most one tray
+ can exist at any time.
+ Polybar will produce a warning if additional tray instances are created.
+
+For transparent background colors, the tray will use pseudo-transparency, true
+transparency is not possible for the tray icons.
+
+Formats
+-------
+
+The module only has a single format:
+
+.. poly-setting:: format
+
+ :type: |type-format|
+ :tags: ``<tray>``: Shows tray icons
+ :default: ``<tray>``
+
+Settings
+--------
+
+.. poly-setting:: tray-spacing
+
+ Space added between tray icons
+
+ :type: |type-extent|, non-negative
+ :default: ``0px``
+
+.. poly-setting:: tray-padding
+
+ Space added before and after each tray icon
+
+ :type: |type-extent|, non-negative
+ :default: ``0px``
+
+.. poly-setting:: tray-size
+
+ Size of individual tray icons
+
+ :type: |type-pwo|, relative to bar height, non-negative
+ :default: 66%
+
+.. poly-setting:: tray-background
+
+ Background color of tray icons
+
+ .. note::
+ This only affects the color of the individual icons and not the space in
+ between, changing this setting to anything else than the bar background
+ will likely not look good unless the background color is also changed for
+ the rest of the tray module (e.g. with ``format-background``).
+
+ :type: |type-color|
+ :default: ``${root.background}``
+
+.. poly-setting:: tray-foreground
+
+ Tray icon color
+
+ This serves as a hint to the tray icon application for which color to use for
+ the icon.
+
+ This is not guaranteed to have any effect (likely only in GTK3) because it
+ targets a non-standard part of the `system tray protocol <systray-spec_>`_ by
+ setting the ``_NET_SYSTEM_TRAY_COLORS`` atom on the tray window.
+
+ :type: |type-color|
+ :default: ``${tray-foreground}``
+
+
+Example
+-------
+
+::
+
+ [module/tray]
+ type = internal/tray
+
+ format-margin = 8px
+ tray-spacing = 8px
+
+Migrating From Legacy Tray Implementation
+-----------------------------------------
+
+.. include:: /migration/3.7/tray.rst
+
+References
+----------
+
+* `System Tray Protocol Specification <systray-spec_>`_
+* `XEmbed Protocol Specification <xembed_>`_
+
+.. _systray-spec: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
+.. _xembed: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
--- /dev/null
+#
+# Generate settings.hpp
+#
+
+list(APPEND XPP_EXTENSION_LIST xpp::randr::extension)
+list(APPEND XPP_EXTENSION_LIST xpp::composite::extension)
+if(WITH_XKB)
+ list(APPEND XPP_EXTENSION_LIST xpp::xkb::extension)
+endif()
+string(REPLACE ";" ", " XPP_EXTENSION_LIST "${XPP_EXTENSION_LIST}")
+
+configure_file(
+ ${CMAKE_CURRENT_LIST_DIR}/settings.hpp.cmake
+ ${CMAKE_BINARY_DIR}/generated-sources/settings.hpp
+ ESCAPE_QUOTES)
--- /dev/null
+#pragma once
+
+#include <mutex>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+// fwd
+struct _snd_ctl;
+struct _snd_hctl_elem;
+struct _snd_hctl;
+typedef struct _snd_ctl snd_ctl_t;
+typedef struct _snd_hctl_elem snd_hctl_elem_t;
+typedef struct _snd_hctl snd_hctl_t;
+
+POLYBAR_NS
+
+namespace alsa {
+ class control {
+ public:
+ explicit control(int numid);
+ ~control();
+
+ control(const control& o) = delete;
+ control& operator=(const control& o) = delete;
+
+ int get_numid();
+ bool wait(int timeout = -1);
+ bool test_device_plugged();
+ void process_events();
+
+ private:
+ int m_numid{0};
+
+ snd_ctl_t* m_ctl{nullptr};
+ snd_hctl_t* m_hctl{nullptr};
+ snd_hctl_elem_t* m_elem{nullptr};
+ };
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <alsa/asoundlib.h>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+namespace alsa {
+ DEFINE_ERROR(alsa_exception);
+ DEFINE_CHILD_ERROR(mixer_error, alsa_exception);
+ DEFINE_CHILD_ERROR(control_error, alsa_exception);
+
+ template <typename T>
+ void throw_exception(string&& message, int error_code) {
+ const char* snd_error = snd_strerror(error_code);
+ if (snd_error != nullptr)
+ message += ": " + string{snd_error};
+ throw T(message.c_str());
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <mutex>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+// fwd
+struct _snd_mixer;
+struct _snd_mixer_elem;
+struct _snd_mixer_selem_id;
+typedef struct _snd_mixer snd_mixer_t;
+typedef struct _snd_mixer_elem snd_mixer_elem_t;
+typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t;
+
+POLYBAR_NS
+
+namespace alsa {
+ class mixer {
+ public:
+ explicit mixer(string&& mixer_selem_name, string&& soundcard_name);
+ ~mixer();
+
+ mixer(const mixer& o) = delete;
+ mixer& operator=(const mixer& o) = delete;
+
+ const string& get_name();
+ const string& get_sound_card();
+
+ bool wait(int timeout = -1);
+ int process_events();
+
+ int get_volume();
+ int get_normalized_volume();
+ void set_volume(float percentage);
+ void set_normalized_volume(float percentage);
+ void set_mute(bool mode);
+ void toggle_mute();
+ bool is_muted();
+
+ private:
+ snd_mixer_t* m_mixer{nullptr};
+ snd_mixer_elem_t* m_elem{nullptr};
+
+ string m_name;
+ string s_name;
+ };
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <mpd/client.h>
+#include <stdlib.h>
+#include <chrono>
+#include <csignal>
+
+#include "common.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+// fwd
+class logger;
+
+namespace chrono = std::chrono;
+
+namespace mpd {
+ extern sig_atomic_t g_connection_closed;
+
+ DEFINE_ERROR(mpd_exception);
+ DEFINE_CHILD_ERROR(client_error, mpd_exception);
+ DEFINE_CHILD_ERROR(server_error, mpd_exception);
+
+ void check_connection(mpd_connection* conn);
+ void check_errors(mpd_connection* conn);
+
+ // types details {{{
+
+ enum class connection_state { NONE = 0, CONNECTED, DISCONNECTED };
+
+ enum class mpdstate {
+ UNKNOWN = 1 << 0,
+ STOPPED = 1 << 1,
+ PLAYING = 1 << 2,
+ PAUSED = 1 << 4,
+ };
+
+ namespace details {
+ struct mpd_connection_deleter {
+ void operator()(mpd_connection* conn);
+ };
+
+ struct mpd_status_deleter {
+ void operator()(mpd_status* status);
+ };
+
+ struct mpd_song_deleter {
+ void operator()(mpd_song* song);
+ };
+ }
+
+ using mpd_connection_t = unique_ptr<mpd_connection, details::mpd_connection_deleter>;
+ using mpd_status_t = unique_ptr<mpd_status, details::mpd_status_deleter>;
+ using mpd_song_t = unique_ptr<mpd_song, details::mpd_song_deleter>;
+
+ // }}}
+ // class : mpdsong {{{
+
+ class mpdsong {
+ public:
+ explicit mpdsong(mpd_song_t&& song) : m_song(forward<decltype(song)>(song)) {}
+
+ operator bool();
+
+ string get_artist();
+ string get_album_artist();
+ string get_album();
+ string get_title();
+ string get_date();
+ unsigned get_duration();
+
+ private:
+ mpd_song_t m_song;
+ };
+
+ // }}}
+ // class : mpdconnection {{{
+
+ class mpdstatus;
+ class mpdconnection {
+ public:
+ explicit mpdconnection(
+ const logger& logger, string host, unsigned int port = 6600, string password = "", unsigned int timeout = 15);
+ ~mpdconnection();
+
+ void connect();
+ void disconnect();
+ bool connected();
+ bool retry_connection(int interval = 1);
+
+ int get_fd();
+ void idle();
+ int noidle();
+
+ unique_ptr<mpdstatus> get_status();
+ unique_ptr<mpdstatus> get_status_safe();
+ unique_ptr<mpdsong> get_song();
+
+ void play();
+ void pause(bool state);
+ void toggle();
+ void stop();
+ void prev();
+ void next();
+ void seek(int songid, int pos);
+
+ void set_repeat(bool mode);
+ void set_random(bool mode);
+ void set_single(bool mode);
+ void set_consume(bool mode);
+
+ operator mpd_connection_t::element_type*();
+
+ protected:
+ void check_prerequisites();
+ void check_prerequisites_commands_list();
+
+ private:
+ const logger& m_log;
+ mpd_connection_t m_connection{};
+
+ struct sigaction m_signal_action {};
+
+ bool m_listactive = false;
+ bool m_idle = false;
+ int m_fd = -1;
+
+ string m_host;
+ unsigned int m_port;
+ string m_password;
+ unsigned int m_timeout;
+ };
+
+ // }}}
+ // class : mpdstatus {{{
+
+ class mpdstatus {
+ public:
+ explicit mpdstatus(mpdconnection* conn, bool autoupdate = true);
+
+ void fetch_data(mpdconnection* conn);
+ void update(int event, mpdconnection* connection);
+
+ bool random() const;
+ bool repeat() const;
+ bool single() const;
+ bool consume() const;
+
+ bool match_state(mpdstate state) const;
+
+ int get_songid() const;
+ int get_queuelen() const;
+ unsigned get_total_time() const;
+ unsigned get_elapsed_time() const;
+ unsigned get_elapsed_percentage();
+ string get_formatted_elapsed();
+ string get_formatted_total();
+ int get_seek_position(int percentage);
+
+ private:
+ mpd_status_t m_status{};
+ unique_ptr<mpdsong> m_song{};
+ mpdstate m_state{mpdstate::UNKNOWN};
+
+ bool m_random{false};
+ bool m_repeat{false};
+ bool m_single{false};
+ bool m_consume{false};
+
+ int m_songid{0};
+ int m_queuelen{0};
+
+ unsigned long m_total_time{0UL};
+ unsigned long m_elapsed_time{0UL};
+ unsigned long m_elapsed_time_ms{0UL};
+ };
+
+ // }}}
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+
+#include <chrono>
+#include <cstdlib>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/math.hpp"
+
+#if WITH_LIBNL
+#include <net/if.h>
+
+struct nl_msg;
+struct nlattr;
+#else
+#include <iwlib.h>
+
+/*
+ * wirless_tools 29 (and possibly earlier) redefines 'inline' in iwlib.h
+ * With clang this leads to a conflict in the POLYBAR_NS macro
+ * wirless_tools 30 doesn't have that issue anymore
+ */
+#ifdef inline
+#undef inline
+#endif
+#endif
+
+POLYBAR_NS
+
+class file_descriptor;
+
+namespace net {
+ DEFINE_ERROR(network_error);
+
+ bool is_interface_valid(const string& ifname);
+ std::pair<string, bool> get_canonical_interface(const string& ifname);
+ bool is_wireless_interface(const string& ifname);
+ std::string find_wireless_interface();
+ std::string find_wired_interface();
+
+ // types {{{
+
+ struct quality_range {
+ int val{0};
+ int max{0};
+
+ int percentage() const {
+ if (val < 0) {
+ return std::max(std::min(std::abs(math_util::percentage(val, max, -20)), 100), 0);
+ }
+ return std::max(std::min(math_util::percentage(val, 0, max), 100), 0);
+ }
+ };
+
+ using bytes_t = unsigned int;
+
+ struct link_activity {
+ bytes_t transmitted{0};
+ bytes_t received{0};
+ std::chrono::steady_clock::time_point time;
+ };
+
+ struct link_status {
+ string ip;
+ string ip6;
+ string mac;
+ link_activity previous{};
+ link_activity current{};
+ };
+
+ // }}}
+ // class : network {{{
+
+ class network {
+ public:
+ explicit network(string interface);
+ virtual ~network() {}
+
+ virtual bool query(bool accumulate = false);
+ virtual bool connected() const = 0;
+ virtual bool ping() const;
+
+ string ip() const;
+ string ip6() const;
+ string mac() const;
+ string downspeed(int minwidth = 3, const string& unit = "B/s") const;
+ string upspeed(int minwidth = 3, const string& unit = "B/s") const;
+ string netspeed(int minwidth = 3, const string& unit = "B/s") const;
+ void set_unknown_up(bool unknown = true);
+
+ protected:
+ void check_tuntap_or_bridge();
+ bool test_interface() const;
+ string format_speedrate(float bytes_diff, int minwidth, const string& unit) const;
+ void query_ip6();
+
+ const logger& m_log;
+ unique_ptr<file_descriptor> m_socketfd;
+ link_status m_status{};
+ string m_interface;
+ bool m_tuntap{false};
+ bool m_bridge{false};
+ bool m_unknown_up{false};
+ };
+
+ // }}}
+ // class : wired_network {{{
+
+ class wired_network : public network {
+ public:
+ explicit wired_network(string interface) : network(interface) {}
+
+ bool query(bool accumulate = false) override;
+ bool connected() const override;
+ string linkspeed() const;
+
+ private:
+ int m_linkspeed{0};
+ };
+
+ // }}}
+
+#if WITH_LIBNL
+ // class : wireless_network {{{
+
+ class wireless_network : public network {
+ public:
+ wireless_network(string interface) : network(interface), m_ifid(if_nametoindex(interface.c_str())){};
+
+ bool query(bool accumulate = false) override;
+ bool connected() const override;
+ string essid() const;
+ int signal() const;
+ int quality() const;
+
+ protected:
+ static int scan_cb(struct nl_msg* msg, void* instance);
+
+ bool associated_or_joined(struct nlattr** bss);
+ void parse_essid(struct nlattr** bss);
+ void parse_frequency(struct nlattr** bss);
+ void parse_quality(struct nlattr** bss);
+ void parse_signal(struct nlattr** bss);
+
+ private:
+ unsigned int m_ifid{};
+ string m_essid{};
+ int m_frequency{};
+ quality_range m_signalstrength{};
+ quality_range m_linkquality{};
+ };
+
+ // }}}
+#else
+ // class : wireless_network {{{
+
+ class wireless_network : public network {
+ public:
+ wireless_network(string interface) : network(interface) {}
+
+ bool query(bool accumulate = false) override;
+ bool connected() const override;
+
+ string essid() const;
+ int signal() const;
+ int quality() const;
+
+ protected:
+ void query_essid(const int& socket_fd);
+ void query_quality(const int& socket_fd);
+
+ private:
+ shared_ptr<wireless_info> m_info{};
+ string m_essid{};
+ quality_range m_signalstrength{};
+ quality_range m_linkquality{};
+ };
+
+ // }}}
+#endif
+
+ using wireless_t = unique_ptr<wireless_network>;
+ using wired_t = unique_ptr<wired_network>;
+} // namespace net
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <pulse/pulseaudio.h>
+
+#include <atomic>
+#include <queue>
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/math.hpp"
+// fwd
+struct pa_context;
+struct pa_threaded_mainloop;
+struct pa_cvolume;
+typedef struct pa_context pa_context;
+typedef struct pa_threaded_mainloop pa_threaded_mainloop;
+
+POLYBAR_NS
+class logger;
+
+DEFINE_ERROR(pulseaudio_error);
+
+class pulseaudio {
+ // events to add to our queue
+ enum class evtype { NEW = 0, CHANGE, REMOVE, SERVER };
+ using queue = std::queue<evtype>;
+
+ public:
+ explicit pulseaudio(const logger& logger, string&& sink_name, bool m_max_volume);
+ ~pulseaudio();
+
+ pulseaudio(const pulseaudio& o) = delete;
+ pulseaudio& operator=(const pulseaudio& o) = delete;
+
+ const string& get_name();
+
+ bool wait();
+ int process_events();
+
+ int get_volume();
+ double get_decibels();
+ void set_volume(float percentage);
+ void inc_volume(int delta_perc);
+ void set_mute(bool mode);
+ void toggle_mute();
+ bool is_muted();
+
+ private:
+ void update_volume(pa_operation* o);
+ static void check_mute_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata);
+ static void get_sink_volume_callback(pa_context* context, const pa_sink_info* info, int is_last, void* userdata);
+ static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata);
+ static void simple_callback(pa_context* context, int success, void* userdata);
+ static void sink_info_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata);
+ static void context_state_callback(pa_context* context, void* userdata);
+
+ inline void wait_loop(pa_operation* op, pa_threaded_mainloop* loop);
+
+ const logger& m_log;
+
+ /**
+ * Has context_state_callback signalled the mainloop during connection.
+ */
+ std::atomic_bool m_state_callback_signal{false};
+
+ // used for temporary callback results
+ int success{0};
+ pa_cvolume cv{};
+ bool muted{false};
+ // default sink name
+ static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@";
+
+ pa_context* m_context{nullptr};
+ pa_threaded_mainloop* m_mainloop{nullptr};
+
+ queue m_events;
+
+ // specified sink name
+ string spec_s_name;
+ string s_name;
+ uint32_t m_index{0};
+
+ pa_volume_t m_max_volume{PA_VOLUME_UI_MAX};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <mutex>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+
+POLYBAR_NS
+
+class script_runner {
+ public:
+ struct data {
+ int counter{0};
+ int pid{-1};
+ int exit_status{0};
+ string output;
+ };
+
+ using on_update = std::function<void(const data&)>;
+ using interval = std::chrono::duration<double>;
+
+ script_runner(on_update on_update, const string& exec, const string& exec_if, bool tail, interval interval_success,
+ interval interval_fail, const vector<pair<string, string>>& env);
+
+ bool check_condition() const;
+ interval process();
+
+ void clear_output();
+
+ void stop();
+
+ bool is_stopping() const;
+
+ protected:
+ bool set_output(string&&);
+ bool set_exit_status(int);
+
+ interval run_tail();
+ interval run();
+
+ private:
+ const logger& m_log;
+
+ const on_update m_on_update;
+
+ const string m_exec;
+ const string m_exec_if;
+ const bool m_tail;
+ const interval m_interval_success;
+ const interval m_interval_fail;
+ const vector<pair<string, string>> m_env;
+
+ data m_data;
+ std::atomic_bool m_stopping{false};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo-xcb.h>
+
+#include <algorithm>
+#include <cmath>
+#include <deque>
+#include <iomanip>
+#include <iterator>
+
+#include "cairo/font.hpp"
+#include "cairo/surface.hpp"
+#include "cairo/types.hpp"
+#include "cairo/utils.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+ /**
+ * @brief Cairo context
+ */
+ class context {
+ public:
+ explicit context(const surface& surface, const logger& log) : m_c(cairo_create(surface)), m_log(log) {
+ auto status = cairo_status(m_c);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ throw application_error(sstream() << "cairo_status(): " << cairo_status_to_string(status));
+ }
+ cairo_set_antialias(m_c, CAIRO_ANTIALIAS_GOOD);
+ }
+
+ virtual ~context() {
+ cairo_destroy(m_c);
+ }
+
+ operator cairo_t*() const {
+ return m_c;
+ }
+
+ context& operator<<(const surface& s) {
+ cairo_set_source_surface(m_c, s, 0.0, 0.0);
+ return *this;
+ }
+
+ context& operator<<(cairo_operator_t o) {
+ cairo_set_operator(m_c, o);
+ return *this;
+ }
+
+ context& operator<<(cairo_pattern_t* s) {
+ cairo_set_source(m_c, s);
+ return *this;
+ }
+
+ context& operator<<(const abspos& p) {
+ if (p.clear) {
+ cairo_new_path(m_c);
+ }
+ cairo_move_to(m_c, p.x, p.y);
+ return *this;
+ }
+
+ context& operator<<(const relpos& p) {
+ cairo_rel_move_to(m_c, p.x, p.y);
+ return *this;
+ }
+
+ context& operator<<(const rgba& f) {
+ cairo_set_source_rgba(m_c, f.red_d(), f.green_d(), f.blue_d(), f.alpha_d());
+ return *this;
+ }
+
+ context& operator<<(const rect& f) {
+ cairo_rectangle(m_c, f.x, f.y, f.w, f.h);
+ return *this;
+ }
+
+ context& operator<<(const line& l) {
+ struct line p {
+ l.x1, l.y1, l.x2, l.y2, l.w
+ };
+ snap(&p.x1, &p.y1);
+ snap(&p.x2, &p.y2);
+ cairo_move_to(m_c, p.x1, p.y1);
+ cairo_line_to(m_c, p.x2, p.y2);
+ cairo_set_line_width(m_c, p.w);
+ cairo_stroke(m_c);
+ return *this;
+ }
+
+ context& operator<<(const translate& d) {
+ cairo_translate(m_c, d.x, d.y);
+ return *this;
+ }
+
+ context& operator<<(const linear_gradient& l) {
+ auto stops = l.steps.size();
+ if (stops >= 2) {
+ auto pattern = cairo_pattern_create_linear(l.x1, l.y1, l.x2, l.y2);
+ auto step = 1.0 / (stops - 1);
+ auto offset = 0.0;
+ for (auto&& color : l.steps) {
+ // clang-format off
+ cairo_pattern_add_color_stop_rgba(pattern, offset, color.red_d(), color.green_d(), color.blue_d(), color.alpha_d());
+ // clang-format on
+ offset += step;
+ }
+ *this << pattern;
+ cairo_pattern_destroy(pattern);
+ }
+ return *this;
+ }
+
+ context& operator<<(const rounded_corners& c) {
+ cairo_new_sub_path(m_c);
+ cairo_arc(
+ m_c, c.x + c.w - c.radius.top_right, c.y + c.radius.top_right, c.radius.top_right, -90 * degree, 0 * degree);
+ cairo_arc(m_c, c.x + c.w - c.radius.bottom_right, c.y + c.h - c.radius.bottom_right, c.radius.bottom_right,
+ 0 * degree, 90 * degree);
+ cairo_arc(m_c, c.x + c.radius.bottom_left, c.y + c.h - c.radius.bottom_left, c.radius.bottom_left, 90 * degree,
+ 180 * degree);
+ cairo_arc(m_c, c.x + c.radius.top_left, c.y + c.radius.top_left, c.radius.top_left, 180 * degree, 270 * degree);
+ cairo_close_path(m_c);
+ return *this;
+ }
+
+ context& operator<<(const circle_segment& segment) {
+ cairo_new_sub_path(m_c);
+ cairo_arc(m_c, segment.x, segment.y, segment.radius, segment.angle_from * degree, segment.angle_to * degree);
+ switch ((int)segment.angle_to) {
+ case 0:
+ cairo_rel_line_to(m_c, -segment.w, 0);
+ break;
+ case 90:
+ cairo_rel_line_to(m_c, 0, -segment.w);
+ break;
+ case 180:
+ cairo_rel_line_to(m_c, segment.w, 0);
+ break;
+ default:
+ cairo_rel_line_to(m_c, 0, segment.w);
+ break;
+ }
+ cairo_arc_negative(m_c, segment.x, segment.y, segment.radius - segment.w, segment.angle_to * degree,
+ segment.angle_from * degree);
+ cairo_close_path(m_c);
+ return *this;
+ }
+
+ context& operator<<(const textblock& t) {
+ double x, y;
+ position(&x, &y);
+
+ // Prioritize the preferred font
+ vector<shared_ptr<font>> fns(m_fonts.begin(), m_fonts.end());
+
+ if (t.font > 0 && t.font <= std::distance(fns.begin(), fns.end())) {
+ std::iter_swap(fns.begin(), fns.begin() + t.font - 1);
+ }
+
+ string utf8 = t.contents;
+ string_util::unicode_charlist chars;
+ bool valid = string_util::utf8_to_ucs4(utf8, chars);
+
+ // The conversion already removed any invalid chunks. We should probably log a warning though.
+ if (!valid) {
+ sstream hex;
+ hex << std::hex << std::setw(2) << std::setfill('0');
+
+ for(const char& c: utf8) {
+ hex << (static_cast<int>(c) & 0xff) << " ";
+ }
+
+ m_log.warn("Dropping invalid parts of UTF8 text '%s' %s", utf8, hex.to_string());
+ }
+
+ while (!chars.empty()) {
+ auto remaining = chars.size();
+ for (auto&& f : fns) {
+ unsigned int matches = 0;
+
+ // Match as many glyphs as possible if the default/preferred font
+ // is being tested. Otherwise test one glyph at a time against
+ // the remaining fonts. Roll back to the top of the font list
+ // when a glyph has been found.
+ if (f == fns.front() && (matches = f->match(chars)) == 0) {
+ continue;
+ } else if (f != fns.front() && (matches = f->match(chars.front())) == 0) {
+ continue;
+ }
+
+ string subset;
+ auto end = chars.begin();
+ while (matches-- && end != chars.end()) {
+ subset += utf8.substr(end->offset, end->length);
+ end++;
+ }
+
+ // Use the font
+ f->use();
+
+ // Get subset extents
+ cairo_text_extents_t extents;
+ f->textwidth(subset, &extents);
+
+ /*
+ * Make sure we don't advance partial pixels, this can cause problems
+ * later when cairo renders background colors over half-pixels.
+ */
+ extents.x_advance = std::ceil(extents.x_advance);
+
+ // Draw the background
+ if (t.bg_rect.h != 0.0) {
+ save();
+ cairo_set_operator(m_c, t.bg_operator);
+ *this << t.bg;
+ cairo_rectangle(m_c, t.bg_rect.x + *t.x_advance, t.bg_rect.y + *t.y_advance,
+ t.bg_rect.w + extents.x_advance, t.bg_rect.h);
+ cairo_fill(m_c);
+ restore();
+ }
+
+ // Render subset
+ auto fontextents = f->extents();
+ f->render(subset, x, y - (fontextents.descent / 2 - fontextents.height / 4) + f->offset());
+
+ // Get updated position
+ position(&x, nullptr);
+
+ // Increase position
+ *t.x_advance += extents.x_advance;
+ *t.y_advance += extents.y_advance;
+
+ chars.erase(chars.begin(), end);
+ break;
+ }
+
+ if (chars.empty()) {
+ break;
+ } else if (remaining != chars.size()) {
+ continue;
+ }
+
+ std::array<char, 5> unicode{};
+ string_util::ucs4_to_utf8(unicode, chars.begin()->codepoint);
+ m_log.warn("Dropping unmatched character '%s' (U+%04x) in '%s'", unicode.data(), chars.begin()->codepoint, t.contents);
+ utf8.erase(chars.begin()->offset, chars.begin()->length);
+ for (auto&& c : chars) {
+ c.offset -= chars.begin()->length;
+ }
+ chars.erase(chars.begin(), ++chars.begin());
+ }
+
+ return *this;
+ }
+
+ context& operator<<(shared_ptr<font>&& f) {
+ m_fonts.emplace_back(forward<decltype(f)>(f));
+ return *this;
+ }
+
+ context& save(bool save_point = false) {
+ if (save_point) {
+ m_points.emplace_front(make_pair<double, double>(0.0, 0.0));
+ position(&m_points.front().first, &m_points.front().second);
+ }
+ m_activegroups++;
+ cairo_save(m_c);
+ return *this;
+ }
+
+ context& restore(bool restore_point = false) {
+ if (!m_activegroups) {
+ throw application_error("Unmatched calls to save/restore");
+ }
+ m_activegroups--;
+ cairo_restore(m_c);
+ if (restore_point && !m_points.empty()) {
+ *this << abspos{m_points.front().first, m_points.front().first};
+ m_points.pop_front();
+ }
+ return *this;
+ }
+
+ context& paint() {
+ cairo_paint(m_c);
+ return *this;
+ }
+
+ context& paint(double alpha) {
+ cairo_paint_with_alpha(m_c, alpha);
+ return *this;
+ }
+
+ context& fill(bool preserve = false) {
+ if (preserve) {
+ cairo_fill_preserve(m_c);
+ } else {
+ cairo_fill(m_c);
+ }
+ return *this;
+ }
+
+ context& mask(cairo_pattern_t* pattern) {
+ cairo_mask(m_c, pattern);
+ return *this;
+ }
+
+ context& pop(cairo_pattern_t** pattern) {
+ *pattern = cairo_pop_group(m_c);
+ return *this;
+ }
+
+ context& push() {
+ cairo_push_group(m_c);
+ return *this;
+ }
+
+ context& destroy(cairo_pattern_t** pattern) {
+ cairo_pattern_destroy(*pattern);
+ *pattern = nullptr;
+ return *this;
+ }
+
+ context& clear(bool paint = true) {
+ cairo_save(m_c);
+ cairo_set_operator(m_c, CAIRO_OPERATOR_CLEAR);
+ if (paint) {
+ cairo_paint(m_c);
+ } else {
+ cairo_fill_preserve(m_c);
+ }
+ cairo_restore(m_c);
+ return *this;
+ }
+
+ context& clip(bool preserve = false) {
+ if (preserve) {
+ cairo_clip_preserve(m_c);
+ } else {
+ cairo_clip(m_c);
+ cairo_new_path(m_c);
+ }
+ return *this;
+ }
+
+ context& clip(const rect& r) {
+ *this << r;
+ return clip();
+ }
+
+ context& reset_clip() {
+ cairo_reset_clip(m_c);
+ return *this;
+ }
+
+ context& position(double* x, double* y = nullptr) {
+ if (cairo_has_current_point(m_c)) {
+ double x_, y_;
+ x = x != nullptr ? x : &x_;
+ y = y != nullptr ? y : &y_;
+ cairo_get_current_point(m_c, x, y);
+ }
+ return *this;
+ }
+
+ context& snap(double* x, double* y) {
+ cairo_user_to_device(m_c, x, y);
+ *x = static_cast<int>(*x + 0.5);
+ *y = static_cast<int>(*y + 0.5);
+ return *this;
+ }
+
+ protected:
+ cairo_t* m_c;
+ const logger& m_log;
+ vector<shared_ptr<font>> m_fonts;
+ std::deque<pair<double, double>> m_points;
+ int m_activegroups{0};
+
+ private:
+ const double degree = M_PI / 180.0;
+ };
+} // namespace cairo
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo-ft.h>
+
+#include "cairo/types.hpp"
+#include "cairo/utils.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/math.hpp"
+#include "utils/scope.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+/**
+ * @brief Global pointer to the Freetype library handler
+ */
+static FT_Library g_ftlib;
+
+/**
+ * @brief Abstract font face
+ */
+class font {
+ public:
+ explicit font(cairo_t* cairo, double offset) : m_cairo(cairo), m_offset(offset) {}
+ virtual ~font(){};
+
+ virtual string name() const = 0;
+ virtual string file() const = 0;
+ virtual double offset() const = 0;
+ virtual double size(double dpi) const = 0;
+
+ virtual cairo_font_extents_t extents() = 0;
+
+ virtual void use() {
+ cairo_set_font_face(m_cairo, cairo_font_face_reference(m_font_face));
+ }
+
+ virtual size_t match(string_util::unicode_character& character) = 0;
+ virtual size_t match(string_util::unicode_charlist& charlist) = 0;
+ virtual size_t render(const string& text, double x = 0.0, double y = 0.0) = 0;
+ virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0;
+
+ protected:
+ cairo_t* m_cairo;
+ cairo_font_face_t* m_font_face{nullptr};
+ cairo_font_extents_t m_extents{};
+ double m_offset{0.0};
+};
+
+/**
+ * @brief Font based on fontconfig/freetype
+ */
+class font_fc : public font {
+ public:
+ explicit font_fc(cairo_t* cairo, FcPattern* pattern, double offset, double dpi_x, double dpi_y)
+ : font(cairo, offset), m_pattern(pattern) {
+ cairo_matrix_t fm;
+ cairo_matrix_t ctm;
+ cairo_matrix_init_scale(&fm, size(dpi_x), size(dpi_y));
+ cairo_get_matrix(m_cairo, &ctm);
+
+ auto fontface = cairo_ft_font_face_create_for_pattern(m_pattern);
+ auto opts = cairo_font_options_create();
+ m_scaled = cairo_scaled_font_create(fontface, &fm, &ctm, opts);
+ cairo_font_options_destroy(opts);
+ cairo_font_face_destroy(fontface);
+
+ auto status = cairo_scaled_font_status(m_scaled);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ throw application_error(sstream() << "cairo_scaled_font_create(): " << cairo_status_to_string(status));
+ }
+
+ auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+ auto face = static_cast<FT_Face>(*lock);
+
+ if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) == FT_Err_Ok) {
+ return;
+ } else if (FT_Select_Charmap(face, FT_ENCODING_BIG5) == FT_Err_Ok) {
+ return;
+ } else if (FT_Select_Charmap(face, FT_ENCODING_SJIS) == FT_Err_Ok) {
+ return;
+ }
+
+ lock.reset();
+ }
+
+ ~font_fc() override {
+ if (m_scaled != nullptr) {
+ cairo_scaled_font_destroy(m_scaled);
+ }
+ if (m_pattern != nullptr) {
+ FcPatternDestroy(m_pattern);
+ }
+ }
+
+ cairo_font_extents_t extents() override {
+ cairo_scaled_font_extents(m_scaled, &m_extents);
+ return m_extents;
+ }
+
+ string name() const override {
+ return property("family");
+ }
+
+ string file() const override {
+ return property("file");
+ }
+
+ double offset() const override {
+ return m_offset;
+ }
+
+ /**
+ * Calculates the font size in pixels for the given dpi
+ *
+ * We use the two font properties size and pixelsize. size is in points and
+ * needs to be scaled with the given dpi. pixelsize is not scaled.
+ *
+ * If both size properties are 0, we fall back to a default value of 10
+ * points for scalable fonts or 10 pixel for non-scalable ones. This should
+ * only happen if both properties are purposefully set to 0
+ *
+ * For scalable fonts we try to use the size property scaled according to
+ * the dpi.
+ * For non-scalable fonts we try to use the pixelsize property as-is
+ */
+ double size(double dpi) const override {
+ bool scalable;
+ double fc_pixelsize = 0, fc_size = 0;
+
+ property(FC_SCALABLE, &scalable);
+
+ // Size in points
+ property(FC_SIZE, &fc_size);
+
+ // Size in pixels
+ property(FC_PIXEL_SIZE, &fc_pixelsize);
+
+ // Fall back to a default value if the size is 0
+ double pixelsize = fc_pixelsize == 0 ? 10 : fc_pixelsize;
+ double size = fc_size == 0 ? 10 : fc_size;
+
+ // Font size in pixels if we use the pixelsize property
+ int px_pixelsize = pixelsize + 0.5;
+
+ /*
+ * Font size in pixels if we use the size property. Since the size
+ * specifies the font size in points, this is converted to pixels
+ * according to the dpi given.
+ * One point is 1/72 inches, thus this gives us the number of 'dots'
+ * (or pixels) for this font
+ */
+ int px_size = size / 72.0 * dpi + 0.5;
+
+ if (fc_size == 0 && fc_pixelsize == 0) {
+ return scalable ? px_size : px_pixelsize;
+ }
+
+ if (scalable) {
+ /*
+ * Use the point size if it's not 0. The pixelsize is only used if the
+ * size property is 0 and pixelsize is not
+ */
+ if (fc_size != 0) {
+ return px_size;
+ } else {
+ return px_pixelsize;
+ }
+ } else {
+ /*
+ * Non-scalable fonts do it the other way around, here the size
+ * property is only used if pixelsize is 0 and size is not
+ */
+ if (fc_pixelsize != 0) {
+ return px_pixelsize;
+ } else {
+ return px_size;
+ }
+ }
+ }
+
+ void use() override {
+ cairo_set_scaled_font(m_cairo, m_scaled);
+ }
+
+ size_t match(string_util::unicode_character& character) override {
+ auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+ auto face = static_cast<FT_Face>(*lock);
+ return FT_Get_Char_Index(face, character.codepoint) ? 1 : 0;
+ }
+
+ size_t match(string_util::unicode_charlist& charlist) override {
+ auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+ auto face = static_cast<FT_Face>(*lock);
+ size_t available_chars = 0;
+ for (auto&& c : charlist) {
+ if (FT_Get_Char_Index(face, c.codepoint)) {
+ available_chars++;
+ } else {
+ break;
+ }
+ }
+
+ return available_chars;
+ }
+
+ size_t render(const string& text, double x = 0.0, double y = 0.0) override {
+ cairo_glyph_t* glyphs{nullptr};
+ cairo_text_cluster_t* clusters{nullptr};
+ cairo_text_cluster_flags_t cf{};
+ int nglyphs = 0;
+ int nclusters = 0;
+
+ string utf8 = string(text);
+ auto status = cairo_scaled_font_text_to_glyphs(
+ m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
+
+ if (status != CAIRO_STATUS_SUCCESS) {
+ throw application_error(sstream() << "cairo_scaled_font_text_to_glyphs() " << cairo_status_to_string(status));
+ }
+
+ size_t bytes = 0;
+ for (int g = 0; g < nglyphs; g++) {
+ if (glyphs[g].index) {
+ bytes += clusters[g].num_bytes;
+ } else {
+ break;
+ }
+ }
+
+ if (bytes && bytes < text.size()) {
+ cairo_glyph_free(glyphs);
+ cairo_text_cluster_free(clusters);
+
+ utf8 = text.substr(0, bytes);
+ auto status = cairo_scaled_font_text_to_glyphs(
+ m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
+
+ if (status != CAIRO_STATUS_SUCCESS) {
+ throw application_error(sstream() << "cairo_scaled_font_text_to_glyphs() " << cairo_status_to_string(status));
+ }
+ }
+
+ if (bytes) {
+ // auto lock = make_unique<utils::device_lock>(cairo_surface_get_device(cairo_get_target(m_cairo)));
+ // if (lock.get()) {
+ // cairo_glyph_path(m_cairo, glyphs, nglyphs);
+ // }
+
+ cairo_text_extents_t extents{};
+ cairo_scaled_font_glyph_extents(m_scaled, glyphs, nglyphs, &extents);
+ cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf);
+ cairo_fill(m_cairo);
+ cairo_move_to(m_cairo, x + extents.x_advance, 0.0);
+ }
+
+ cairo_glyph_free(glyphs);
+ cairo_text_cluster_free(clusters);
+
+ return bytes;
+ }
+
+ void textwidth(const string& text, cairo_text_extents_t* extents) override {
+ cairo_scaled_font_text_extents(m_scaled, text.c_str(), extents);
+ }
+
+ protected:
+ string property(string&& property) const {
+ FcChar8* file;
+ if (FcPatternGetString(m_pattern, property.c_str(), 0, &file) == FcResultMatch) {
+ return string(reinterpret_cast<char*>(file));
+ } else {
+ return "";
+ }
+ }
+
+ void property(string&& property, bool* dst) const {
+ FcBool b;
+ FcPatternGetBool(m_pattern, property.c_str(), 0, &b);
+ *dst = b;
+ }
+
+ void property(string&& property, double* dst) const {
+ FcPatternGetDouble(m_pattern, property.c_str(), 0, dst);
+ }
+
+ void property(string&& property, int* dst) const {
+ FcPatternGetInteger(m_pattern, property.c_str(), 0, dst);
+ }
+
+ private:
+ cairo_scaled_font_t* m_scaled{nullptr};
+ FcPattern* m_pattern{nullptr};
+};
+
+/**
+ * Match and create font from given fontconfig pattern
+ */
+inline decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset, double dpi_x, double dpi_y) {
+ static bool fc_init{false};
+ if (!fc_init && !(fc_init = FcInit())) {
+ throw application_error("Could not load fontconfig");
+ } else if (FT_Init_FreeType(&g_ftlib) != FT_Err_Ok) {
+ throw application_error("Could not load FreeType");
+ }
+
+ static scope_util::on_exit fc_cleanup([] {
+ FT_Done_FreeType(g_ftlib);
+ FcFini();
+ });
+
+ auto pattern = FcNameParse((FcChar8*)fontname.c_str());
+
+ if (!pattern) {
+ logger::make().err("Could not parse font \"%s\"", fontname);
+ throw application_error("Could not parse font \"" + fontname + "\"");
+ }
+
+ FcDefaultSubstitute(pattern);
+ FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
+
+ FcResult result;
+ FcPattern* match = FcFontMatch(nullptr, pattern, &result);
+ FcPatternDestroy(pattern);
+
+ if (match == nullptr) {
+ throw application_error("Could not load font \"" + fontname + "\"");
+ }
+
+#ifdef DEBUG_FONTCONFIG
+ FcPatternPrint(match);
+#endif
+
+ return make_shared<font_fc>(cairo, match, offset, dpi_x, dpi_y);
+}
+} // namespace cairo
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+ class context;
+ class surface;
+ class xcb_surface;
+ class font;
+ class font_fc;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo-xcb.h>
+
+#include "cairo/types.hpp"
+#include "common.hpp"
+#include "errors.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+ /**
+ * @brief Base surface
+ */
+ class surface {
+ public:
+ explicit surface(cairo_surface_t* s) : m_s(s) {}
+ virtual ~surface() {
+ cairo_surface_destroy(m_s);
+ }
+
+ operator cairo_surface_t*() const {
+ return m_s;
+ }
+
+ void flush() {
+ cairo_surface_flush(m_s);
+ }
+
+ void show(bool clear = true) {
+ if (clear) {
+ cairo_surface_show_page(m_s);
+ } else {
+ cairo_surface_copy_page(m_s);
+ }
+ }
+
+ void dirty() {
+ cairo_surface_mark_dirty(m_s);
+ }
+
+ void dirty(const rect& r) {
+ cairo_surface_mark_dirty_rectangle(m_s, r.x, r.y, r.w, r.h);
+ }
+
+ void write_png(const string& dst) {
+ auto status = cairo_surface_write_to_png(m_s, dst.c_str());
+ if (status != CAIRO_STATUS_SUCCESS) {
+ throw application_error(sstream() << "cairo_surface_write_to_png(): " << cairo_status_to_string(status));
+ }
+ }
+
+ protected:
+ cairo_surface_t* m_s;
+ };
+
+ /**
+ * @brief Surface for xcb
+ */
+ class xcb_surface : public surface {
+ public:
+ explicit xcb_surface(xcb_connection_t* c, xcb_pixmap_t p, xcb_visualtype_t* v, int w, int h)
+ : surface(cairo_xcb_surface_create(c, p, v, w, h)) {}
+
+ ~xcb_surface() override {}
+
+ void set_drawable(xcb_drawable_t d, int w, int h) {
+ cairo_surface_flush(m_s);
+ cairo_xcb_surface_set_drawable(m_s, d, w, h);
+ }
+ };
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo.h>
+
+#include "common.hpp"
+#include "components/types.hpp"
+
+POLYBAR_NS
+
+enum class alignment;
+
+namespace cairo {
+ struct point {
+ double x;
+ double y;
+ };
+ struct abspos {
+ double x;
+ double y;
+ bool clear{true};
+ };
+ struct relpos {
+ double x;
+ double y;
+ };
+ struct rect {
+ double x;
+ double y;
+ double w;
+ double h;
+ };
+ struct line {
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+ double w;
+ };
+ struct translate {
+ double x;
+ double y;
+ };
+ struct linear_gradient {
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+ vector<rgba> steps;
+ };
+ struct rounded_corners {
+ double x;
+ double y;
+ double w;
+ double h;
+ struct radius radius;
+ };
+ struct circle_segment {
+ double x;
+ double y;
+ double w;
+ double angle_from;
+ double angle_to;
+ double radius;
+ };
+ struct textblock {
+ alignment align;
+ string contents;
+ int font;
+ rgba bg{};
+ cairo_operator_t bg_operator;
+ rect bg_rect;
+ double* x_advance;
+ double* y_advance;
+ };
+} // namespace cairo
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo-ft.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+namespace utils {
+ /**
+ * @brief RAII wrapper used acquire cairo_device_t
+ */
+ class device_lock {
+ public:
+ explicit device_lock(cairo_device_t* device);
+ ~device_lock();
+ operator bool() const;
+ operator cairo_device_t*() const;
+
+ private:
+ cairo_device_t* m_device{nullptr};
+ };
+
+ /**
+ * @brief RAII wrapper used to access the underlying
+ * FT_Face of a scaled font face
+ */
+ class ft_face_lock {
+ public:
+ explicit ft_face_lock(cairo_scaled_font_t* font);
+ ~ft_face_lock();
+ operator FT_Face() const;
+
+ private:
+ cairo_scaled_font_t* m_font;
+ FT_Face m_face;
+ };
+
+ /**
+ * @see <cairo/cairo.h>
+ */
+ cairo_operator_t str2operator(const string& mode, cairo_operator_t fallback);
+} // namespace utils
+} // namespace cairo
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "settings.hpp"
+
+#define POLYBAR_NS namespace polybar {
+#define POLYBAR_NS_END }
+
+#ifndef PIPE_READ
+#define PIPE_READ 0
+#endif
+#ifndef PIPE_WRITE
+#define PIPE_WRITE 1
+#endif
+
+POLYBAR_NS
+
+using std::array;
+using std::forward;
+using std::function;
+using std::make_pair;
+using std::make_shared;
+using std::make_unique;
+using std::move;
+using std::pair;
+using std::shared_ptr;
+using std::size_t;
+using std::string;
+using std::to_string;
+using std::unique_ptr;
+using std::vector;
+
+using namespace std::string_literals;
+
+constexpr size_t operator"" _z(unsigned long long n) {
+ return n;
+}
+
+/**
+ * Convert an enum to its underlying type.
+ */
+template <typename E>
+constexpr auto to_integral(E e) {
+ static_assert(std::is_enum<E>::value, "only enums are supported");
+ return static_cast<typename std::underlying_type_t<E>>(e);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cstdlib>
+#include <set>
+
+#include "common.hpp"
+#include "components/eventloop.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "settings.hpp"
+#include "tags/action_context.hpp"
+#include "utils/math.hpp"
+#include "x11/types.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+class config;
+class connection;
+class logger;
+class renderer;
+class screen;
+namespace legacy_tray {
+class tray_manager;
+}
+
+namespace tags {
+class dispatch;
+}
+// }}}
+
+class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify, evt::enter_notify,
+ evt::leave_notify, evt::motion_notify, evt::destroy_notify, evt::client_message, evt::configure_notify>,
+ public signal_receiver<SIGN_PRIORITY_BAR, signals::ui::dim_window> {
+ public:
+ using make_type = unique_ptr<bar>;
+ static make_type make(eventloop::loop&, const config&, bool only_initialize_values = false);
+
+ explicit bar(connection&, signal_emitter&, const config&, const logger&, eventloop::loop&, unique_ptr<screen>&&,
+ unique_ptr<tags::dispatch>&&, unique_ptr<tags::action_context>&&, bool only_initialize_values);
+ ~bar();
+
+ const bar_settings& settings() const;
+
+ void start(const string& tray_module_name);
+
+ void parse(string&& data, bool force = false);
+
+ void hide();
+ void show();
+ void toggle();
+
+ protected:
+ void restack_window();
+ void reconfigure_window();
+ void reconfigure_geom();
+ void reconfigure_pos();
+ void reconfigure_struts();
+ void reconfigure_wm_hints();
+ void broadcast_visibility();
+
+ void map_window();
+
+ void trigger_click(mousebtn btn, int pos);
+
+ void handle(const evt::client_message& evt) override;
+ void handle(const evt::destroy_notify& evt) override;
+ void handle(const evt::enter_notify& evt) override;
+ void handle(const evt::leave_notify& evt) override;
+ void handle(const evt::motion_notify& evt) override;
+ void handle(const evt::button_press& evt) override;
+ void handle(const evt::expose& evt) override;
+ void handle(const evt::property_notify& evt) override;
+ void handle(const evt::configure_notify& evt) override;
+
+ bool on(const signals::ui::dim_window&) override;
+
+#if WITH_XCURSOR
+ /**
+ * Change cursor to the given cursor name.
+ *
+ * The cursor name must be valid (cursor_util::valid)
+ */
+ void change_cursor(const string& name);
+#endif
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const config& m_conf;
+ const logger& m_log;
+ eventloop::loop& m_loop;
+ unique_ptr<screen> m_screen;
+ unique_ptr<legacy_tray::tray_manager> m_tray;
+ unique_ptr<renderer> m_renderer;
+ unique_ptr<tags::dispatch> m_dispatch;
+ unique_ptr<tags::action_context> m_action_ctxt;
+
+ bar_settings m_opts{};
+
+ /**
+ * Name of currently active cursor
+ */
+ string m_cursor{};
+
+ string m_lastinput{};
+ std::set<mousebtn> m_dblclicks;
+
+ eventloop::timer_handle_t m_leftclick_timer{m_loop.handle<eventloop::TimerHandle>()};
+ eventloop::timer_handle_t m_middleclick_timer{m_loop.handle<eventloop::TimerHandle>()};
+ eventloop::timer_handle_t m_rightclick_timer{m_loop.handle<eventloop::TimerHandle>()};
+ eventloop::timer_handle_t m_dim_timer{m_loop.handle<eventloop::TimerHandle>()};
+
+ bool m_visible{true};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <map>
+#include <unordered_set>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "tags/types.hpp"
+POLYBAR_NS
+
+using std::map;
+
+// fwd decl
+using namespace drawtypes;
+namespace modules {
+ struct module_interface;
+}
+
+class builder {
+ public:
+ explicit builder(const bar_settings& bar);
+
+ void reset();
+ string flush();
+ void node(const string& str);
+ void node(const string& str, int font_index);
+ void node(const label_t& label);
+ void node_repeat(const label_t& label, size_t n);
+ void offset(extent_val pixels = ZERO_PX_EXTENT);
+ void spacing(spacing_val size);
+ void font(int index);
+ void font_close();
+ void background(rgba color);
+ void background_close();
+ void foreground(rgba color);
+ void foreground_close();
+ void overline(const rgba& color);
+ void overline_close();
+ void underline(const rgba& color);
+ void underline_close();
+ void control(tags::controltag tag);
+ void action(mousebtn index, string action);
+ void action(mousebtn btn, const modules::module_interface& module, string action, string data);
+ void action(mousebtn index, string action, const label_t& label);
+ void action(mousebtn btn, const modules::module_interface& module, string action, string data, const label_t& label);
+ void action_close();
+
+ static string get_spacing_format_string(spacing_val space);
+
+ protected:
+ void append(const string& text);
+
+ void overline_color_close();
+ void underline_color_close();
+
+ void tag_open(tags::syntaxtag tag, const string& value);
+ void tag_open(tags::attribute attr);
+ void tag_close(tags::syntaxtag tag);
+ void tag_close(tags::attribute attr);
+
+ private:
+ const bar_settings& m_bar;
+ string m_output;
+
+ map<tags::syntaxtag, int> m_tags{};
+ std::unordered_set<tags::attribute> m_attrs{};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+namespace command_line {
+ DEFINE_ERROR(argument_error);
+ DEFINE_ERROR(value_error);
+
+ class option;
+ using choices = vector<string>;
+ using options = vector<option>;
+ using values = std::map<string, string>;
+ using posargs = vector<string>;
+
+ // class definition : option {{{
+
+ class option {
+ public:
+ string flag;
+ string flag_long;
+ string desc;
+ string token;
+ const choices values;
+
+ explicit option(string&& flag, string&& flag_long, string&& desc, string&& token = "", const choices&& c = {})
+ : flag(forward<string>(flag))
+ , flag_long(forward<string>(flag_long))
+ , desc(forward<string>(desc))
+ , token(forward<string>(token))
+ , values(forward<const choices>(c)) {}
+ };
+
+ // }}}
+ // class definition : parser {{{
+
+ class parser {
+ public:
+ using make_type = unique_ptr<parser>;
+ static make_type make(string&& scriptname, const options&& opts);
+
+ explicit parser(string&& synopsis, const options&& opts);
+
+ void usage() const;
+
+ void process_input(const vector<string>& values);
+
+ bool has(const string& option) const;
+ bool has(size_t index) const;
+ string get(string opt) const;
+ string get(size_t index) const;
+ bool compare(string opt, const string& val) const;
+ bool compare(size_t index, const string& val) const;
+
+ protected:
+ auto is_short(const string& option, const string& opt_short) const;
+ auto is_long(const string& option, const string& opt_long) const;
+ auto is(const string& option, string opt_short, string opt_long) const;
+
+ auto parse_value(string input, const string& input_next, choices values) const;
+ void parse(const string& input, const string& input_next = "");
+
+ private:
+ string m_synopsis{};
+ const options m_opts;
+ values m_optvalues{};
+ posargs m_posargs{};
+ bool m_skipnext{false};
+ };
+
+ // }}}
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <unordered_map>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/env.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+#if WITH_XRM
+#include "x11/xresources.hpp"
+#endif
+
+POLYBAR_NS
+
+DEFINE_ERROR(value_error);
+DEFINE_ERROR(key_error);
+
+using valuemap_t = std::unordered_map<string, string>;
+using sectionmap_t = std::map<string, valuemap_t>;
+using file_list = vector<string>;
+
+class config {
+ public:
+ explicit config(const logger& logger, string&& path, string&& bar)
+ : m_log(logger), m_file(move(path)), m_barname(move(bar)){};
+
+ const string& filepath() const;
+ string section() const;
+
+ static constexpr const char* BAR_PREFIX = "bar/";
+
+ /**
+ * @brief Instruct the config to connect to the xresource manager
+ */
+ void use_xrm();
+
+ void set_sections(sectionmap_t sections);
+
+ void set_included(file_list included);
+
+ file_list get_included_files() const;
+
+ void warn_deprecated(const string& section, const string& key, string replacement = "") const;
+
+ /**
+ * Returns true if a given parameter exists
+ */
+ bool has(const string& section, const string& key) const;
+
+ /**
+ * Set parameter value
+ */
+ void set(const string& section, const string& key, string&& value);
+
+ /**
+ * Get parameter for the current bar by name
+ */
+ template <typename T = string>
+ T get(const string& key) const {
+ return get<T>(section(), key);
+ }
+
+ /**
+ * Get value of a variable by section and parameter name
+ */
+ template <typename T = string>
+ T get(const string& section, const string& key) const {
+ auto it = m_sections.find(section);
+ if (it == m_sections.end()) {
+ throw key_error("Missing section \"" + section + "\"");
+ }
+ if (it->second.find(key) == it->second.end()) {
+ throw key_error("Missing parameter \"" + section + "." + key + "\"");
+ }
+ return convert<T>(dereference(section, key, it->second.at(key)));
+ }
+
+ /**
+ * Get value of a variable by section and parameter name
+ * with a default value in case the parameter isn't defined
+ */
+ template <typename T = string>
+ T get(const string& section, const string& key, const T& default_value) const {
+ try {
+ string string_value{get<string>(section, key)};
+ return convert<T>(dereference(move(section), move(key), move(string_value)));
+ } catch (const key_error& err) {
+ return default_value;
+ } catch (const std::exception& err) {
+ m_log.err("Invalid value for \"%s.%s\", using default value (reason: %s)", section, key, err.what());
+ return default_value;
+ }
+ }
+
+ /**
+ * Get list of key-value pairs starting with a prefix by section.
+ *
+ * Eg: if you have in config `env-FOO = bar`,
+ * get_with_prefix(section, "env-") will return [{"FOO", "bar"}]
+ */
+ template <typename T = string>
+ vector<pair<string, T>> get_with_prefix(const string& section, const string& key_prefix) const {
+ auto it = m_sections.find(section);
+ if (it == m_sections.end()) {
+ throw key_error("Missing section \"" + section + "\"");
+ }
+
+ vector<pair<string, T>> list;
+ for (const auto& kv_pair : it->second) {
+ const auto& key = kv_pair.first;
+
+ if (key.substr(0, key_prefix.size()) == key_prefix) {
+ const T& val = get<T>(section, key);
+ list.emplace_back(key.substr(key_prefix.size()), val);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Get list of values for the current bar by name
+ */
+ template <typename T = string>
+ vector<T> get_list(const string& key) const {
+ return get_list<T>(section(), key);
+ }
+
+ /**
+ * Get list of values by section and parameter name
+ */
+ template <typename T = string>
+ vector<T> get_list(const string& section, const string& key) const {
+ vector<T> results;
+
+ while (true) {
+ try {
+ string string_value{get<string>(section, key + "-" + to_string(results.size()))};
+
+ if (!string_value.empty()) {
+ results.emplace_back(convert<T>(dereference(section, key, move(string_value))));
+ } else {
+ results.emplace_back(convert<T>(move(string_value)));
+ }
+ } catch (const key_error& err) {
+ break;
+ }
+ }
+
+ if (results.empty()) {
+ throw key_error("Missing parameter \"" + section + "." + key + "-0\"");
+ }
+
+ return results;
+ }
+
+ /**
+ * Get list of values by section and parameter name
+ * with a default list in case the list isn't defined
+ */
+ template <typename T = string>
+ vector<T> get_list(const string& section, const string& key, const vector<T>& default_value) const {
+ vector<T> results;
+
+ while (true) {
+ try {
+ string string_value{get<string>(section, key + "-" + to_string(results.size()))};
+
+ if (!string_value.empty()) {
+ results.emplace_back(convert<T>(dereference(section, key, move(string_value))));
+ } else {
+ results.emplace_back(convert<T>(move(string_value)));
+ }
+ } catch (const key_error& err) {
+ break;
+ } catch (const std::exception& err) {
+ m_log.err("Invalid value in list \"%s.%s\", using list as-is (reason: %s)", section, key, err.what());
+ return default_value;
+ }
+ }
+
+ if (!results.empty()) {
+ return results;
+ ;
+ }
+
+ return default_value;
+ }
+
+ /**
+ * Attempt to load value using the deprecated key name. If successful show a
+ * warning message. If it fails load the value using the new key and given
+ * fallback value
+ */
+ template <typename T = string>
+ T deprecated(const string& section, const string& old, const string& newkey, const T& fallback) const {
+ try {
+ T value{get<T>(section, old)};
+ warn_deprecated(section, old, newkey);
+ return value;
+ } catch (const key_error& err) {
+ return get<T>(section, newkey, fallback);
+ } catch (const std::exception& err) {
+ m_log.err("Invalid value for \"%s.%s\", using fallback key \"%s.%s\" (reason: %s)", section, old, section, newkey,
+ err.what());
+ return get<T>(section, newkey, fallback);
+ }
+ }
+
+ protected:
+ void copy_inherited();
+
+ template <typename T>
+ T convert(string&& value) const;
+
+ /**
+ * Dereference value reference
+ */
+ string dereference(const string& section, const string& key, const string& var) const;
+
+ /**
+ * Dereference local value reference defined using:
+ * ${root.key}
+ * ${root.key:fallback}
+ * ${self.key}
+ * ${self.key:fallback}
+ * ${section.key}
+ * ${section.key:fallback}
+ */
+ string dereference_local(string section, const string& key, const string& current_section) const;
+
+ /**
+ * Dereference environment variable reference defined using:
+ * ${env:key}
+ * ${env:key:fallback value}
+ */
+ string dereference_env(string var) const;
+
+ /**
+ * Dereference X resource db value defined using:
+ * ${xrdb:key}
+ * ${xrdb:key:fallback value}
+ */
+ string dereference_xrdb(string var) const;
+
+ /**
+ * Dereference file reference by reading its contents
+ * ${file:/absolute/file/path}
+ * ${file:/absolute/file/path:fallback value}
+ */
+ string dereference_file(string var) const;
+
+ private:
+ const logger& m_log;
+ string m_file;
+ string m_barname;
+ sectionmap_t m_sections{};
+
+ /**
+ * Absolute path of all files that were parsed in the process of parsing the
+ * config (Path of the main config file also included)
+ */
+ file_list m_included;
+#if WITH_XRM
+ unique_ptr<xresource_manager> m_xrm;
+#endif
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <set>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(parser_error);
+
+/**
+ * @brief Exception object for syntax errors
+ *
+ * Contains filepath and line number where syntax error was found
+ */
+class syntax_error : public parser_error {
+ public:
+ /**
+ * Default values are used when the thrower doesn't know the position.
+ * parse_line has to catch, set the proper values and rethrow
+ */
+ explicit syntax_error(string msg, const string& file = "", int line_no = -1)
+ : parser_error(file + ":" + to_string(line_no) + ": " + msg), msg(move(msg)) {}
+
+ const string& get_msg() {
+ return msg;
+ };
+
+ private:
+ string msg;
+};
+
+class invalid_name_error : public syntax_error {
+ public:
+ /**
+ * type is either Header or Key
+ */
+ invalid_name_error(const string& type, const string& name)
+ : syntax_error(type + " name '" + name + "' is empty or contains forbidden characters.") {}
+
+ invalid_name_error(const string& type, const string& name, const string& file, int line_no)
+ : syntax_error(type + " name '" + name + "' is empty or contains forbidden characters.", file, line_no) {}
+};
+
+/**
+ * @brief All different types a line in a config can be
+ */
+enum class line_type { KEY, HEADER, COMMENT, EMPTY, UNKNOWN };
+
+/**
+ * @brief Storage for a single config line
+ *
+ * More sanitized than the actual string of the comment line, with information
+ * about line type and structure
+ */
+struct line_t {
+ /**
+ * Whether or not this struct represents a "useful" line, a line that has
+ * any semantic significance (key-value or header line)
+ * If false all other fields are not set.
+ * Set this to false, if you want to return a line that has no effect
+ * (for example when you parse a comment line)
+ */
+ bool useful;
+
+ /**
+ * Index of the config_parser::files vector where this line is from
+ */
+ int file_index;
+ int line_no;
+
+ /**
+ * We access header, if is_header == true otherwise we access key, value
+ */
+ bool is_header;
+
+ /**
+ * Only set for header lines
+ */
+ string header;
+
+ /**
+ * Only set for key-value lines
+ */
+ string key, value;
+};
+
+class config_parser {
+ public:
+ config_parser(const logger& logger, string&& file);
+ /**
+ * This prevents passing a temporary logger to the constructor because that would be UB, as the temporary would be
+ * destroyed once the constructor returns.
+ */
+ config_parser(logger&& logger, string&& file) = delete;
+
+ /**
+ * @brief Performs the parsing of the main config file m_file
+ *
+ * @returns config class instance populated with the parsed config
+ *
+ * @throws syntax_error If there was any kind of syntax error
+ * @throws parser_error If aynthing else went wrong
+ */
+ config parse(string barname);
+
+ protected:
+ /**
+ * @brief Converts the `lines` vector to a proper sectionmap
+ */
+ sectionmap_t create_sectionmap();
+
+ /**
+ * @brief Parses the given file, extracts key-value pairs and section
+ * headers and adds them onto the `lines` vector
+ *
+ * This method directly resolves `include-file` directives and checks for
+ * cyclic dependencies
+ *
+ * `file` is expected to be an already resolved absolute path
+ */
+ void parse_file(const string& file, file_list path);
+
+ /**
+ * @brief Parses the given line string to the given line_t struct
+ *
+ * We use the INI file syntax (https://en.wikipedia.org/wiki/INI_file)
+ * Whitespaces (tested with isspace()) at the beginning and end of a line are ignored
+ * Keys and section names can contain any character except for the following:
+ * - spaces
+ * - equal sign (=)
+ * - semicolon (;)
+ * - pound sign (#)
+ * - Any kind of parentheses ([](){})
+ * - colon (:)
+ * - period (.)
+ * - dollar sign ($)
+ * - backslash (\)
+ * - percent sign (%)
+ * - single and double quotes ('")
+ * So basically any character that has any kind of special meaning is prohibited.
+ *
+ * Comment lines have to start with a semicolon (;) or a pound sign (#),
+ * you cannot put a comment after another type of line.
+ *
+ * key and section names are case-sensitive.
+ *
+ * Keys are specified as `key = value`, spaces around the equal sign, as
+ * well as double quotes around the value are ignored
+ *
+ * sections are defined as [section], everything inside the square brackets is part of the name
+ *
+ * @throws syntax_error if the line isn't well formed. The syntax error
+ * does not contain the filename or line numbers because parse_line
+ * doesn't know about those. Whoever calls parse_line needs to
+ * catch those exceptions and set the file path and line number
+ */
+ void parse_line(line_t& line, const string& line_str);
+
+ /**
+ * @brief Determines the type of a line read from a config file
+ *
+ * Expects that line is trimmed
+ * This mainly looks at the first character and doesn't check if the line is
+ * actually syntactically correct.
+ * HEADER ('['), COMMENT (';' or '#') and EMPTY (None) are uniquely
+ * identified by their first character (or lack thereof). Any line that
+ * is none of the above and contains an equal sign, is treated as KEY.
+ * All others are UNKNOWN
+ */
+ static line_type get_line_type(const string& line);
+
+ /**
+ * @brief Parse a line containing a section header and returns the header name
+ *
+ * Only assumes that the line starts with '[' and is trimmed
+ *
+ * @throws syntax_error if the line doesn't end with ']' or the header name
+ * contains forbidden characters
+ */
+ string parse_header(const line_t& line, const string& line_str);
+
+ /**
+ * @brief Parses a line containing a key-value pair and returns the key name
+ * and the value string inside an std::pair
+ *
+ * Only assumes that the line contains '=' at least once and is trimmed
+ *
+ * @throws syntax_error if the key contains forbidden characters
+ */
+ std::pair<string, string> parse_key(const line_t& line, const string& line_str);
+
+ /**
+ * @brief Parses the given value, checks if the given value contains
+ * one or more unescaped backslashes and logs an error if yes
+ */
+ string parse_escaped_value(const line_t& line, string&& value, const string& key);
+
+ /**
+ * @brief Name of all the files the config includes values from
+ *
+ * The line_t struct uses indices to this vector to map lines to their
+ * original files. This allows us to point the user to the exact location
+ * of errors
+ */
+ file_list m_files;
+
+ private:
+ /**
+ * @brief Checks if the given name doesn't contain any spaces or characters
+ * in config_parser::m_forbidden_chars
+ */
+ bool is_valid_name(const string& name);
+
+ vector<string> get_bars(const sectionmap_t& sections) const;
+
+ /**
+ * @brief Whether or not an xresource manager should be used
+ *
+ * Is set to true if any ${xrdb...} references are found
+ */
+ bool use_xrm{false};
+
+ const logger& m_log;
+
+ /**
+ * @brief Absolute path to the main config file
+ */
+ string m_config_file;
+
+ /**
+ * @brief List of all the lines in the config (with included files)
+ *
+ * The order here matters, as we have not yet associated key-value pairs
+ * with sections
+ */
+ vector<line_t> m_lines;
+
+ /**
+ * @brief None of these characters can be used in the key and section names
+ */
+ const string m_forbidden_chars{"\"'=;#[](){}:.$\\%"};
+
+ /**
+ * @brief List of names that cannot be used as section names
+ *
+ * These strings have a special meaning inside references and so the
+ * section [self] could never be referenced.
+ *
+ * Note: BAR is deprecated
+ */
+ const std::set<string> m_reserved_section_names = {"self", "BAR", "root"};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <mutex>
+#include <queue>
+
+#include "common.hpp"
+#include "components/eventloop.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "settings.hpp"
+#include "utils/actions.hpp"
+#include "utils/file.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+using std::queue;
+
+// fwd decl {{{
+
+enum class alignment;
+class bar;
+class config;
+class connection;
+class inotify_watch;
+class logger;
+class signal_emitter;
+namespace modules {
+ struct module_interface;
+} // namespace modules
+using module_t = shared_ptr<modules::module_interface>;
+using modulemap_t = std::map<alignment, vector<module_t>>;
+// }}}
+
+class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eventqueue::exit_reload,
+ signals::eventqueue::notify_change, signals::eventqueue::notify_forcechange,
+ signals::eventqueue::check_state, signals::ipc::action, signals::ipc::command,
+ signals::ipc::hook, signals::ui::button_press, signals::ui::update_background> {
+ public:
+ using make_type = unique_ptr<controller>;
+ static make_type make(bool has_ipc, eventloop::loop&, const config&);
+
+ explicit controller(connection&, signal_emitter&, const logger&, const config&, bool has_ipc, eventloop::loop&);
+ ~controller();
+
+ bool run(bool writeback, string snapshot_dst, bool confwatch);
+
+ void trigger_action(string&& input_data);
+ void trigger_quit(bool reload);
+ void trigger_update(bool force);
+
+ void stop(bool reload);
+
+ void signal_handler(int signum);
+
+ void conn_cb();
+ void create_config_watcher(const string& filename);
+ void confwatch_handler(const char* fname);
+ void notifier_handler();
+ void screenshot_handler();
+
+ protected:
+ void trigger_notification();
+ void start_modules();
+ void read_events(bool confwatch);
+ void process_inputdata(string&& cmd);
+ bool process_update(bool force);
+
+ void update_reload(bool reload);
+
+ bool on(const signals::eventqueue::notify_change& evt) override;
+ bool on(const signals::eventqueue::notify_forcechange& evt) override;
+ bool on(const signals::eventqueue::exit_reload& evt) override;
+ bool on(const signals::eventqueue::check_state& evt) override;
+ bool on(const signals::ui::button_press& evt) override;
+ bool on(const signals::ipc::action& evt) override;
+ bool on(const signals::ipc::command& evt) override;
+ bool on(const signals::ipc::hook& evt) override;
+ bool on(const signals::ui::update_background& evt) override;
+
+ private:
+ struct notifications_t {
+ bool quit;
+ bool reload;
+ bool update;
+ bool force_update;
+ queue<string> inputdata;
+
+ notifications_t() : quit(false), reload(false), update(false), force_update(false), inputdata(queue<string>{}) {}
+ };
+
+ size_t setup_modules(alignment align);
+
+ bool forward_action(const actions_util::action& cmd);
+ bool try_forward_legacy_action(const string& cmd);
+
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+ const config& m_conf;
+ eventloop::loop& m_loop;
+ unique_ptr<bar> m_bar;
+ bool m_has_ipc;
+ string m_tray_module_name;
+
+ /**
+ * @brief Async handle to notify the eventloop
+ *
+ * This handle is used to notify the eventloop of changes which are not otherwise covered by other handles.
+ * E.g. click actions.
+ */
+ eventloop::async_handle_t m_notifier{m_loop.handle<eventloop::AsyncHandle>([this]() { notifier_handler(); })};
+
+ /**
+ * Notification data for the controller.
+ *
+ * Triggers, potentially from other threads, update this structure and notify the controller through m_notifier.
+ */
+ notifications_t m_notifications{};
+
+ /**
+ * @brief Protected m_notifications.
+ *
+ * All accesses to m_notifications must hold this mutex.
+ */
+ std::mutex m_notification_mutex{};
+
+ /**
+ * @brief Destination path of generated snapshot
+ */
+ string m_snapshot_dst;
+
+ /**
+ * @brief Controls weather the output gets printed to stdout
+ */
+ bool m_writeback{false};
+
+ /**
+ * @brief Loaded modules
+ */
+ vector<module_t> m_modules;
+
+ /**
+ * @brief Loaded modules grouped by block
+ */
+ modulemap_t m_blocks;
+
+ /**
+ * @brief Flag to trigger reload after shutdown
+ */
+ bool m_reload{false};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <uv.h>
+
+#include <stdexcept>
+#include <utility>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace eventloop {
+/**
+ * Runs any libuv function with an integer error code return value and throws an
+ * exception on error.
+ */
+#define UV(fun, ...) \
+ do { \
+ int res = fun(__VA_ARGS__); \
+ if (res < 0) { \
+ throw std::runtime_error(__FILE__ ":"s + std::to_string(__LINE__) + \
+ ": libuv error for '" #fun "(" #__VA_ARGS__ ")': "s + uv_strerror(res)); \
+ } \
+ } while (0);
+
+ using cb_void = function<void(void)>;
+
+ template <typename Event>
+ using cb_event = std::function<void(const Event&)>;
+
+ template <typename Self, typename H>
+ class Handle : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ Handle(uv_loop_t* l) : uv_loop(l) {
+ get()->data = this;
+ }
+
+ void leak(std::shared_ptr<Self> h) {
+ lifetime_extender = std::move(h);
+ }
+
+ void unleak() {
+ reset_callbacks();
+ lifetime_extender.reset();
+ }
+
+ H* raw() {
+ return get();
+ }
+
+ const H* raw() const {
+ return get();
+ }
+
+ /**
+ * Close this handle and free associated memory.
+ *
+ * After this function returns, any reference to this object should be considered invalid.
+ */
+ void close() {
+ if (!is_closing()) {
+ uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast<Self*>(handle->data)); });
+ }
+ }
+
+ bool is_closing() const {
+ return uv_is_closing(this->template get<uv_handle_t>());
+ }
+
+ bool is_active() {
+ return uv_is_active(this->template get<uv_handle_t>()) != 0;
+ }
+
+ protected:
+ ~Handle() = default;
+
+ /**
+ * Resets all callbacks stored in the handle as part of closing the handle.
+ *
+ * This releases any lambda captures, breaking possible cyclic dependencies in shared_ptr.
+ */
+ virtual void reset_callbacks() = 0;
+
+ /**
+ * Generic callback function that can be used for all uv handle callbacks.
+ *
+ * @tparam Event Event class/struct. Must have a constructor that takes all arguments passed to the uv callback,
+ * except for the handle argument.
+ * @tparam Member Pointer to class member where callback function is stored
+ * @tparam Args Additional arguments in the uv callback. Inferred by the compiler
+ */
+ template <typename Event, cb_event<Event> Self::*Member, typename... Args>
+ static void event_cb(H* handle, Args... args) {
+ Self& This = *static_cast<Self*>(handle->data);
+ (This.*Member)(Event{std::forward<Args>(args)...});
+ }
+
+ /**
+ * Same as event_cb except that no event is constructed.
+ */
+ template <cb_void Self::*Member>
+ static void void_event_cb(H* handle) {
+ Self& This = *static_cast<Self*>(handle->data);
+ (This.*Member)();
+ }
+
+ static Self& cast(H* handle) {
+ return *static_cast<Self*>(handle->data);
+ }
+
+ template <typename T = H>
+ T* get() {
+ return reinterpret_cast<T*>(&uv_handle);
+ }
+
+ template <typename T = H>
+ const T* get() const {
+ return reinterpret_cast<const T*>(&uv_handle);
+ }
+
+ uv_loop_t* loop() const {
+ return uv_loop;
+ }
+
+ static void close_callback(Self& self) {
+ self.unleak();
+ }
+
+ static void alloc_callback(uv_handle_t*, size_t, uv_buf_t* buf) {
+ buf->base = new char[BUFSIZ];
+ buf->len = BUFSIZ;
+ }
+
+ private:
+ H uv_handle;
+ uv_loop_t* uv_loop;
+
+ /**
+ * The handle stores the shared_ptr to itself so that it effectively leaks memory.
+ *
+ * This saves us from having to guarantee that the handle's lifetime extends to at least after it is closed.
+ *
+ * Once the handle is closed, either explicitly or by walking all handles when the loop shuts down, this reference
+ * is reset and the object is explicitly destroyed.
+ */
+ std::shared_ptr<Self> lifetime_extender;
+ };
+
+ struct ErrorEvent {
+ int status;
+ };
+
+ using cb_error = cb_event<ErrorEvent>;
+
+ class WriteRequest : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ using cb_write = cb_void;
+
+ WriteRequest(cb_write&& user_cb, cb_error&& err_cb);
+
+ static WriteRequest& create(cb_write&& user_cb, cb_error&& err_cb);
+
+ uv_write_t* get();
+
+ /**
+ * Trigger the write callback.
+ *
+ * After that, this object is destroyed.
+ */
+ void trigger(int status);
+
+ protected:
+ WriteRequest& leak(std::unique_ptr<WriteRequest> h);
+
+ void unleak();
+
+ void reset_callbacks();
+
+ private:
+ uv_write_t req{};
+
+ cb_write write_callback;
+ cb_error write_err_cb;
+
+ /**
+ * The handle stores the unique_ptr to itself so that it effectively leaks memory.
+ *
+ * This means that each instance manages its own lifetime.
+ */
+ std::unique_ptr<WriteRequest> lifetime_extender;
+ };
+
+ struct SignalEvent {
+ int signum;
+ };
+
+ class SignalHandle final : public Handle<SignalHandle, uv_signal_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_event<SignalEvent>;
+
+ void init();
+ void start(int signum, cb&& user_cb);
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ cb callback;
+ };
+
+ struct PollEvent {
+ uv_poll_event event;
+ };
+
+ class PollHandle final : public Handle<PollHandle, uv_poll_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_event<PollEvent>;
+
+ void init(int fd);
+ void start(int events, cb&& user_cb, cb_error&& err_cb);
+ static void poll_callback(uv_poll_t*, int status, int events);
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ cb callback;
+ cb_error err_cb;
+ };
+
+ struct FSEvent {
+ const char* path;
+ uv_fs_event event;
+ };
+
+ class FSEventHandle final : public Handle<FSEventHandle, uv_fs_event_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_event<FSEvent>;
+
+ void init();
+ void start(const string& path, int flags, cb&& user_cb, cb_error&& err_cb);
+ static void fs_event_callback(uv_fs_event_t*, const char* path, int events, int status);
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ cb callback;
+ cb_error err_cb;
+ };
+
+ class TimerHandle final : public Handle<TimerHandle, uv_timer_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_void;
+
+ void init();
+ void start(uint64_t timeout, uint64_t repeat, cb&& user_cb);
+ void stop();
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ cb callback;
+ };
+
+ class AsyncHandle final : public Handle<AsyncHandle, uv_async_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_void;
+
+ void init(cb&& user_cb);
+ void send();
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ cb callback;
+ };
+
+ struct ReadEvent {
+ const char* data;
+ size_t len;
+ };
+
+ template <typename Self, typename H>
+ class StreamHandle : public Handle<Self, H> {
+ public:
+ using Handle<Self, H>::Handle;
+ using cb_read = cb_event<ReadEvent>;
+ using cb_read_eof = cb_void;
+ using cb_connection = cb_void;
+
+ void listen(int backlog, cb_connection&& user_cb, cb_error&& err_cb) {
+ this->connection_callback = std::move(user_cb);
+ this->connection_err_cb = std::move(err_cb);
+ UV(uv_listen, this->template get<uv_stream_t>(), backlog, connection_cb);
+ };
+
+ static void connection_cb(uv_stream_t* server, int status) {
+ auto& self = Self::cast((H*)server);
+
+ if (status == 0) {
+ self.connection_callback();
+ } else {
+ self.connection_err_cb(ErrorEvent{status});
+ }
+ }
+
+ template <typename ClientSelf, typename ClientH>
+ void accept(StreamHandle<ClientSelf, ClientH>& client) {
+ UV(uv_accept, this->template get<uv_stream_t>(), client.template get<uv_stream_t>());
+ }
+
+ void read_start(cb_read&& fun, cb_void&& eof_cb, cb_error&& err_cb) {
+ this->read_callback = std::move(fun);
+ this->read_eof_cb = std::move(eof_cb);
+ this->read_err_cb = std::move(err_cb);
+ UV(uv_read_start, this->template get<uv_stream_t>(), &this->alloc_callback, &read_cb);
+ };
+
+ static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
+ auto& self = Self::cast((H*)handle);
+ /*
+ * Wrap pointer so that it gets automatically freed once the function returns (even with exceptions)
+ */
+ auto buf_ptr = unique_ptr<char[]>(buf->base);
+ if (nread > 0) {
+ self.read_callback(ReadEvent{buf->base, (size_t)nread});
+ } else if (nread < 0) {
+ if (nread != UV_EOF) {
+ self.read_err_cb(ErrorEvent{(int)nread});
+ } else {
+ self.read_eof_cb();
+ }
+ }
+ };
+
+ void write(const vector<uint8_t>& data, WriteRequest::cb_write&& user_cb = {}, cb_error&& err_cb = {}) {
+ WriteRequest& req = WriteRequest::create(std::move(user_cb), std::move(err_cb));
+
+ uv_buf_t buf{(char*)data.data(), data.size()};
+
+ UV(uv_write, req.get(), this->template get<uv_stream_t>(), &buf, 1,
+ [](uv_write_t* r, int status) { static_cast<WriteRequest*>(r->data)->trigger(status); });
+ }
+
+ protected:
+ ~StreamHandle() = default;
+
+ void reset_callbacks() override {
+ read_callback = nullptr;
+ read_eof_cb = nullptr;
+ read_err_cb = nullptr;
+ connection_callback = nullptr;
+ connection_err_cb = nullptr;
+ }
+
+ private:
+ /**
+ * Callback for receiving data
+ */
+ cb_read read_callback;
+
+ /**
+ * Callback for receiving EOF.
+ *
+ * Called after the associated handle has been closed.
+ */
+ cb_read_eof read_eof_cb;
+
+ /**
+ * Called if an error occurs.
+ */
+ cb_error read_err_cb;
+
+ cb_connection connection_callback;
+ cb_error connection_err_cb;
+ };
+
+ class PipeHandle final : public StreamHandle<PipeHandle, uv_pipe_t> {
+ public:
+ using StreamHandle::StreamHandle;
+ using cb_connect = cb_void;
+
+ void init(bool ipc = false);
+ void open(int fd);
+
+ void bind(const string& path);
+
+ void connect(const string& name, cb_connect&& user_cb, cb_error&& err_cb);
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ static void connect_cb(uv_connect_t* req, int status);
+
+ cb_error connect_err_cb;
+ cb_connect connect_callback;
+ };
+
+ class PrepareHandle final : public Handle<PrepareHandle, uv_prepare_t> {
+ public:
+ using Handle::Handle;
+ using cb = cb_void;
+
+ void init();
+ void start(cb&& user_cb);
+
+ protected:
+ void reset_callbacks() override;
+
+ private:
+ static void connect_cb(uv_connect_t* req, int status);
+
+ cb callback;
+ };
+
+ using signal_handle_t = shared_ptr<SignalHandle>;
+ using poll_handle_t = shared_ptr<PollHandle>;
+ using fs_event_handle_t = shared_ptr<FSEventHandle>;
+ using timer_handle_t = shared_ptr<TimerHandle>;
+ using async_handle_t = shared_ptr<AsyncHandle>;
+ using pipe_handle_t = shared_ptr<PipeHandle>;
+ using prepare_handle_t = shared_ptr<PrepareHandle>;
+
+ class loop : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ loop();
+ ~loop();
+ void run();
+ void stop();
+ uint64_t now() const;
+
+ template <typename H, typename... Args>
+ shared_ptr<H> handle(Args&&... args) {
+ auto ptr = make_shared<H>(get());
+ ptr->init(std::forward<Args>(args)...);
+ ptr->leak(ptr);
+ return ptr;
+ }
+
+ uv_loop_t* get() const;
+
+ private:
+ std::unique_ptr<uv_loop_t> m_loop{nullptr};
+ };
+
+} // namespace eventloop
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cstdio>
+#include <map>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+POLYBAR_NS
+
+enum class loglevel {
+ NONE = 0,
+ ERROR,
+ WARNING,
+ NOTICE,
+ INFO,
+ TRACE,
+};
+
+class logger {
+ public:
+ using make_type = const logger&;
+ static make_type make(loglevel level = loglevel::NONE);
+
+ explicit logger(loglevel level);
+
+ const logger& operator=(const logger&) const {
+ return *this;
+ }
+
+ static loglevel parse_verbosity(const string& name, loglevel fallback = loglevel::NONE);
+
+ void verbosity(loglevel level);
+
+#ifdef DEBUG_LOGGER // {{{
+ template <typename... Args>
+ void trace(const string& message, Args&&... args) const {
+ output(loglevel::TRACE, message, std::forward<Args>(args)...);
+ }
+#ifdef DEBUG_LOGGER_VERBOSE
+ template <typename... Args>
+ void trace_x(const string& message, Args&&... args) const {
+ output(loglevel::TRACE, message, std::forward<Args>(args)...);
+ }
+#else
+ template <typename... Args>
+ void trace_x(Args&&...) const {}
+#endif
+#else
+ template <typename... Args>
+ void trace(Args&&...) const {}
+ template <typename... Args>
+ void trace_x(Args&&...) const {}
+#endif // }}}
+
+ /**
+ * Output an info message
+ */
+ template <typename... Args>
+ void info(const string& message, Args&&... args) const {
+ output(loglevel::INFO, message, std::forward<Args>(args)...);
+ }
+
+ /**
+ * Output a notice
+ */
+ template <typename... Args>
+ void notice(const string& message, Args&&... args) const {
+ output(loglevel::NOTICE, message, std::forward<Args>(args)...);
+ }
+
+ /**
+ * Output a warning message
+ */
+ template <typename... Args>
+ void warn(const string& message, Args&&... args) const {
+ output(loglevel::WARNING, message, std::forward<Args>(args)...);
+ }
+
+ /**
+ * Output an error message
+ */
+ template <typename... Args>
+ void err(const string& message, Args&&... args) const {
+ output(loglevel::ERROR, message, std::forward<Args>(args)...);
+ }
+
+ protected:
+ template <typename T>
+ decltype(auto) convert(T&& arg) const {
+ return forward<T>(arg);
+ }
+
+ /**
+ * Convert string
+ */
+ const char* convert(string& arg) const;
+ const char* convert(const string& arg) const;
+
+ /**
+ * Convert thread id
+ */
+ size_t convert(std::thread::id arg) const;
+
+ /**
+ * Write the log message to the output channel
+ * if the defined verbosity level allows it
+ */
+ template <typename... Args>
+ void output(loglevel level, const string& format, Args&&... values) const {
+ if (level > m_level) {
+ return;
+ }
+
+#if defined(__clang__) // {{{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-security"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-security"
+#endif // }}}
+
+ dprintf(m_fd, (m_prefixes.at(level) + format + m_suffixes.at(level) + "\n").c_str(), convert(values)...);
+
+#if defined(__clang__) // {{{
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif // }}}
+ }
+
+ private:
+ /**
+ * Logger verbosity level
+ */
+ loglevel m_level{loglevel::TRACE};
+
+ /**
+ * File descriptor used when writing the log messages
+ */
+ int m_fd{STDERR_FILENO};
+
+ /**
+ * Loglevel specific prefixes
+ */
+ std::map<loglevel, string> m_prefixes;
+
+ /**
+ * Loglevel specific suffixes
+ */
+ std::map<loglevel, string> m_suffixes;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cairo/cairo.h>
+
+#include <bitset>
+#include <memory>
+
+#include "cairo/fwd.hpp"
+#include "common.hpp"
+#include "components/renderer_interface.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "x11/extensions/fwd.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+class connection;
+class config;
+class logger;
+class background_manager;
+class bg_slice;
+// }}}
+
+using std::map;
+
+struct alignment_block {
+ cairo_pattern_t* pattern;
+ /**
+ * The x-position where the next thing will be rendered.
+ */
+ double x;
+ double y;
+ /**
+ * The total width of this block.
+ *
+ * This is always >= x, but may be larger because a negative offset may
+ * decrease x, but the width doesn't change.
+ */
+ double width;
+};
+
+class renderer : public renderer_interface,
+ public signal_receiver<SIGN_PRIORITY_RENDERER, signals::ui::request_snapshot> {
+ public:
+ using make_type = unique_ptr<renderer>;
+ static make_type make(const bar_settings& bar, tags::action_context& action_ctxt, const config&);
+
+ explicit renderer(connection& conn, signal_emitter& sig, const config&, const logger& logger, const bar_settings& bar,
+ background_manager& background_manager, tags::action_context& action_ctxt);
+ ~renderer();
+
+ xcb_window_t window() const;
+ xcb_visualtype_t* visual() const;
+ int depth() const;
+
+ void begin(xcb_rectangle_t rect);
+ void end();
+ void flush();
+
+ void render_offset(const tags::context& ctxt, const extent_val offset) override;
+ void render_text(const tags::context& ctxt, const string&&) override;
+
+ void change_alignment(const tags::context& ctxt) override;
+
+ double get_x(const tags::context& ctxt) const override;
+
+ double get_alignment_start(const alignment align) const override;
+
+ void apply_tray_position(const tags::context& context) override;
+
+ protected:
+ void fill_background();
+ void fill_overline(rgba color, double x, double w);
+ void fill_underline(rgba color, double x, double w);
+ void fill_borders();
+ void draw_offset(const tags::context& ctxt, rgba color, double x, double w);
+
+ double block_x(alignment a) const;
+ double block_y(alignment a) const;
+ double block_w(alignment a) const;
+ double block_h(alignment a) const;
+
+ void increase_x(double dx);
+
+ void flush(alignment a);
+ void highlight_clickable_areas();
+
+ bool on(const signals::ui::request_snapshot& evt) override;
+
+ protected:
+ struct reserve_area {
+ edge side{edge::NONE};
+ unsigned int size{0U};
+ };
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const config& m_conf;
+ const logger& m_log;
+ const bar_settings& m_bar;
+ std::shared_ptr<bg_slice> m_background;
+
+ int m_depth{-1};
+ xcb_window_t m_window;
+ xcb_colormap_t m_colormap{XCB_NONE};
+ xcb_visualtype_t* m_visual;
+ xcb_gcontext_t m_gcontext;
+
+ /**
+ * Background pixmap for the bar window
+ *
+ * All bar contents are rendered onto this.
+ */
+ xcb_pixmap_t m_pixmap;
+
+ xcb_rectangle_t m_rect{0, 0, 0U, 0U};
+ reserve_area m_cleararea{};
+
+ unique_ptr<cairo::context> m_context;
+ unique_ptr<cairo::xcb_surface> m_surface;
+ map<alignment, alignment_block> m_blocks;
+ cairo_pattern_t* m_cornermask{};
+
+ cairo_operator_t m_comp_bg{CAIRO_OPERATOR_SOURCE};
+ cairo_operator_t m_comp_fg{CAIRO_OPERATOR_OVER};
+ cairo_operator_t m_comp_ol{CAIRO_OPERATOR_OVER};
+ cairo_operator_t m_comp_ul{CAIRO_OPERATOR_OVER};
+ cairo_operator_t m_comp_border{CAIRO_OPERATOR_OVER};
+ bool m_pseudo_transparency{false};
+
+ alignment m_align;
+
+ bool m_fixedcenter;
+ string m_snapshot_dst;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+#include <map>
+
+#include "common.hpp"
+#include "tags/action_context.hpp"
+#include "tags/context.hpp"
+POLYBAR_NS
+
+class renderer_interface {
+ public:
+ renderer_interface(const tags::action_context& action_ctxt) : m_action_ctxt(action_ctxt){};
+
+ virtual void render_offset(const tags::context& ctxt, const extent_val offset) = 0;
+ virtual void render_text(const tags::context& ctxt, const string&& str) = 0;
+ virtual void change_alignment(const tags::context& ctxt) = 0;
+
+ /**
+ * Get the current x-coordinate of the renderer.
+ *
+ * This position denotes the coordinate where the next thing will be rendered.
+ * It is relative to the start of the current alignment because the absolute
+ * positions may not be known until after the renderer has finished.
+ */
+ virtual double get_x(const tags::context& ctxt) const = 0;
+
+ /**
+ * Get the absolute x-position of the start of an alignment block.
+ *
+ * The position is absolute in terms of the bar window.
+ *
+ * Only call this after all the rendering is finished as these values change
+ * when new things are rendered.
+ */
+ virtual double get_alignment_start(const alignment align) const = 0;
+
+ virtual void apply_tray_position(const tags::context& context) = 0;
+
+ protected:
+ /**
+ * Stores information about actions in the current render cycle.
+ */
+ const tags::action_context& m_action_ctxt;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "events/signal_emitter.hpp"
+#include "events/signal_fwd.hpp"
+#include "x11/extensions/randr.hpp"
+#include "x11/types.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+// fwd
+class config;
+class logger;
+class connection;
+class signal_emitter;
+
+class screen : public xpp::event::sink<evt::map_notify, evt::randr_screen_change_notify> {
+ public:
+ using make_type = unique_ptr<screen>;
+ static make_type make(const config&);
+
+ explicit screen(connection& conn, signal_emitter& emitter, const logger& logger, const config& conf);
+ ~screen();
+
+ protected:
+ void handle(const evt::map_notify& evt) override;
+ void handle(const evt::randr_screen_change_notify& evt) override;
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+ const config& m_conf;
+
+ xcb_window_t m_root;
+ xcb_window_t m_proxy{XCB_NONE};
+
+ vector<monitor_t> m_monitors;
+ struct size m_size {
+ 0U, 0U
+ };
+ bool m_sigraised{false};
+
+ /**
+ * Original event mask on the root window.
+ * Used to restore event mask after the proxy window is mapped.
+ */
+ uint32_t m_root_mask{0};
+
+ bool have_monitors_changed() const;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "common.hpp"
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+struct randr_output;
+using monitor_t = shared_ptr<randr_output>;
+
+namespace drawtypes {
+ class label;
+}
+
+using label_t = shared_ptr<drawtypes::label>;
+// }}}
+
+struct enum_hash {
+ template <typename T>
+ inline typename std::enable_if<std::is_enum<T>::value, size_t>::type operator()(T const value) const {
+ return static_cast<size_t>(value);
+ }
+};
+
+enum class edge { NONE = 0, TOP, BOTTOM, LEFT, RIGHT, ALL };
+
+enum class alignment { NONE = 0, LEFT, CENTER, RIGHT };
+
+enum class mousebtn {
+ NONE = 0,
+ LEFT,
+ MIDDLE,
+ RIGHT,
+ SCROLL_UP,
+ SCROLL_DOWN,
+ DOUBLE_LEFT,
+ DOUBLE_MIDDLE,
+ DOUBLE_RIGHT,
+ // Terminator value, do not use
+ BTN_COUNT,
+};
+
+static inline mousebtn mousebtn_get_double(mousebtn btn) {
+ switch (btn) {
+ case mousebtn::LEFT:
+ return mousebtn::DOUBLE_LEFT;
+ case mousebtn::MIDDLE:
+ return mousebtn::DOUBLE_MIDDLE;
+ case mousebtn::RIGHT:
+ return mousebtn::DOUBLE_RIGHT;
+ default:
+ return mousebtn::NONE;
+ }
+}
+
+/**
+ * Order of values for _NET_WM_STRUT_PARTIAL
+ *
+ * https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45381391268672
+ */
+enum class strut {
+ LEFT = 0,
+ RIGHT,
+ TOP,
+ BOTTOM,
+ LEFT_START_Y,
+ LEFT_END_Y,
+ RIGHT_START_Y,
+ RIGHT_END_Y,
+ TOP_START_X,
+ TOP_END_X,
+ BOTTOM_START_X,
+ BOTTOM_END_X,
+};
+
+struct position {
+ int x{0};
+ int y{0};
+
+ bool operator==(const position& other) {
+ return this->x == other.x && this->y == other.y;
+ }
+};
+
+struct size {
+ unsigned w{1U};
+ unsigned h{1U};
+};
+
+enum class spacing_type { SPACE, POINT, PIXEL };
+
+enum class extent_type { POINT, PIXEL };
+
+struct spacing_val {
+ spacing_type type{spacing_type::SPACE};
+ /**
+ * Numerical spacing value. Is truncated to an integer for pixels and spaces.
+ * Must be non-negative.
+ */
+ float value{0};
+
+ /**
+ * Any non-positive number is interpreted as no spacing.
+ */
+ operator bool() const {
+ return value > 0;
+ }
+};
+
+static constexpr spacing_val ZERO_SPACE = {spacing_type::SPACE, 0};
+
+/*
+ * Defines the signed length of something as either a number of pixels or points.
+ *
+ * Used for widths, heights, and offsets
+ */
+struct extent_val {
+ extent_type type{extent_type::PIXEL};
+ float value{0};
+
+ operator bool() const {
+ return value != 0;
+ }
+};
+
+static constexpr extent_val ZERO_PX_EXTENT = {extent_type::PIXEL, 0};
+
+struct side_values {
+ spacing_val left{ZERO_SPACE};
+ spacing_val right{ZERO_SPACE};
+};
+
+struct percentage_with_offset {
+ double percentage{0};
+ extent_val offset{ZERO_PX_EXTENT};
+};
+
+struct radius {
+ double top_left{0.0};
+ double top_right{0.0};
+ double bottom_left{0.0};
+ double bottom_right{0.0};
+
+ operator bool() const {
+ return top_left != 0.0 || top_right != 0.0 || bottom_left != 0.0 || bottom_right != 0.0;
+ }
+};
+
+struct border_settings {
+ rgba color{0xFF000000};
+ unsigned int size{0U};
+};
+
+struct line_settings {
+ rgba color{0xFF000000};
+ unsigned int size{0U};
+};
+
+struct action {
+ mousebtn button{mousebtn::NONE};
+ string command{};
+};
+
+/**
+ * Settings specific to the X window system.
+ */
+struct x_settings {
+ xcb_window_t window{XCB_NONE};
+ xcb_visualtype_t* visual{nullptr};
+ int depth{-1};
+};
+
+struct bar_settings {
+ explicit bar_settings() = default;
+ bar_settings(const bar_settings& other) = default;
+
+ x_settings x_data;
+
+ monitor_t monitor{};
+ bool monitor_strict{false};
+ bool monitor_exact{true};
+ bool bottom{false};
+ struct size size {
+ 1U, 1U
+ };
+
+ double dpi_x{0.};
+ double dpi_y{0.};
+
+ position pos{0, 0};
+ position offset{0, 0};
+ side_values padding{ZERO_SPACE, ZERO_SPACE};
+ side_values module_margin{ZERO_SPACE, ZERO_SPACE};
+ bool struts{true};
+ struct {
+ int top;
+ int bottom;
+ } strut{0, 0};
+
+ rgba background{0xFF000000};
+ rgba foreground{0xFFFFFFFF};
+ vector<rgba> background_steps;
+
+ line_settings underline{};
+ line_settings overline{};
+
+ std::unordered_map<edge, border_settings, enum_hash> borders{};
+
+ struct radius radius {};
+ /**
+ * TODO deprecated
+ */
+ spacing_val spacing{ZERO_SPACE};
+ label_t separator{};
+
+ string wmname{};
+ string locale{};
+
+ bool override_redirect{false};
+
+ int double_click_interval{400};
+
+ /**
+ * Name of cursor to use for clickable areas
+ */
+ string cursor_click{};
+
+ /**
+ * Name of cursor to use for scrollable areas
+ */
+ string cursor_scroll{};
+
+ vector<action> actions{};
+
+ bool dimmed{false};
+ double dimvalue{1.0};
+
+ const xcb_rectangle_t inner_area(bool abspos = false) const {
+ xcb_rectangle_t rect = this->outer_area(abspos);
+
+ if (borders.find(edge::TOP) != borders.end()) {
+ rect.y += borders.at(edge::TOP).size;
+ rect.height -= borders.at(edge::TOP).size;
+ }
+ if (borders.find(edge::BOTTOM) != borders.end()) {
+ rect.height -= borders.at(edge::BOTTOM).size;
+ }
+ if (borders.find(edge::LEFT) != borders.end()) {
+ rect.x += borders.at(edge::LEFT).size;
+ rect.width -= borders.at(edge::LEFT).size;
+ }
+ if (borders.find(edge::RIGHT) != borders.end()) {
+ rect.width -= borders.at(edge::RIGHT).size;
+ }
+ return rect;
+ }
+
+ const xcb_rectangle_t outer_area(bool abspos = false) const {
+ xcb_rectangle_t rect{0, 0, 0, 0};
+ rect.width += size.w;
+ rect.height += size.h;
+
+ if (abspos) {
+ rect.x = pos.x;
+ rect.y = pos.y;
+ }
+
+ return rect;
+ }
+};
+
+struct event_timer {
+ xcb_timestamp_t event{0L};
+ xcb_timestamp_t offset{1L};
+
+ bool allow(xcb_timestamp_t time) {
+ bool pass = time >= event + offset;
+ event = time;
+ return pass;
+ };
+
+ bool deny(xcb_timestamp_t time) {
+ return !allow(time);
+ };
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#ifndef DEBUG
+#error "Not a debug build..."
+#endif
+
+#include <chrono>
+#include <cstdio>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace debug_util {
+ /**
+ * Wrapper that starts tracking the time when created
+ * and reports the duration when it goes out of scope
+ */
+ class scope_timer {
+ public:
+ using clock_t = std::chrono::high_resolution_clock;
+ using duration_t = std::chrono::milliseconds;
+
+ explicit scope_timer() : m_start(clock_t::now()) {}
+ ~scope_timer() {
+ printf("%lums\n", std::chrono::duration_cast<duration_t>(clock_t::now() - m_start).count());
+ }
+
+ private:
+ clock_t::time_point m_start;
+ };
+
+ inline unique_ptr<scope_timer> make_scope_timer() {
+ return make_unique<scope_timer>();
+ }
+
+ template <class T>
+ void execution_speed(const T& expr) noexcept {
+ auto start = std::chrono::high_resolution_clock::now();
+ expr();
+ auto finish = std::chrono::high_resolution_clock::now();
+ printf("execution speed: %lums\n", std::chrono::duration_cast<std::chrono::milliseconds>(finish - start).count());
+ }
+
+ template <class T>
+ void memory_usage(const T& object) noexcept {
+ printf("memory usage: %lub\n", sizeof(object));
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <atomic>
+#include <chrono>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+namespace drawtypes {
+ class animation : public non_copyable_mixin {
+ public:
+ explicit animation(unsigned int framerate_ms) : m_framerate_ms(framerate_ms) {}
+ explicit animation(vector<label_t>&& frames, int framerate_ms)
+ : m_frames(move(frames))
+ , m_framerate_ms(framerate_ms)
+ , m_framecount(m_frames.size())
+ , m_frame(m_frames.size() - 1) {}
+
+ void add(label_t&& frame);
+ void increment();
+
+ label_t get() const;
+ unsigned int framerate() const;
+
+ explicit operator bool() const;
+
+ protected:
+ vector<label_t> m_frames;
+
+ unsigned int m_framerate_ms = 1000;
+ size_t m_framecount = 0;
+ std::atomic_size_t m_frame{0_z};
+ };
+
+ using animation_t = shared_ptr<animation>;
+
+ animation_t load_animation(
+ const config& conf, const string& section, string name = "animation", bool required = true);
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ class iconset : public non_copyable_mixin {
+ public:
+ void add(string id, label_t&& icon);
+ bool has(const string& id);
+ label_t get(const string& id, const string& fallback_id = "", bool fuzzy_match = false);
+ operator bool();
+
+ protected:
+ std::map<string, label_t> m_icons;
+ };
+
+ using iconset_t = shared_ptr<iconset>;
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cassert>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ struct token {
+ string token;
+ size_t min{0_z};
+ size_t max{0_z};
+ string suffix{""s};
+ bool zpad{false};
+ bool rpadding{false};
+ };
+
+ class label : public non_copyable_mixin {
+ public:
+ rgba m_foreground{};
+ rgba m_background{};
+ rgba m_underline{};
+ rgba m_overline{};
+ int m_font{0};
+ side_values m_padding{ZERO_SPACE, ZERO_SPACE};
+ side_values m_margin{ZERO_SPACE, ZERO_SPACE};
+
+ size_t m_minlen{0};
+ /*
+ * If m_ellipsis is true, m_maxlen MUST be larger or equal to the length of
+ * the ellipsis (3), everything else is a programming error
+ *
+ * load_label should take care of this, but be aware, if you are creating
+ * labels in a different way.
+ */
+ size_t m_maxlen{0_z};
+ alignment m_alignment{alignment::LEFT};
+ bool m_ellipsis{true};
+
+ explicit label(string text, int font) : m_font(font), m_text(move(text)), m_tokenized(m_text) {}
+ explicit label(string text, rgba foreground = rgba{}, rgba background = rgba{}, rgba underline = rgba{},
+ rgba overline = rgba{}, int font = 0, side_values padding = {ZERO_SPACE, ZERO_SPACE},
+ side_values margin = {ZERO_SPACE, ZERO_SPACE}, int minlen = 0, size_t maxlen = 0_z,
+ alignment label_alignment = alignment::LEFT, bool ellipsis = true, vector<token>&& tokens = {})
+ : m_foreground(move(foreground))
+ , m_background(move(background))
+ , m_underline(move(underline))
+ , m_overline(move(overline))
+ , m_font(font)
+ , m_padding(padding)
+ , m_margin(margin)
+ , m_minlen(minlen)
+ , m_maxlen(maxlen)
+ , m_alignment(label_alignment)
+ , m_ellipsis(ellipsis)
+ , m_text(move(text))
+ , m_tokenized(m_text)
+ , m_tokens(forward<vector<token>>(tokens)) {
+ assert(!m_ellipsis || (m_maxlen == 0 || m_maxlen >= 3));
+ }
+
+ string get() const;
+ explicit operator bool();
+ label_t clone();
+ void clear();
+ void reset_tokens();
+ void reset_tokens(const string& tokenized);
+ bool has_token(const string& token) const;
+ void replace_token(const string& token, string replacement);
+ void replace_defined_values(const label_t& label);
+ void copy_undefined(const label_t& label);
+
+ private:
+ string m_text{};
+ string m_tokenized{};
+ const vector<token> m_tokens{};
+ };
+
+ label_t load_label(const config& conf, const string& section, string name, bool required = true, string def = ""s);
+ label_t load_optional_label(const config& conf, string section, string name, string def = ""s);
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <tuple>
+#include <vector>
+
+#include "common.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+using std::tuple;
+
+POLYBAR_NS
+
+namespace drawtypes {
+ class layouticonset : public non_copyable_mixin {
+ public:
+ explicit layouticonset(label_t&& default_icon);
+
+ bool add(const string& layout, const string& variant, label_t&& icon);
+ label_t get(const string& layout, const string& variant) const;
+ bool contains(const string& layout, const string& variant) const;
+
+ static constexpr const char* VARIANT_ANY = "_";
+
+ protected:
+ label_t m_default_icon;
+ vector<tuple<string, string, label_t>> m_layout_icons;
+ };
+
+ using layouticonset_t = shared_ptr<layouticonset>;
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/builder.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ class progressbar : public non_copyable_mixin {
+ public:
+ explicit progressbar(const bar_settings& bar, int width, string format);
+
+ void set_fill(label_t&& fill);
+ void set_empty(label_t&& empty);
+ void set_indicator(label_t&& indicator);
+ void set_gradient(bool mode);
+ void set_colors(vector<rgba>&& colors);
+
+ string output(float percentage);
+
+ protected:
+ void fill(unsigned int perc, unsigned int fill_width);
+
+ private:
+ unique_ptr<builder> m_builder;
+ vector<rgba> m_colors;
+ string m_format;
+ unsigned int m_width;
+ unsigned int m_colorstep = 1;
+ bool m_gradient = false;
+
+ label_t m_fill;
+ label_t m_empty;
+ label_t m_indicator;
+ };
+
+ using progressbar_t = shared_ptr<progressbar>;
+
+ progressbar_t load_progressbar(const bar_settings& bar, const config& conf, const string& section, string name);
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ class ramp : public non_copyable_mixin {
+ public:
+ explicit ramp() = default;
+ explicit ramp(vector<label_t>&& icons) : m_icons(forward<decltype(icons)>(icons)) {}
+
+ void add(label_t&& icon);
+ void add(label_t&& icon, unsigned weight);
+ label_t get(size_t index);
+ label_t get_by_percentage(float percentage);
+ label_t get_by_percentage_with_borders(float percentage, float min, float max);
+ label_t get_by_percentage_with_borders(int percentage, int min, int max);
+ operator bool();
+
+ protected:
+ vector<label_t> m_icons;
+ };
+
+ using ramp_t = shared_ptr<ramp>;
+
+ ramp_t load_ramp(const config& conf, const string& section, string name, bool required = true);
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+using std::exception;
+using std::runtime_error;
+using std::strerror;
+
+class application_error : public runtime_error {
+ public:
+ explicit application_error(const string& message, int code = 0) : runtime_error(message), code(code) {}
+ int code{0};
+};
+
+class system_error : public application_error {
+ public:
+ explicit system_error() : application_error(strerror(errno), errno) {}
+ explicit system_error(const string& message)
+ : application_error(message + " (reason: " + strerror(errno) + ")", errno) {}
+};
+
+#define DEFINE_CHILD_ERROR(error, parent) \
+ class error : public parent { \
+ using parent::parent; \
+ }
+#define DEFINE_ERROR(error) DEFINE_CHILD_ERROR(error, application_error)
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/types.hpp"
+
+POLYBAR_NS
+
+namespace signals {
+ namespace detail {
+ class signal {
+ public:
+ explicit signal() = default;
+ virtual ~signal() {}
+ virtual size_t size() const = 0;
+ };
+
+ template <typename Derived>
+ class base_signal : public signal {
+ public:
+ using base_type = base_signal<Derived>;
+
+ explicit base_signal() = default;
+ virtual ~base_signal() {}
+
+ virtual size_t size() const override {
+ return sizeof(Derived);
+ };
+ };
+
+ template <typename Derived, typename ValueType>
+ class value_signal : public base_signal<Derived> {
+ public:
+ using base_type = value_signal<Derived, ValueType>;
+
+ explicit value_signal(void* data) : m_ptr(data) {}
+ explicit value_signal(const ValueType&& data) : m_ptr(&data) {}
+ explicit value_signal(const ValueType& data) : m_ptr(&data) {}
+
+ virtual ~value_signal() {}
+
+ inline const ValueType cast() const {
+ return *static_cast<const ValueType*>(m_ptr);
+ }
+
+ private:
+ const void* m_ptr;
+ };
+ } // namespace detail
+
+ namespace eventqueue {
+ struct exit_reload : public detail::base_signal<exit_reload> {
+ using base_type::base_type;
+ };
+ struct notify_change : public detail::base_signal<notify_change> {
+ using base_type::base_type;
+ };
+ struct notify_forcechange : public detail::base_signal<notify_forcechange> {
+ using base_type::base_type;
+ };
+ struct check_state : public detail::base_signal<check_state> {
+ using base_type::base_type;
+ };
+ } // namespace eventqueue
+
+ namespace ipc {
+ struct command : public detail::value_signal<command, string> {
+ using base_type::base_type;
+ };
+ struct hook : public detail::value_signal<hook, string> {
+ using base_type::base_type;
+ };
+ struct action : public detail::value_signal<action, string> {
+ using base_type::base_type;
+ };
+ } // namespace ipc
+
+ namespace ui {
+ struct changed : public detail::base_signal<changed> {
+ using base_type::base_type;
+ };
+ struct button_press : public detail::value_signal<button_press, string> {
+ using base_type::base_type;
+ };
+ struct visibility_change : public detail::value_signal<visibility_change, bool> {
+ using base_type::base_type;
+ };
+ struct dim_window : public detail::value_signal<dim_window, double> {
+ using base_type::base_type;
+ };
+ struct request_snapshot : public detail::value_signal<request_snapshot, string> {
+ using base_type::base_type;
+ };
+ /// emitted whenever the desktop background slice changes
+ struct update_background : public detail::base_signal<update_background> {
+ using base_type::base_type;
+ };
+ /// emitted when the bar geometry changes (such as position of the bar on the screen)
+ struct update_geometry : public detail::base_signal<update_geometry> {
+ using base_type::base_type;
+ };
+ } // namespace ui
+
+ namespace ui_tray {
+ struct tray_pos_change : public detail::value_signal<tray_pos_change, int> {
+ using base_type::base_type;
+ };
+ struct tray_visibility : public detail::value_signal<tray_visibility, bool> {
+ using base_type::base_type;
+ };
+ } // namespace ui_tray
+} // namespace signals
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "events/signal_receiver.hpp"
+
+POLYBAR_NS
+
+/**
+ * @brief Holds all signal receivers attached to the emitter
+ */
+extern signal_receivers_t g_signal_receivers;
+
+/**
+ * Wrapper used to delegate emitted signals
+ * to attached signal receivers
+ */
+class signal_emitter {
+ public:
+ using make_type = signal_emitter&;
+ static make_type make();
+
+ explicit signal_emitter() = default;
+ virtual ~signal_emitter() {}
+
+ template <typename Signal>
+ bool emit(const Signal& sig) {
+ try {
+ if (g_signal_receivers.find(id<Signal>()) != g_signal_receivers.end()) {
+ for (auto&& item : g_signal_receivers.at(id<Signal>())) {
+ if (item.second->on(sig)) {
+ return true;
+ }
+ }
+ }
+ } catch (const std::exception& e) {
+ logger::make().err("Signal receiver raised an exception: %s", e.what());
+ }
+
+ return false;
+ }
+
+ template <typename Signal, typename Next, typename... Signals>
+ bool emit(const Signal& sig) const {
+ return emit<Signal>(sig) || emit<Next, Signals...>(sig);
+ }
+
+ template <int Priority, typename Signal, typename... Signals>
+ void attach(signal_receiver<Priority, Signal, Signals...>* s) {
+ attach<signal_receiver<Priority, Signal, Signals...>, Signal, Signals...>(s);
+ }
+
+ template <int Priority, typename Signal, typename... Signals>
+ void detach(signal_receiver<Priority, Signal, Signals...>* s) {
+ detach<signal_receiver<Priority, Signal, Signals...>, Signal, Signals...>(s);
+ }
+
+ protected:
+ template <typename Signal>
+ std::type_index id() const {
+ return typeid(Signal);
+ }
+
+ template <typename Receiver, typename Signal>
+ void attach(Receiver* s) {
+ attach(s, id<Signal>());
+ }
+
+ template <typename Receiver, typename Signal, typename Next, typename... Signals>
+ void attach(Receiver* s) {
+ attach(s, id<Signal>());
+ attach<Receiver, Next, Signals...>(s);
+ }
+
+ void attach(signal_receiver_interface* s, std::type_index id) {
+ g_signal_receivers[id].emplace(s->priority(), s);
+ }
+
+ template <typename Receiver, typename Signal>
+ void detach(Receiver* s) {
+ detach(s, id<Signal>());
+ }
+
+ template <typename Receiver, typename Signal, typename Next, typename... Signals>
+ void detach(Receiver* s) {
+ detach(s, id<Signal>());
+ detach<Receiver, Next, Signals...>(s);
+ }
+
+ void detach(signal_receiver_interface* d, std::type_index id) {
+ try {
+ auto& prio_map = g_signal_receivers.at(id);
+ const auto& prio_sink_pair = prio_map.equal_range(d->priority());
+
+ for (auto it = prio_sink_pair.first; it != prio_sink_pair.second;) {
+ if (d == it->second) {
+ it = prio_map.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ } catch (...) {
+ }
+ }
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class signal_emitter;
+class signal_receiver_interface;
+template <int Priority, typename Signal, typename... Signals>
+class signal_receiver;
+
+namespace signals {
+ namespace detail {
+ class signal;
+ }
+
+ namespace eventqueue {
+ struct exit_reload;
+ struct notify_change;
+ struct notify_forcechange;
+ struct check_state;
+ } // namespace eventqueue
+ namespace ipc {
+ struct command;
+ struct hook;
+ struct action;
+ } // namespace ipc
+ namespace ui {
+ struct changed;
+ struct button_press;
+ struct visibility_change;
+ struct dim_window;
+ struct request_snapshot;
+ struct update_background;
+ struct update_geometry;
+ } // namespace ui
+ namespace ui_tray {
+ struct tray_pos_change;
+ struct tray_visibility;
+ } // namespace ui_tray
+} // namespace signals
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <map>
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class signal_receiver_interface {
+ public:
+ using prio = int;
+ using prio_map = std::multimap<prio, signal_receiver_interface*>;
+ virtual ~signal_receiver_interface() {}
+ virtual prio priority() const = 0;
+ template <typename Signal>
+ bool on(const Signal& signal);
+};
+
+template <typename Signal>
+class signal_receiver_impl {
+ public:
+ virtual ~signal_receiver_impl() {}
+ virtual bool on(const Signal&) = 0;
+};
+
+template <typename Signal>
+bool signal_receiver_interface::on(const Signal& s) {
+ auto event_sink = dynamic_cast<signal_receiver_impl<Signal>*>(this);
+
+ if (event_sink != nullptr && event_sink->on(s)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <int Priority, typename Signal, typename... Signals>
+class signal_receiver : public signal_receiver_interface,
+ public signal_receiver_impl<Signal>,
+ public signal_receiver_impl<Signals>... {
+ public:
+ prio priority() const override {
+ return Priority;
+ }
+};
+
+using signal_receivers_t = std::unordered_map<std::type_index, signal_receiver_interface::prio_map>;
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "ipc/msg.hpp"
+
+POLYBAR_NS
+
+namespace ipc {
+
+ /**
+ * Decoder for the IPC message format.
+ */
+ class decoder {
+ public:
+ DEFINE_ERROR(error);
+ /**
+ * Callback is called whenever a full message is received.
+ * The message version, message type, and the data is passed.
+ */
+ using cb = std::function<void(uint8_t, type_t, const std::vector<uint8_t>&)>;
+ decoder(const logger&, cb callback);
+
+ /**
+ * Call this function whenever new data arrives.
+ *
+ * Will throw deocder::error in case of error.
+ * If an error is thrown, this instance is closed and this function may not be called again.
+ */
+ void on_read(const uint8_t* buf, size_t size);
+ void close() noexcept;
+ bool closed() const;
+
+ protected:
+ void process_data(const uint8_t*, size_t size);
+ ssize_t process_header_data(const uint8_t*, size_t size);
+ ssize_t process_msg_data(const uint8_t*, size_t size);
+
+ ipc::header header;
+ size_t to_read_header{ipc::HEADER_SIZE};
+
+ std::vector<uint8_t> buf;
+ size_t to_read_buf{0};
+
+ cb callback;
+
+ private:
+ enum class state {
+ // Waiting for header data (header does not contain full header)
+ HEADER,
+ // Waiting for message data (header contains valid header)
+ PAYLOAD,
+ CLOSED,
+ } state{state::HEADER};
+ const logger& m_log;
+ };
+
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "ipc/msg.hpp"
+
+POLYBAR_NS
+
+namespace ipc {
+ vector<uint8_t> encode(const type_t type, const vector<uint8_t>& data = {});
+ vector<uint8_t> encode(const type_t type, const string& data);
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <set>
+
+#include "common.hpp"
+#include "components/eventloop.hpp"
+#include "ipc/decoder.hpp"
+#include "settings.hpp"
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+class signal_emitter;
+class logger;
+
+namespace ipc {
+ /**
+ * Component used for inter-process communication.
+ *
+ * A unique messaging channel will be setup for each
+ * running process which will allow messages and
+ * events to be sent to the process externally.
+ */
+ class ipc : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ using make_type = unique_ptr<ipc>;
+ static make_type make(eventloop::loop& loop);
+
+ explicit ipc(signal_emitter& emitter, const logger& logger, eventloop::loop& loop);
+ ~ipc();
+
+ static string get_socket_path(int pid);
+
+ protected:
+ bool trigger_ipc(v0::ipc_type type, const string& msg);
+ void trigger_legacy_ipc(const string& msg);
+
+ void on_connection();
+
+ private:
+ signal_emitter& m_sig;
+ const logger& m_log;
+ eventloop::loop& m_loop;
+
+ eventloop::pipe_handle_t m_socket;
+
+ class connection : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ using cb = std::function<void(connection&, uint8_t, type_t, const std::vector<uint8_t>&)>;
+ connection(eventloop::loop& loop, cb msg_callback);
+ ~connection();
+ eventloop::pipe_handle_t client_pipe;
+ decoder dec;
+ };
+
+ void remove_client(connection& conn);
+
+ /**
+ * Custom transparent comparator so that we can lookup and erase connections from their reference.
+ */
+ struct connection_cmp {
+ using is_transparent = std::true_type;
+ bool operator()(const unique_ptr<connection>& a, const unique_ptr<connection>& b) const {
+ return a.get() < b.get();
+ }
+
+ bool operator()(const connection& a, const unique_ptr<connection>& b) const {
+ return &a < b.get();
+ }
+
+ bool operator()(const unique_ptr<connection>& a, const connection& b) const {
+ return a.get() < &b;
+ }
+ };
+
+ std::set<unique_ptr<connection>, connection_cmp> connections;
+
+ // Named pipe properties (deprecated)
+ struct fifo {
+ fifo(eventloop::loop& loop, ipc& ipc, const string& path);
+ ~fifo();
+ eventloop::pipe_handle_t pipe_handle;
+ };
+
+ unique_ptr<fifo> ipc_pipe;
+
+ string m_pipe_path{};
+ /**
+ * Buffer for the currently received IPC message over the named pipe
+ */
+ string m_pipe_buffer{};
+ void receive_data(const string& buf);
+ void receive_eof();
+ };
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+#include <array>
+
+POLYBAR_NS
+
+/**
+ * Defines the binary message format for IPC communications over the IPC socket.
+ *
+ * This is an internal API, do not connect to the socket using 3rd party programs, always use `polybar-msg`.
+ */
+namespace ipc {
+ /**
+ * Magic string prefixed to every ipc message.
+ *
+ * THIS MUST NEVER CHANGE.
+ */
+ static constexpr std::array<uint8_t, 7> MAGIC = {'p', 'o', 'l', 'y', 'i', 'p', 'c'};
+ static const string MAGIC_STR = string(reinterpret_cast<const char*>(MAGIC.data()), MAGIC.size());
+
+ static constexpr uint8_t VERSION = 0;
+
+ using type_t = uint8_t;
+
+ /**
+ * Message type indicating success.
+ */
+ static constexpr type_t TYPE_OK = 0;
+ static constexpr type_t TYPE_ERR = 255;
+
+ union header {
+ struct header_data {
+ uint8_t magic[MAGIC.size()];
+ /**
+ * Version number of the message format.
+ */
+ uint8_t version;
+ /**
+ * Size of the following message in bytes
+ */
+ uint32_t size;
+ /**
+ * Type of the message that follows.
+ *
+ * Meaning of the values depend on version.
+ * Only TYPE_OK(0) indicate success and TYPE_ERR(255) always indicates an error, in which case the entire message
+ * is a string.
+ */
+ type_t type;
+ } __attribute__((packed)) s;
+ uint8_t d[sizeof(header_data)];
+ };
+
+ /**
+ * Size of the standard header shared by all versions.
+ *
+ * THIS MUST NEVER CHANGE.
+ */
+ static constexpr size_t HEADER_SIZE = 13;
+ static_assert(sizeof(header) == HEADER_SIZE, "");
+ static_assert(sizeof(header::header_data) == HEADER_SIZE, "");
+
+ /**
+ * Definitions for version 0 of the IPC message format.
+ *
+ * The format is very simple. The header defines the type (cmd or action) and the payload is the message for that type
+ * as a string.
+ */
+ namespace v0 {
+ enum class ipc_type : type_t {
+ /**
+ * Message type for ipc commands
+ */
+ CMD = 1,
+ /**
+ * Message type for ipc module actions
+ */
+ ACTION = 2,
+ };
+ }
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace ipc {
+ string get_runtime_path();
+ string ensure_runtime_path();
+ string get_socket_path(const string& pid_string);
+ string get_socket_path(int pid);
+ string get_glob_socket_path();
+ int get_pid_from_socket(const string& path);
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+// fwd
+namespace alsa {
+ class mixer;
+ class control;
+} // namespace alsa
+
+namespace modules {
+ enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE };
+ enum class control { NONE = 0, HEADPHONE };
+
+ using mixer_t = shared_ptr<alsa::mixer>;
+ using control_t = shared_ptr<alsa::control>;
+
+ class alsa_module : public event_module<alsa_module> {
+ public:
+ explicit alsa_module(const bar_settings&, string, const config&);
+
+ void teardown();
+ bool has_event();
+ bool update();
+ string get_format() const;
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = ALSA_TYPE;
+
+ static constexpr auto EVENT_INC = "inc";
+ static constexpr auto EVENT_DEC = "dec";
+ static constexpr auto EVENT_TOGGLE = "toggle";
+
+ protected:
+ void action_inc();
+ void action_dec();
+ void action_toggle();
+
+ void change_volume(int interval);
+
+ void action_epilogue(const vector<mixer_t>& mixers);
+
+ vector<mixer_t> get_mixers();
+
+ private:
+ static constexpr auto FORMAT_VOLUME = "format-volume";
+ static constexpr auto FORMAT_MUTED = "format-muted";
+
+ static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
+ static constexpr auto TAG_RAMP_HEADPHONES = "<ramp-headphones>";
+ static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
+ static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
+ static constexpr auto TAG_LABEL_MUTED = "<label-muted>";
+
+ progressbar_t m_bar_volume;
+ ramp_t m_ramp_volume;
+ ramp_t m_ramp_headphones;
+ label_t m_label_volume;
+ label_t m_label_muted;
+
+ map<mixer, mixer_t> m_mixer;
+ map<control, control_t> m_ctrl;
+ int m_headphoneid{0};
+ bool m_mapped{false};
+ int m_interval{5};
+ atomic<bool> m_muted{false};
+ atomic<bool> m_headphones{false};
+ atomic<int> m_volume{0};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/inotify_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ /**
+ * Reads value from `/sys/class/backlight/` to get a brightness value for some device.
+ *
+ * There are two file providing brightness values: `brightness` and `actual_brightness`.
+ * The `actual_brightness` file is usually more reliable, but in some cases does not work (provides completely wrong
+ * values, doesn't update) depending on kernel version, graphics driver, and/or graphics card.
+ * Which file is used is controlled by the use-actual-brightness setting.
+ *
+ * The general issue with the `brightness` file seems to be that, while it does receive inotify events, the events it
+ * receives are not for modification of the file and arrive just before the file is updated with a new value. The module
+ * thus reads and displays an outdated brightness value. To compensate for this, the module periodically (controlled by
+ * `poll-interval`) forces an update. By default, this is only enabled if the `backlight` file is used.
+ */
+ class backlight_module : public inotify_module<backlight_module> {
+ public:
+ struct brightness_handle {
+ void filepath(const string& path);
+ float read() const;
+
+ private:
+ string m_path;
+ };
+
+ string get_output();
+
+ explicit backlight_module(const bar_settings&, string, const config&);
+
+ void idle();
+ bool on_event(const inotify_event& event);
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = BACKLIGHT_TYPE;
+
+ static constexpr const char* EVENT_INC = "inc";
+ static constexpr const char* EVENT_DEC = "dec";
+
+ protected:
+ void action_inc();
+ void action_dec();
+
+ void change_value(int value_mod);
+
+ private:
+ static constexpr auto TAG_LABEL = "<label>";
+ static constexpr auto TAG_BAR = "<bar>";
+ static constexpr auto TAG_RAMP = "<ramp>";
+
+ ramp_t m_ramp;
+ label_t m_label;
+ progressbar_t m_progressbar;
+ string m_path_backlight;
+ float m_max_brightness{};
+ bool m_scroll{false};
+ int m_scroll_interval{5};
+ bool m_use_actual_brightness{true};
+
+ brightness_handle m_val;
+ brightness_handle m_max;
+
+ int m_percentage = 0;
+
+ chrono::duration<double> m_interval{};
+ chrono::steady_clock::time_point m_lastpoll;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "modules/meta/inotify_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class battery_module : public inotify_module<battery_module> {
+ public:
+ enum class state {
+ NONE = 0,
+ CHARGING,
+ DISCHARGING,
+ LOW,
+ FULL,
+ };
+
+ enum class value {
+ NONE = 0,
+ ADAPTER,
+ CAPACITY,
+ CAPACITY_MAX,
+ VOLTAGE,
+ RATE,
+ };
+
+ template <typename ReturnType>
+ struct value_reader {
+ using return_type = ReturnType;
+
+ explicit value_reader() = default;
+ explicit value_reader(function<ReturnType()>&& fn) : m_fn(forward<decltype(fn)>(fn)) {}
+
+ ReturnType read() const {
+ return m_fn();
+ }
+
+ private:
+ const function<ReturnType()> m_fn;
+ };
+
+ using state_reader = mutex_wrapper<value_reader<bool /* is_charging */>>;
+ using capacity_reader = mutex_wrapper<value_reader<int /* percentage */>>;
+ using rate_reader = mutex_wrapper<value_reader<unsigned long /* seconds */>>;
+ using consumption_reader = mutex_wrapper<value_reader<string /* watts */>>;
+
+ public:
+ explicit battery_module(const bar_settings&, string, const config&);
+
+ void start() override;
+ void teardown();
+ void idle();
+ bool on_event(const inotify_event& event);
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = BATTERY_TYPE;
+
+ protected:
+ state current_state();
+ int current_percentage();
+ int clamp_percentage(int percentage, state state) const;
+ string current_time();
+ string current_consumption();
+ void subthread();
+
+ private:
+ static constexpr const char* FORMAT_CHARGING{"format-charging"};
+ static constexpr const char* FORMAT_DISCHARGING{"format-discharging"};
+ static constexpr const char* FORMAT_FULL{"format-full"};
+ static constexpr const char* FORMAT_LOW{"format-low"};
+
+ static constexpr const char* TAG_ANIMATION_CHARGING{"<animation-charging>"};
+ static constexpr const char* TAG_ANIMATION_DISCHARGING{"<animation-discharging>"};
+ static constexpr const char* TAG_ANIMATION_LOW{"<animation-low>"};
+ static constexpr const char* TAG_BAR_CAPACITY{"<bar-capacity>"};
+ static constexpr const char* TAG_RAMP_CAPACITY{"<ramp-capacity>"};
+ static constexpr const char* TAG_LABEL_CHARGING{"<label-charging>"};
+ static constexpr const char* TAG_LABEL_DISCHARGING{"<label-discharging>"};
+ static constexpr const char* TAG_LABEL_FULL{"<label-full>"};
+ static constexpr const char* TAG_LABEL_LOW{"<label-low>"};
+
+ static const size_t SKIP_N_UNCHANGED{3_z};
+
+ unique_ptr<state_reader> m_state_reader;
+ unique_ptr<capacity_reader> m_capacity_reader;
+ unique_ptr<rate_reader> m_rate_reader;
+ unique_ptr<consumption_reader> m_consumption_reader;
+
+ label_t m_label_charging;
+ label_t m_label_discharging;
+ label_t m_label_full;
+ label_t m_label_low;
+ animation_t m_animation_charging;
+ animation_t m_animation_discharging;
+ animation_t m_animation_low;
+ progressbar_t m_bar_capacity;
+ ramp_t m_ramp_capacity;
+
+ string m_fstate;
+ string m_fcapnow;
+ string m_fcapfull;
+ string m_frate;
+ string m_fvoltage;
+
+ state m_state{state::DISCHARGING};
+ int m_percentage{0};
+
+ int m_fullat{100};
+ int m_lowat{10};
+ string m_timeformat;
+ size_t m_unchanged{SKIP_N_UNCHANGED};
+ chrono::duration<double> m_interval{};
+ chrono::steady_clock::time_point m_lastpoll;
+ thread m_subthread;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "modules/meta/types.hpp"
+#include "utils/bspwm.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class bspwm_module : public event_module<bspwm_module> {
+ public:
+ enum class state {
+ NONE = 0U,
+ EMPTY,
+ OCCUPIED,
+ FOCUSED,
+ URGENT,
+ DIMMED, // used when the monitor is out of focus
+ };
+
+ enum class mode {
+ NONE = 0U,
+ LAYOUT_MONOCLE,
+ LAYOUT_TILED,
+ STATE_FULLSCREEN,
+ STATE_FLOATING,
+ STATE_PSEUDOTILED,
+ NODE_LOCKED,
+ NODE_STICKY,
+ NODE_PRIVATE,
+ NODE_MARKED
+ };
+
+ struct bspwm_monitor {
+ vector<pair<unsigned int, label_t>> workspaces;
+ vector<label_t> modes;
+ label_t label;
+ string name;
+ bool focused{false};
+ };
+
+ public:
+ explicit bspwm_module(const bar_settings&, string, const config&);
+
+ void stop() override;
+ bool has_event();
+ bool update();
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = BSPWM_TYPE;
+
+ static constexpr auto EVENT_FOCUS = "focus";
+ static constexpr auto EVENT_NEXT = "next";
+ static constexpr auto EVENT_PREV = "prev";
+
+ protected:
+ void action_focus(const string& data);
+ void action_next();
+ void action_prev();
+
+ void focus_direction(bool next);
+ void send_command(const string& payload_cmd, const string& log_info);
+
+ private:
+ bool handle_status(string& data);
+
+ static constexpr auto DEFAULT_ICON = "ws-icon-default";
+ static constexpr auto DEFAULT_LABEL = "%icon% %name%";
+ static constexpr auto DEFAULT_MONITOR_LABEL = "%name%";
+
+ static constexpr auto TAG_LABEL_MONITOR = "<label-monitor>";
+ static constexpr auto TAG_LABEL_STATE = "<label-state>";
+ static constexpr auto TAG_LABEL_MODE = "<label-mode>";
+
+ bspwm_util::connection_t m_subscriber;
+
+ vector<unique_ptr<bspwm_monitor>> m_monitors;
+
+ map<mode, label_t> m_modelabels;
+ map<unsigned int, label_t> m_statelabels;
+ label_t m_monitorlabel;
+ iconset_t m_icons;
+
+ /**
+ * Separator that is inserted in between workspaces
+ */
+ label_t m_labelseparator;
+
+ bool m_click{true};
+ bool m_scroll{true};
+ bool m_occscroll{false};
+ bool m_revscroll{true};
+ bool m_pinworkspaces{true};
+ bool m_inlinemode{false};
+ string_util::hash_type m_hash{0U};
+ bool m_fuzzy_match{false};
+
+ // used while formatting output
+ size_t m_index{0U};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class counter_module : public timer_module<counter_module> {
+ public:
+ explicit counter_module(const bar_settings&, string, const config&);
+
+ bool update();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = COUNTER_TYPE;
+
+ private:
+ static constexpr auto TAG_COUNTER = "<counter>";
+
+ int m_counter{0};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ enum class cpu_state { NORMAL = 0, WARN };
+ struct cpu_time {
+ unsigned long long user;
+ unsigned long long nice;
+ unsigned long long system;
+ unsigned long long idle;
+ unsigned long long steal;
+ unsigned long long total;
+ };
+
+ using cpu_time_t = unique_ptr<cpu_time>;
+
+ class cpu_module : public timer_module<cpu_module> {
+ public:
+ explicit cpu_module(const bar_settings&, string, const config&);
+
+ bool update();
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = CPU_TYPE;
+
+ protected:
+ bool read_values();
+ float get_load(size_t core) const;
+
+ private:
+ static constexpr auto TAG_LABEL = "<label>";
+ static constexpr auto TAG_LABEL_WARN = "<label-warn>";
+ static constexpr auto TAG_BAR_LOAD = "<bar-load>";
+ static constexpr auto TAG_RAMP_LOAD = "<ramp-load>";
+ static constexpr auto TAG_RAMP_LOAD_PER_CORE = "<ramp-coreload>";
+ static constexpr auto FORMAT_WARN = "format-warn";
+
+ label_t m_label;
+ label_t m_labelwarn;
+ progressbar_t m_barload;
+ ramp_t m_rampload;
+ ramp_t m_rampload_core;
+ spacing_val m_ramp_padding{spacing_type::SPACE, 1U};
+
+ vector<cpu_time_t> m_cputimes;
+ vector<cpu_time_t> m_cputimes_prev;
+
+ float m_totalwarn = 80;
+ float m_total = 0;
+ vector<float> m_load;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <atomic>
+#include <ctime>
+#include <iomanip>
+#include <iostream>
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class date_module : public timer_module<date_module> {
+ public:
+ explicit date_module(const bar_settings&, string, const config&);
+
+ bool update();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = DATE_TYPE;
+
+ static constexpr auto EVENT_TOGGLE = "toggle";
+
+ protected:
+ void action_toggle();
+
+ private:
+ static constexpr auto TAG_LABEL = "<label>";
+
+ // @deprecated: Use <label>
+ static constexpr auto TAG_DATE = "<date>";
+
+ label_t m_label;
+
+ string m_dateformat;
+ string m_dateformat_alt;
+ string m_timeformat;
+ string m_timeformat_alt;
+
+ string m_date;
+ string m_time;
+
+ // Single stringstream to be used to gather the results of std::put_time
+ std::stringstream datetime_stream;
+
+ std::atomic<bool> m_toggled{false};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ /**
+ * Filesystem structure
+ */
+ struct fs_mount {
+ string mountpoint;
+ bool mounted = false;
+
+ string type;
+ string fsname;
+
+ uint64_t bytes_free{0ULL};
+ uint64_t bytes_used{0ULL};
+ uint64_t bytes_avail{0ULL};
+ uint64_t bytes_total{0ULL};
+
+ int percentage_free{0};
+ int percentage_used{0};
+
+ explicit fs_mount(string mountpoint, bool mounted = false) : mountpoint(move(mountpoint)), mounted(mounted) {}
+ };
+
+ using fs_mount_t = unique_ptr<fs_mount>;
+
+ /**
+ * Module used to display filesystem stats.
+ */
+ class fs_module : public timer_module<fs_module> {
+ public:
+ explicit fs_module(const bar_settings&, string, const config&);
+
+ bool update();
+ string get_format() const;
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = FS_TYPE;
+
+ private:
+ static constexpr auto FORMAT_MOUNTED = "format-mounted";
+ static constexpr auto FORMAT_WARN = "format-warn";
+ static constexpr auto FORMAT_UNMOUNTED = "format-unmounted";
+ static constexpr auto TAG_LABEL_MOUNTED = "<label-mounted>";
+ static constexpr auto TAG_LABEL_UNMOUNTED = "<label-unmounted>";
+ static constexpr auto TAG_LABEL_WARN = "<label-warn>";
+ static constexpr auto TAG_BAR_USED = "<bar-used>";
+ static constexpr auto TAG_BAR_FREE = "<bar-free>";
+ static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
+
+ label_t m_labelmounted;
+ label_t m_labelunmounted;
+ label_t m_labelwarn;
+ progressbar_t m_barused;
+ progressbar_t m_barfree;
+ ramp_t m_rampcapacity;
+
+ vector<string> m_mountpoints;
+ vector<fs_mount_t> m_mounts;
+ bool m_fixed{false};
+ bool m_remove_unmounted{false};
+ spacing_val m_spacing{spacing_type::SPACE, 2U};
+ int m_perc_used_warn{90};
+
+ // used while formatting output
+ size_t m_index{0_z};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <atomic>
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+#include "utils/http.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ /**
+ * Module used to query the GitHub API for notification count
+ */
+ class github_module : public timer_module<github_module> {
+ public:
+ explicit github_module(const bar_settings&, string, const config&);
+
+ bool update();
+ bool build(builder* builder, const string& tag) const;
+ string get_format() const;
+
+ static constexpr auto TYPE = GITHUB_TYPE;
+
+ private:
+ void update_label(int);
+ int get_number_of_notification();
+ string request();
+ static constexpr auto TAG_LABEL = "<label>";
+ static constexpr auto TAG_LABEL_OFFLINE = "<label-offline>";
+ static constexpr auto FORMAT_OFFLINE = "format-offline";
+
+ label_t m_label{};
+ label_t m_label_offline{};
+ string m_api_url;
+ string m_user;
+ string m_accesstoken{};
+ http_downloader m_http{};
+ bool m_empty_notifications{false};
+ std::atomic<bool> m_offline{false};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <i3ipc++/ipc.hpp>
+
+#include "components/config.hpp"
+#include "modules/meta/event_module.hpp"
+#include "modules/meta/types.hpp"
+#include "utils/i3.hpp"
+#include "utils/io.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class i3_module : public event_module<i3_module> {
+ public:
+ enum class state {
+ NONE,
+ /**
+ * @brief Active workspace on focused monitor
+ */
+ FOCUSED,
+ /**
+ * @brief Inactive workspace on any monitor
+ */
+ UNFOCUSED,
+ /**
+ * @brief Active workspace on unfocused monitor
+ */
+ VISIBLE,
+ /**
+ * @brief Workspace with urgency hint set
+ */
+ URGENT,
+ };
+
+ struct workspace {
+ explicit workspace(string name, enum state state_, label_t&& label)
+ : name(name), state(state_), label(forward<label_t>(label)) {}
+
+ operator bool();
+
+ string name;
+ enum state state;
+ label_t label;
+ };
+
+ public:
+ explicit i3_module(const bar_settings&, string, const config&);
+
+ void stop() override;
+ bool has_event();
+ bool update();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = I3_TYPE;
+
+ static constexpr auto EVENT_FOCUS = "focus";
+ static constexpr auto EVENT_NEXT = "next";
+ static constexpr auto EVENT_PREV = "prev";
+
+ protected:
+ void action_focus(const string& ws);
+ void action_next();
+ void action_prev();
+
+ void focus_direction(bool next);
+
+ private:
+ static string make_workspace_command(const string& workspace);
+
+ static constexpr const char* DEFAULT_TAGS{"<label-state> <label-mode>"};
+ static constexpr const char* DEFAULT_MODE{"default"};
+ static constexpr const char* DEFAULT_WS_ICON{"ws-icon-default"};
+ static constexpr const char* DEFAULT_WS_LABEL{"%icon% %name%"};
+
+ static constexpr const char* TAG_LABEL_STATE{"<label-state>"};
+ static constexpr const char* TAG_LABEL_MODE{"<label-mode>"};
+
+ map<state, label_t> m_statelabels;
+ vector<unique_ptr<workspace>> m_workspaces;
+ iconset_t m_icons;
+
+ label_t m_modelabel;
+ bool m_modeactive{false};
+
+ /**
+ * Separator that is inserted in between workspaces
+ */
+ label_t m_labelseparator;
+
+ bool m_click{true};
+ bool m_scroll{true};
+ bool m_revscroll{true};
+ bool m_wrap{true};
+ bool m_indexsort{false};
+ bool m_pinworkspaces{false};
+ bool m_show_urgent{false};
+ bool m_strip_wsnumbers{false};
+ bool m_fuzzy_match{false};
+
+ unique_ptr<i3_util::connection_t> m_ipc;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "utils/command.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ /**
+ * Module that allow users to configure hooks on
+ * received ipc messages. The hook will execute the defined
+ * shell script and the resulting output will be used
+ * as the module content.
+ */
+ class ipc_module : public module<ipc_module> {
+ public:
+ /**
+ * Hook structure that will be fired
+ * when receiving a message with specified id
+ */
+ struct hook {
+ string payload;
+ string command;
+ };
+
+ public:
+ explicit ipc_module(const bar_settings&, string, const config&);
+
+ void start() override;
+ void update();
+ string get_output();
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+ void on_message(const string& message);
+
+ static constexpr auto TYPE = IPC_TYPE;
+
+ static constexpr auto EVENT_SEND = "send";
+ static constexpr auto EVENT_HOOK = "hook";
+ static constexpr auto EVENT_NEXT = "next";
+ static constexpr auto EVENT_PREV = "prev";
+ static constexpr auto EVENT_RESET = "reset";
+
+ protected:
+ void action_send(const string& data);
+ void action_hook(const string& data);
+ void action_next();
+ void action_prev();
+ void action_reset();
+
+ void hook_offset(int offset);
+
+ bool has_initial() const;
+ bool has_hook() const;
+
+ void set_hook(int h);
+ void update_output() ;
+ private:
+ static constexpr auto TAG_OUTPUT = "<output>";
+ static constexpr auto TAG_LABEL = "<label>";
+
+ label_t m_label;
+
+ vector<unique_ptr<hook>> m_hooks;
+ map<mousebtn, string> m_actions;
+ string m_output;
+
+ int m_initial{-1};
+ int m_current_hook{-1};
+ void exec_hook();
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ enum class memtype { NONE = 0, TOTAL, USED, FREE, SHARED, BUFFERS, CACHE, AVAILABLE };
+ enum class memory_state { NORMAL = 0, WARN };
+ class memory_module : public timer_module<memory_module> {
+ public:
+ explicit memory_module(const bar_settings&, string, const config&);
+
+ bool update();
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = MEMORY_TYPE;
+
+ private:
+ static constexpr const char* TAG_LABEL{"<label>"};
+ static constexpr const char* TAG_LABEL_WARN{"<label-warn>"};
+ static constexpr const char* TAG_BAR_USED{"<bar-used>"};
+ static constexpr const char* TAG_BAR_FREE{"<bar-free>"};
+ static constexpr const char* TAG_RAMP_USED{"<ramp-used>"};
+ static constexpr const char* TAG_RAMP_FREE{"<ramp-free>"};
+ static constexpr const char* TAG_BAR_SWAP_USED{"<bar-swap-used>"};
+ static constexpr const char* TAG_BAR_SWAP_FREE{"<bar-swap-free>"};
+ static constexpr const char* TAG_RAMP_SWAP_USED{"<ramp-swap-used>"};
+ static constexpr const char* TAG_RAMP_SWAP_FREE{"<ramp-swap-free>"};
+ static constexpr const char* FORMAT_WARN{"format-warn"};
+
+ label_t m_label;
+ label_t m_labelwarn;
+ progressbar_t m_bar_memused;
+ progressbar_t m_bar_memfree;
+ int m_perc_memused{0};
+ int m_perc_memfree{0};
+ int m_perc_memused_warn{90};
+ ramp_t m_ramp_memused;
+ ramp_t m_ramp_memfree;
+ progressbar_t m_bar_swapused;
+ progressbar_t m_bar_swapfree;
+ int m_perc_swap_used{0};
+ int m_perc_swap_free{0};
+ ramp_t m_ramp_swapused;
+ ramp_t m_ramp_swapfree;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class menu_module : public static_module<menu_module> {
+ public:
+ struct menu_tree_item {
+ string exec;
+ label_t label;
+ };
+
+ struct menu_tree {
+ vector<unique_ptr<menu_tree_item>> items;
+ };
+
+ public:
+ explicit menu_module(const bar_settings&, string, const config&);
+
+ bool build(builder* builder, const string& tag) const;
+ void update() {}
+
+ static constexpr auto TYPE = MENU_TYPE;
+
+ static constexpr auto EVENT_OPEN = "open";
+ static constexpr auto EVENT_CLOSE = "close";
+ static constexpr auto EVENT_EXEC = "exec";
+
+ protected:
+ void action_open(const string& data);
+ void action_close();
+ void action_exec(const string& item);
+
+ private:
+ static constexpr auto TAG_LABEL_TOGGLE = "<label-toggle>";
+ static constexpr auto TAG_MENU = "<menu>";
+
+ bool m_expand_right{true};
+
+ label_t m_labelopen;
+ label_t m_labelclose;
+ label_t m_labelseparator;
+
+ vector<unique_ptr<menu_tree>> m_levels;
+
+ int m_level{-1};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+/**
+ * Header file to include the headers for all modules.
+ */
+
+#include "modules/backlight.hpp"
+#include "modules/battery.hpp"
+#include "modules/bspwm.hpp"
+#include "modules/counter.hpp"
+#include "modules/cpu.hpp"
+#include "modules/date.hpp"
+#include "modules/fs.hpp"
+#include "modules/ipc.hpp"
+#include "modules/memory.hpp"
+#include "modules/menu.hpp"
+#include "modules/meta/base.hpp"
+#include "modules/script.hpp"
+#include "modules/temperature.hpp"
+#include "modules/text.hpp"
+#include "modules/tray.hpp"
+#include "modules/xbacklight.hpp"
+#include "modules/xwindow.hpp"
+#include "modules/xworkspaces.hpp"
+#if ENABLE_I3
+#include "modules/i3.hpp"
+#endif
+#if ENABLE_MPD
+#include "modules/mpd.hpp"
+#endif
+#if ENABLE_NETWORK
+#include "modules/network.hpp"
+#endif
+#if ENABLE_ALSA
+#include "modules/alsa.hpp"
+#endif
+#if ENABLE_PULSEAUDIO
+#include "modules/pulseaudio.hpp"
+#endif
+#if ENABLE_CURL
+#include "modules/github.hpp"
+#endif
+#if ENABLE_XKEYBOARD
+#include "modules/xkeyboard.hpp"
+#endif
--- /dev/null
+#pragma once
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <map>
+#include <mutex>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/concurrency.hpp"
+#include "utils/inotify.hpp"
+#include "utils/string.hpp"
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+using std::atomic;
+using std::map;
+
+#define DEFAULT_FORMAT "format"
+
+#define CONST_MOD(name) static_cast<name const&>(*this)
+#define CAST_MOD(name) static_cast<name*>(this)
+
+// fwd decl {{{
+
+namespace drawtypes {
+ class ramp;
+ using ramp_t = shared_ptr<ramp>;
+ class progressbar;
+ using progressbar_t = shared_ptr<progressbar>;
+ class animation;
+ using animation_t = shared_ptr<animation>;
+ class iconset;
+ using iconset_t = shared_ptr<iconset>;
+} // namespace drawtypes
+
+class builder;
+class config;
+class logger;
+class signal_emitter;
+
+class action_router;
+// }}}
+
+namespace modules {
+
+ using namespace drawtypes;
+
+ DEFINE_ERROR(module_error);
+ DEFINE_CHILD_ERROR(undefined_format, module_error);
+ DEFINE_CHILD_ERROR(undefined_format_tag, module_error);
+
+ // class definition : module_format {{{
+
+ struct module_format {
+ string value{};
+ label_t prefix{};
+ label_t suffix{};
+ rgba fg{};
+ rgba bg{};
+ rgba ul{};
+ rgba ol{};
+ size_t ulsize{0};
+ size_t olsize{0};
+ spacing_val spacing{ZERO_SPACE};
+ spacing_val padding{ZERO_SPACE};
+ spacing_val margin{ZERO_SPACE};
+ extent_val offset{ZERO_PX_EXTENT};
+ int font{0};
+
+ string decorate(builder* builder, string output);
+ };
+
+ // }}}
+ // class definition : module_formatter {{{
+
+ class module_formatter {
+ public:
+ explicit module_formatter(const config& conf, string modname) : m_conf(conf), m_modname(modname) {}
+
+ void add(string name, string fallback, vector<string>&& tags, vector<string>&& whitelist = {});
+ void add_optional(string name, vector<string>&& tags, vector<string>&& whitelist = {});
+ bool has(const string& tag, const string& format_name);
+ bool has(const string& tag);
+ bool has_format(const string& format_name);
+ shared_ptr<module_format> get(const string& format_name);
+
+ protected:
+ void add_value(string&& name, string&& value, vector<string>&& tags, vector<string>&& whitelist);
+
+ const config& m_conf;
+ string m_modname;
+ map<string, shared_ptr<module_format>> m_formats;
+ };
+
+ // }}}
+
+ // class definition : module_interface {{{
+
+ struct module_interface {
+ public:
+ virtual ~module_interface() {}
+
+ /**
+ * The type users have to specify in the module section `type` key
+ */
+ virtual string type() const = 0;
+
+ /**
+ * Module name w/o 'module/' prefix
+ */
+ virtual string name_raw() const = 0;
+ virtual string name() const = 0;
+ virtual bool running() const = 0;
+ virtual bool visible() const = 0;
+
+ /**
+ * Handle action, possibly with data attached
+ *
+ * Any implementation is free to ignore the data, if the action does not
+ * require additional data.
+ *
+ * @returns true if the action is supported and false otherwise
+ */
+ virtual bool input(const string& action, const string& data) = 0;
+
+ virtual void start() = 0;
+ virtual void join() = 0;
+ virtual void stop() = 0;
+ virtual void halt(string error_message) = 0;
+ virtual string contents() = 0;
+ };
+
+ // }}}
+ // class definition : module {{{
+
+ template <class Impl>
+ class module : public module_interface {
+ public:
+ module(const bar_settings& bar, string name, const config&);
+ ~module() noexcept;
+
+ static constexpr auto EVENT_MODULE_TOGGLE = "module_toggle";
+ static constexpr auto EVENT_MODULE_SHOW = "module_show";
+ static constexpr auto EVENT_MODULE_HIDE = "module_hide";
+
+ string type() const override;
+
+ string name_raw() const override;
+ string name() const override;
+ bool running() const override;
+
+ bool visible() const override;
+
+ void start() override;
+ void join() final override;
+ void stop() override;
+ void halt(string error_message) override;
+ void teardown();
+ string contents() override;
+
+ bool input(const string& action, const string& data) final override;
+
+ protected:
+ void broadcast();
+ void idle();
+ void sleep(chrono::duration<double> duration);
+ template <class Clock, class Duration>
+ void sleep_until(chrono::time_point<Clock, Duration> point);
+
+ /**
+ * Wakes up the module.
+ *
+ * It should be possible to interrupt any blocking operation inside a
+ * module using this function.
+ *
+ * In addition, after a wake up whatever was woken up should immediately
+ * check whether the module is still running.
+ *
+ * Modules that don't follow this, could stall the operation of whatever
+ * code called this function.
+ */
+ void wakeup();
+ string get_format() const;
+ string get_output();
+
+ void set_visible(bool value);
+
+ void action_module_toggle();
+ void action_module_show();
+ void action_module_hide();
+
+ protected:
+ signal_emitter& m_sig;
+ const bar_settings& m_bar;
+ const logger& m_log;
+ const config& m_conf;
+
+ unique_ptr<action_router> m_router;
+
+ mutex m_buildlock;
+ mutex m_updatelock;
+ mutex m_sleeplock;
+ std::condition_variable m_sleephandler;
+
+ const string m_name;
+ const string m_name_raw;
+ unique_ptr<builder> m_builder;
+ unique_ptr<module_formatter> m_formatter;
+ vector<thread> m_threads;
+ thread m_mainthread;
+
+ bool m_handle_events{true};
+
+ private:
+ atomic<bool> m_enabled{false};
+ atomic<bool> m_visible{true};
+ atomic<bool> m_changed{true};
+ string m_cache;
+ };
+
+ // }}}
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+#include <cassert>
+
+#include "components/builder.hpp"
+#include "components/logger.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "modules/meta/base.hpp"
+#include "utils/action_router.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+
+ // module<Impl> public {{{
+
+ template <typename Impl>
+ module<Impl>::module(const bar_settings& bar, string name, const config& conf)
+ : m_sig(signal_emitter::make())
+ , m_bar(bar)
+ , m_log(logger::make())
+ , m_conf(conf)
+ , m_router(make_unique<action_router>())
+ , m_name("module/" + name)
+ , m_name_raw(name)
+ , m_builder(make_unique<builder>(bar))
+ , m_formatter(make_unique<module_formatter>(m_conf, m_name))
+ , m_handle_events(m_conf.get(m_name, "handle-events", true))
+ , m_visible(!m_conf.get(m_name, "hidden", false)) {
+ m_router->register_action(EVENT_MODULE_TOGGLE, [this]() { action_module_toggle(); });
+ m_router->register_action(EVENT_MODULE_SHOW, [this]() { action_module_show(); });
+ m_router->register_action(EVENT_MODULE_HIDE, [this]() { action_module_hide(); });
+ }
+
+ template <typename Impl>
+ module<Impl>::~module() noexcept {
+ m_log.trace("%s: Deconstructing", name());
+
+ if (running()) {
+ /*
+ * We can't stop in the destructor because we have to call the subclasses which at this point already have been
+ * destructed.
+ */
+ m_log.err("%s: Module was not stopped before deconstructing.", name());
+ }
+ }
+
+ template <typename Impl>
+ string module<Impl>::name() const {
+ return m_name;
+ }
+
+ template <typename Impl>
+ string module<Impl>::name_raw() const {
+ return m_name_raw;
+ }
+
+ template <typename Impl>
+ string module<Impl>::type() const {
+ return string(Impl::TYPE);
+ }
+
+ template <typename Impl>
+ bool module<Impl>::running() const {
+ return static_cast<bool>(m_enabled);
+ }
+
+ template <class Impl>
+ void module<Impl>::start() {
+ m_enabled = true;
+ }
+
+ template <class Impl>
+ void module<Impl>::join() {
+ for (auto&& thread_ : m_threads) {
+ if (thread_.joinable()) {
+ thread_.join();
+ }
+ }
+ if (m_mainthread.joinable()) {
+ m_mainthread.join();
+ }
+ }
+
+ template <typename Impl>
+ bool module<Impl>::visible() const {
+ return static_cast<bool>(m_visible);
+ }
+
+ template <typename Impl>
+ void module<Impl>::stop() {
+ if (!static_cast<bool>(m_enabled)) {
+ return;
+ }
+
+ m_log.info("%s: Stopping", name());
+ m_enabled = false;
+
+ std::lock(m_buildlock, m_updatelock);
+ std::lock_guard<std::mutex> guard_a(m_buildlock, std::adopt_lock);
+ std::lock_guard<std::mutex> guard_b(m_updatelock, std::adopt_lock);
+ {
+ CAST_MOD(Impl)->wakeup();
+ CAST_MOD(Impl)->teardown();
+
+ m_sig.emit(signals::eventqueue::check_state{});
+ }
+ }
+
+ template <typename Impl>
+ void module<Impl>::halt(string error_message) {
+ m_log.err("%s: %s", name(), error_message);
+ m_log.notice("Stopping '%s'...", name());
+ stop();
+ }
+
+ template <typename Impl>
+ void module<Impl>::teardown() {}
+
+ template <typename Impl>
+ string module<Impl>::contents() {
+ if (m_changed) {
+ m_log.info("%s: Rebuilding cache", name());
+ m_cache = CAST_MOD(Impl)->get_output();
+ // Make sure builder is really empty
+ m_builder->flush();
+ if (!m_cache.empty()) {
+ // Add a reset tag after the module
+ m_builder->control(tags::controltag::R);
+ m_cache += m_builder->flush();
+ }
+ m_changed = false;
+ }
+ return m_cache;
+ }
+
+ template <typename Impl>
+ bool module<Impl>::input(const string& name, const string& data) {
+ if (!m_router->has_action(name)) {
+ return false;
+ }
+
+ try {
+ m_router->invoke(name, data);
+ } catch (const exception& err) {
+ m_log.err("%s: Failed to handle command '%s' with data '%s' (%s)", this->name(), name, data, err.what());
+ }
+ return true;
+ }
+
+ // }}}
+ // module<Impl> protected {{{
+
+ template <typename Impl>
+ void module<Impl>::broadcast() {
+ m_changed = true;
+ m_sig.emit(signals::eventqueue::notify_change{});
+ }
+
+ template <typename Impl>
+ void module<Impl>::idle() {
+ if (running()) {
+ CAST_MOD(Impl)->sleep(25ms);
+ }
+ }
+
+ template <typename Impl>
+ void module<Impl>::sleep(chrono::duration<double> sleep_duration) {
+ if (running()) {
+ std::unique_lock<std::mutex> lck(m_sleeplock);
+ m_sleephandler.wait_for(lck, sleep_duration);
+ }
+ }
+
+ template <typename Impl>
+ template <class Clock, class Duration>
+ void module<Impl>::sleep_until(chrono::time_point<Clock, Duration> point) {
+ if (running()) {
+ std::unique_lock<std::mutex> lck(m_sleeplock);
+ m_sleephandler.wait_until(lck, point);
+ }
+ }
+
+ template <typename Impl>
+ void module<Impl>::wakeup() {
+ m_log.trace("%s: Release sleep lock", name());
+ m_sleephandler.notify_all();
+ }
+
+ template <typename Impl>
+ string module<Impl>::get_format() const {
+ return DEFAULT_FORMAT;
+ }
+
+ template <typename Impl>
+ string module<Impl>::get_output() {
+ std::lock_guard<std::mutex> guard(m_buildlock);
+ auto format_name = CONST_MOD(Impl).get_format();
+ auto format = m_formatter->get(format_name);
+
+ /*
+ * Builder for building individual tags isolated, so that we can
+ */
+ builder tag_builder(m_bar);
+
+ // Whether any tags have been processed yet
+ bool has_tags = false;
+
+ // Cursor pointing into 'value'
+ size_t cursor = 0;
+ const string& value{format->value};
+
+ /*
+ * Search for all tags in the format definition. A tag is enclosed in '<' and '>'.
+ * Each tag is given to the module to produce some output for it. All other text is added as-is.
+ */
+ while (cursor < value.size()) {
+ // Check if there are any tags left
+
+ // Start index of next tag
+ size_t start = value.find('<', cursor);
+
+ if (start == string::npos) {
+ break;
+ }
+
+ // End index (inclusive) of next tag
+ size_t end = value.find('>', start + 1);
+
+ if (end == string::npos) {
+ break;
+ }
+
+ // Potential regular text that appears before the tag.
+ string non_tag;
+
+ // There is some non-tag text
+ if (start > cursor) {
+ /*
+ * Produce anything between the previous and current tag as regular text.
+ */
+ non_tag = value.substr(cursor, start - cursor);
+ if (!has_tags) {
+ /*
+ * If no module tag has been built we do not want to add
+ * whitespace defined between the format tags, but we do still
+ * want to output other non-tag content
+ */
+ non_tag = string_util::ltrim(move(non_tag), ' ');
+ }
+ }
+
+ string tag = value.substr(start, end - start + 1);
+ bool tag_built = CONST_MOD(Impl).build(&tag_builder, tag);
+ string tag_content = tag_builder.flush();
+
+ /*
+ * Remove exactly one space between two tags if the second tag was not built.
+ */
+ if (!tag_built && has_tags && !format->spacing) {
+ if (!non_tag.empty() && non_tag.back() == ' ') {
+ non_tag.erase(non_tag.size() - 1);
+ }
+ }
+
+ m_builder->node(non_tag);
+
+ if (tag_built) {
+ if (has_tags) {
+ // format-spacing is added between all tags
+ m_builder->spacing(format->spacing);
+ }
+
+ m_builder->node(tag_content);
+ has_tags = true;
+ }
+
+ cursor = end + 1;
+ }
+
+ if (cursor < value.size()) {
+ m_builder->node(value.substr(cursor));
+ }
+
+ return format->decorate(&*m_builder, m_builder->flush());
+ }
+
+ template <typename Impl>
+ void module<Impl>::set_visible(bool value) {
+ m_log.notice("%s: Visibility changed (state=%s)", m_name, value ? "shown" : "hidden");
+ m_visible = value;
+ broadcast();
+ }
+
+ template <typename Impl>
+ void module<Impl>::action_module_toggle() {
+ set_visible(!m_visible);
+ }
+
+ template <typename Impl>
+ void module<Impl>::action_module_show() {
+ set_visible(true);
+ }
+
+ template <typename Impl>
+ void module<Impl>::action_module_hide() {
+ set_visible(false);
+ }
+
+ // }}}
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+ struct event_handler_interface {
+ virtual ~event_handler_interface() {}
+ virtual void connect(connection&) {}
+ virtual void disconnect(connection&) {}
+ };
+
+ template <typename Event, typename... Events>
+ class event_handler : public event_handler_interface, public xpp::event::sink<Event, Events...> {
+ public:
+ virtual ~event_handler() {}
+
+ virtual void connect(connection& conn) override {
+ conn.attach_sink(this, SINK_PRIORITY_MODULE);
+ }
+
+ virtual void disconnect(connection& conn) override {
+ conn.detach_sink(this, SINK_PRIORITY_MODULE);
+ }
+ };
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template <class Impl>
+ class event_module : public module<Impl> {
+ public:
+ using module<Impl>::module;
+
+ void start() override {
+ this->module<Impl>::start();
+ this->m_mainthread = thread(&event_module::runner, this);
+ }
+
+ protected:
+ void runner() {
+ this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+ try {
+ // warm up module output before entering the loop
+ std::unique_lock<std::mutex> guard(this->m_updatelock);
+ CAST_MOD(Impl)->update();
+ CAST_MOD(Impl)->broadcast();
+ guard.unlock();
+
+ const auto check = [&]() -> bool {
+ std::lock_guard<std::mutex> guard(this->m_updatelock);
+ return CAST_MOD(Impl)->has_event() && CAST_MOD(Impl)->update();
+ };
+
+ while (this->running()) {
+ if (check()) {
+ CAST_MOD(Impl)->broadcast();
+ }
+ CAST_MOD(Impl)->idle();
+ }
+ } catch (const exception& err) {
+ CAST_MOD(Impl)->halt(err.what());
+ }
+ }
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ using module_t = shared_ptr<module_interface>;
+
+ /**
+ * Creates a new module instance.
+ *
+ * @param type The type of the module (as given by each module's TYPE field)
+ * @param bar An instance of the @ref bar_settings
+ * @param module_name The user-specified module name
+ * @param log A @ref logger instance
+ * @param config A @ref config instance
+ */
+ module_t make_module(string&& type, const bar_settings& bar, string module_name, const logger& log, const config& config);
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "components/builder.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template <class Impl>
+ class inotify_module : public module<Impl> {
+ public:
+ using module<Impl>::module;
+
+ void start() override {
+ this->module<Impl>::start();
+ this->m_mainthread = thread(&inotify_module::runner, this);
+ }
+
+ protected:
+ void runner() {
+ this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+ try {
+ // Warm up module output before entering the loop
+ std::unique_lock<std::mutex> guard(this->m_updatelock);
+ CAST_MOD(Impl)->on_event({});
+ CAST_MOD(Impl)->broadcast();
+ guard.unlock();
+
+ while (this->running()) {
+ std::lock_guard<std::mutex> guard(this->m_updatelock);
+ CAST_MOD(Impl)->poll_events();
+ }
+ } catch (const module_error& err) {
+ CAST_MOD(Impl)->halt(err.what());
+ } catch (const std::exception& err) {
+ CAST_MOD(Impl)->halt(err.what());
+ }
+ }
+
+ void watch(string path, int mask = IN_ALL_EVENTS) {
+ this->m_log.trace("%s: Attach inotify at %s", this->name(), path);
+ m_watchlist.insert(make_pair(path, mask));
+ }
+
+ void idle() {
+ this->sleep(200ms);
+ }
+
+ void poll_events() {
+ vector<inotify_watch> watches;
+
+ try {
+ for (auto&& w : m_watchlist) {
+ watches.emplace_back(w.first);
+ watches.back().attach(w.second);
+ }
+ } catch (const system_error& e) {
+ this->m_log.err("%s: Error while creating inotify watch (what: %s)", this->name(), e.what());
+ CAST_MOD(Impl)->sleep(0.1s);
+ return;
+ }
+
+ while (this->running()) {
+ for (auto&& w : watches) {
+ this->m_log.trace_x("%s: Poll inotify watch %s", this->name(), w.path());
+
+ if (w.poll(1000 / watches.size())) {
+ auto event = w.get_event();
+ if (CAST_MOD(Impl)->on_event(event)) {
+ CAST_MOD(Impl)->broadcast();
+ }
+ CAST_MOD(Impl)->idle();
+ return;
+ }
+
+ if (!this->running()) {
+ break;
+ }
+ }
+ CAST_MOD(Impl)->idle();
+ }
+ }
+
+ private:
+ map<string, int> m_watchlist;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template <class Impl>
+ class static_module : public module<Impl> {
+ public:
+ using module<Impl>::module;
+
+ void start() override {
+ this->module<Impl>::start();
+ CAST_MOD(Impl)->update();
+ CAST_MOD(Impl)->broadcast();
+ }
+
+ bool build(builder*, string) const {
+ return true;
+ }
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ using interval_t = chrono::duration<double>;
+
+ template <class Impl>
+ class timer_module : public module<Impl> {
+ public:
+ using module<Impl>::module;
+
+ void start() override {
+ this->module<Impl>::start();
+ this->m_mainthread = thread(&timer_module::runner, this);
+ }
+
+ protected:
+ /**
+ * Loads and sets the interval for this module.
+ *
+ * Will throw an exception if a non-positive (<= 0) number is given.
+ */
+ void set_interval(interval_t def) {
+ m_interval = this->m_conf.template get<decltype(m_interval)>(this->name(), "interval", def);
+
+ if (m_interval <= 0s) {
+ throw module_error(
+ this->name() + ": 'interval' must be larger than 0 (got '" + to_string(m_interval.count()) + "s')");
+ }
+ }
+
+ void runner() {
+ this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+
+ const auto check = [&]() -> bool {
+ std::unique_lock<std::mutex> guard(this->m_updatelock);
+ return CAST_MOD(Impl)->update();
+ };
+
+ try {
+ // warm up module output before entering the loop
+ check();
+ CAST_MOD(Impl)->broadcast();
+
+ while (this->running()) {
+ if (check()) {
+ CAST_MOD(Impl)->broadcast();
+ }
+ // wait until next full interval to avoid drifting clocks
+ using clock = chrono::steady_clock;
+ using sys_duration_t = clock::time_point::duration;
+
+ auto sys_interval = chrono::duration_cast<sys_duration_t>(m_interval);
+ clock::time_point now = clock::now();
+ sys_duration_t adjusted = sys_interval - (now.time_since_epoch() % sys_interval);
+
+ // The seemingly arbitrary addition of 500ms is due
+ // to the fact that if we wait the exact time our
+ // thread will be woken just a tiny bit prematurely
+ // and therefore the wrong time will be displayed.
+ // It is currently unknown why exactly the thread gets
+ // woken prematurely.
+ CAST_MOD(Impl)->sleep_until(now + adjusted + 500ms);
+ }
+ } catch (const exception& err) {
+ CAST_MOD(Impl)->halt(err.what());
+ }
+ }
+
+ protected:
+ interval_t m_interval{1.0};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+
+ static constexpr const char ALSA_TYPE[] = "internal/alsa";
+ static constexpr const char BACKLIGHT_TYPE[] = "internal/backlight";
+ static constexpr const char BATTERY_TYPE[] = "internal/battery";
+ static constexpr const char BSPWM_TYPE[] = "internal/bspwm";
+ static constexpr const char COUNTER_TYPE[] = "internal/counter";
+ static constexpr const char CPU_TYPE[] = "internal/cpu";
+ static constexpr const char DATE_TYPE[] = "internal/date";
+ static constexpr const char FS_TYPE[] = "internal/fs";
+ static constexpr const char GITHUB_TYPE[] = "internal/github";
+ static constexpr const char I3_TYPE[] = "internal/i3";
+ static constexpr const char MEMORY_TYPE[] = "internal/memory";
+ static constexpr const char MPD_TYPE[] = "internal/mpd";
+ static constexpr const char NETWORK_TYPE[] = "internal/network";
+ static constexpr const char PULSEAUDIO_TYPE[] = "internal/pulseaudio";
+ static constexpr const char TEMPERATURE_TYPE[] = "internal/temperature";
+ static constexpr const char TRAY_TYPE[] = "internal/tray";
+ static constexpr const char XBACKLIGHT_TYPE[] = "internal/xbacklight";
+ static constexpr const char XKEYBOARD_TYPE[] = "internal/xkeyboard";
+ static constexpr const char XWINDOW_TYPE[] = "internal/xwindow";
+ static constexpr const char XWORKSPACES_TYPE[] = "internal/xworkspaces";
+
+ static constexpr const char IPC_TYPE[] = "custom/ipc";
+ static constexpr const char MENU_TYPE[] = "custom/menu";
+ static constexpr const char SCRIPT_TYPE[] = "custom/script";
+ static constexpr const char TEXT_TYPE[] = "custom/text";
+
+} // namespace modules
+
+POLYBAR_NS_END
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include <chrono>
+
+#include "adapters/mpd.hpp"
+#include "modules/meta/event_module.hpp"
+#include "modules/meta/types.hpp"
+#include "utils/env.hpp"
+
+POLYBAR_NS
+
+using namespace mpd;
+
+namespace modules {
+ class mpd_module : public event_module<mpd_module> {
+ public:
+ explicit mpd_module(const bar_settings&, string, const config&);
+
+ void teardown();
+ inline bool connected() const;
+ void idle();
+ bool has_event();
+ bool update();
+ string get_format() const;
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = MPD_TYPE;
+
+ static constexpr const char* EVENT_PLAY = "play";
+ static constexpr const char* EVENT_PAUSE = "pause";
+ static constexpr const char* EVENT_STOP = "stop";
+ static constexpr const char* EVENT_PREV = "prev";
+ static constexpr const char* EVENT_NEXT = "next";
+ static constexpr const char* EVENT_REPEAT = "repeat";
+ static constexpr const char* EVENT_SINGLE = "single";
+ static constexpr const char* EVENT_RANDOM = "random";
+ static constexpr const char* EVENT_CONSUME = "consume";
+ static constexpr const char* EVENT_SEEK = "seek";
+
+ private:
+ void action_play();
+ void action_pause();
+ void action_stop();
+ void action_prev();
+ void action_next();
+ void action_repeat();
+ void action_single();
+ void action_random();
+ void action_consume();
+ void action_seek(const string& data);
+
+ private:
+ static constexpr const char* FORMAT_ONLINE{"format-online"};
+ static constexpr const char* FORMAT_PLAYING{"format-playing"};
+ static constexpr const char* FORMAT_PAUSED{"format-paused"};
+ static constexpr const char* FORMAT_STOPPED{"format-stopped"};
+ static constexpr const char* TAG_BAR_PROGRESS{"<bar-progress>"};
+ static constexpr const char* TAG_TOGGLE{"<toggle>"};
+ static constexpr const char* TAG_TOGGLE_STOP{"<toggle-stop>"};
+ static constexpr const char* TAG_LABEL_SONG{"<label-song>"};
+ static constexpr const char* TAG_LABEL_TIME{"<label-time>"};
+ static constexpr const char* TAG_ICON_RANDOM{"<icon-random>"};
+ static constexpr const char* TAG_ICON_REPEAT{"<icon-repeat>"};
+ /*
+ * Deprecated
+ */
+ static constexpr const char* TAG_ICON_REPEAT_ONE{"<icon-repeatone>"};
+ /*
+ * Replaces icon-repeatone
+ *
+ * repeatone is misleading, since it doesn't actually affect the repeating behaviour
+ */
+ static constexpr const char* TAG_ICON_SINGLE{"<icon-single>"};
+ static constexpr const char* TAG_ICON_CONSUME{"<icon-consume>"};
+ static constexpr const char* TAG_ICON_PREV{"<icon-prev>"};
+ static constexpr const char* TAG_ICON_STOP{"<icon-stop>"};
+ static constexpr const char* TAG_ICON_PLAY{"<icon-play>"};
+ static constexpr const char* TAG_ICON_PAUSE{"<icon-pause>"};
+ static constexpr const char* TAG_ICON_NEXT{"<icon-next>"};
+ static constexpr const char* TAG_ICON_SEEKB{"<icon-seekb>"};
+ static constexpr const char* TAG_ICON_SEEKF{"<icon-seekf>"};
+
+ static constexpr const char* FORMAT_OFFLINE{"format-offline"};
+ static constexpr const char* TAG_LABEL_OFFLINE{"<label-offline>"};
+
+ unique_ptr<mpdconnection> m_mpd;
+
+ /*
+ * Stores the mpdstatus instance for the current connection
+ * m_status is not initialized if mpd is not connect, you always have to
+ * make sure that m_status is not NULL before dereferencing it
+ */
+ unique_ptr<mpdstatus> m_status;
+
+ string m_host{env_util::get("MPD_HOST", "127.0.0.1")};
+ string m_pass;
+ unsigned int m_port{6600U};
+
+ chrono::steady_clock::time_point m_lastsync{};
+ float m_synctime{1.0f};
+
+ int m_quick_attempts{0};
+
+ // This flag is used to let thru a broadcast once every time
+ // the connection state changes
+ connection_state m_statebroadcasted{connection_state::NONE};
+
+ progressbar_t m_bar_progress;
+ iconset_t m_icons;
+ label_t m_label_song;
+ label_t m_label_time;
+ label_t m_label_offline;
+
+ rgba m_toggle_on_color;
+ rgba m_toggle_off_color;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "adapters/net.hpp"
+#include "components/config.hpp"
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ enum class connection_state { NONE = 0, CONNECTED, DISCONNECTED, PACKETLOSS };
+
+ class network_module : public timer_module<network_module> {
+ public:
+ explicit network_module(const bar_settings&, string, const config&);
+
+ void teardown();
+ bool update();
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = NETWORK_TYPE;
+
+ protected:
+ void subthread_routine();
+
+ private:
+ static constexpr auto FORMAT_CONNECTED = "format-connected";
+ static constexpr auto FORMAT_PACKETLOSS = "format-packetloss";
+ static constexpr auto FORMAT_DISCONNECTED = "format-disconnected";
+ static constexpr auto TAG_RAMP_SIGNAL = "<ramp-signal>";
+ static constexpr auto TAG_RAMP_QUALITY = "<ramp-quality>";
+ static constexpr auto TAG_LABEL_CONNECTED = "<label-connected>";
+ static constexpr auto TAG_LABEL_DISCONNECTED = "<label-disconnected>";
+ static constexpr auto TAG_LABEL_PACKETLOSS = "<label-packetloss>";
+ static constexpr auto TAG_ANIMATION_PACKETLOSS = "<animation-packetloss>";
+
+ net::wired_t m_wired;
+ net::wireless_t m_wireless;
+
+ ramp_t m_ramp_signal;
+ ramp_t m_ramp_quality;
+ animation_t m_animation_packetloss;
+ map<connection_state, label_t> m_label;
+
+ atomic<bool> m_connected{false};
+ atomic<bool> m_packetloss{false};
+
+ int m_signal{0};
+ int m_quality{0};
+ int m_counter{-1}; // -1 to ignore the first run
+
+ string m_interface;
+ int m_ping_nth_update{0};
+ int m_udspeed_minwidth{0};
+ bool m_accumulate{false};
+ bool m_unknown_up{false};
+ string m_udspeed_unit{"B/s"};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+// fwd
+class pulseaudio;
+
+namespace modules {
+ using pulseaudio_t = shared_ptr<pulseaudio>;
+
+ class pulseaudio_module : public event_module<pulseaudio_module> {
+ public:
+ explicit pulseaudio_module(const bar_settings&, string, const config&);
+
+ void teardown();
+ bool has_event();
+ bool update();
+ string get_format() const;
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = PULSEAUDIO_TYPE;
+
+ static constexpr auto EVENT_INC = "inc";
+ static constexpr auto EVENT_DEC = "dec";
+ static constexpr auto EVENT_TOGGLE = "toggle";
+
+ protected:
+ void action_inc();
+ void action_dec();
+ void action_toggle();
+
+ private:
+ static constexpr auto FORMAT_VOLUME = "format-volume";
+ static constexpr auto FORMAT_MUTED = "format-muted";
+
+ static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
+ static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
+ static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
+ static constexpr auto TAG_LABEL_MUTED = "<label-muted>";
+
+ progressbar_t m_bar_volume;
+ ramp_t m_ramp_volume;
+ label_t m_label_volume;
+ label_t m_label_muted;
+
+ pulseaudio_t m_pulseaudio;
+
+ int m_interval{5};
+ atomic<bool> m_muted{false};
+ atomic<int> m_volume{0};
+ atomic<double> m_decibels{0};
+ atomic<bool> m_reverse_scroll{false};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "adapters/script_runner.hpp"
+#include "modules/meta/base.hpp"
+#include "modules/meta/types.hpp"
+#include "utils/command.hpp"
+#include "utils/io.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class script_module : public module<script_module> {
+ public:
+ explicit script_module(const bar_settings&, string, const config&);
+
+ void start() override;
+ void stop() override;
+
+ string get_output();
+ string get_format() const;
+
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = SCRIPT_TYPE;
+
+ protected:
+ bool check_condition();
+
+ private:
+ void handle_runner_update(const script_runner::data&);
+
+ static constexpr auto TAG_LABEL = "<label>";
+ static constexpr auto TAG_LABEL_FAIL = "<label-fail>";
+ static constexpr auto FORMAT_FAIL = "format-fail";
+
+ const bool m_tail;
+ const script_runner::interval m_interval_success{0};
+ const script_runner::interval m_interval_fail{0};
+ const script_runner::interval m_interval_if{0};
+
+ script_runner m_runner;
+
+ map<mousebtn, string> m_actions;
+
+ label_t m_label;
+ label_t m_label_fail;
+
+ int m_exit_status{0};
+ script_runner::data m_data;
+ std::mutex m_data_mutex;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <istream>
+
+#include "modules/meta/timer_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ enum class temp_state { NORMAL = 0, WARN };
+
+ class temperature_module : public timer_module<temperature_module> {
+ public:
+ explicit temperature_module(const bar_settings&, string, const config&);
+
+ bool update();
+ string get_format() const;
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = TEMPERATURE_TYPE;
+
+ private:
+ static constexpr auto TAG_LABEL = "<label>";
+ static constexpr auto TAG_LABEL_WARN = "<label-warn>";
+ static constexpr auto TAG_RAMP = "<ramp>";
+ static constexpr auto FORMAT_WARN = "format-warn";
+
+ map<temp_state, label_t> m_label;
+ ramp_t m_ramp;
+
+ string m_path;
+ string m_zone_type;
+ int m_zone = 0;
+ // Base temperature used for where to start the ramp
+ int m_tempbase = 0;
+ int m_tempwarn = 0;
+ int m_temp = 0;
+
+ // Whether or not to show units with the %temperature-X% tokens
+ bool m_units{true};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ class text_module : public static_module<text_module> {
+ public:
+ explicit text_module(const bar_settings&, string, const config&);
+
+ void update() {}
+ string get_format() const;
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = TEXT_TYPE;
+
+ private:
+ static constexpr const char* TAG_LABEL{"<label>"};
+
+ label_t m_label;
+
+ string m_format{DEFAULT_FORMAT};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/bar.hpp"
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "x11/tray_manager.hpp"
+
+POLYBAR_NS
+namespace modules {
+class tray_module : public static_module<tray_module> {
+ public:
+ explicit tray_module(const bar_settings& bar_settings, string name_, const config&);
+ string get_format() const;
+
+ void start() override;
+
+ bool build(builder* builder, const string& tag) const;
+ void update() {}
+
+ static constexpr auto TYPE = TRAY_TYPE;
+
+ private:
+ static constexpr const char* TAG_TRAY{"<tray>"};
+
+ tray::manager m_tray;
+};
+} // namespace modules
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "settings.hpp"
+#include "x11/extensions/randr.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+ /**
+ * Backlight module built using the RandR X extension.
+ *
+ * This is built as a replacement for the old backlight
+ * module that was set up using with inotify watches listening
+ * for changes to the raw file handle.
+ *
+ * This module is a lot faster, it's more responsive and it will
+ * be dormant until new values are reported. Inotify watches
+ * are a bit random when it comes to proc-/sysfs.
+ */
+ class xbacklight_module : public static_module<xbacklight_module>, public event_handler<evt::randr_notify> {
+ public:
+ explicit xbacklight_module(const bar_settings& bar, string name_, const config&);
+
+ void update();
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = XBACKLIGHT_TYPE;
+
+ static constexpr const char* EVENT_INC = "inc";
+ static constexpr const char* EVENT_DEC = "dec";
+
+ protected:
+ void handle(const evt::randr_notify& evt) override;
+
+ void action_inc();
+ void action_dec();
+
+ void change_value(int value_mod);
+
+ private:
+ static constexpr const char* TAG_LABEL{"<label>"};
+ static constexpr const char* TAG_BAR{"<bar>"};
+ static constexpr const char* TAG_RAMP{"<ramp>"};
+
+ connection& m_connection;
+ monitor_t m_output;
+ xcb_window_t m_proxy{};
+
+ ramp_t m_ramp;
+ label_t m_label;
+ progressbar_t m_progressbar;
+
+ bool m_scroll{true};
+ int m_percentage{0};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "drawtypes/layouticonset.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "x11/extensions/xkb.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+ /**
+ * Keyboard module using the X keyboard extension
+ */
+ class xkeyboard_module
+ : public static_module<xkeyboard_module>,
+ public event_handler<evt::xkb_new_keyboard_notify, evt::xkb_state_notify, evt::xkb_indicator_state_notify> {
+ public:
+ explicit xkeyboard_module(const bar_settings& bar, string name_, const config&);
+
+ string get_output();
+ void update();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = XKEYBOARD_TYPE;
+
+ static constexpr const char* EVENT_SWITCH = "switch";
+
+ protected:
+ bool query_keyboard();
+ bool blacklisted(const string& indicator_name);
+
+ void handle(const evt::xkb_new_keyboard_notify& evt) override;
+ void handle(const evt::xkb_state_notify& evt) override;
+ void handle(const evt::xkb_indicator_state_notify& evt) override;
+
+ void action_switch();
+
+ void define_layout_icon(const string& entry, const string& layout, const string& variant, label_t&& icon);
+ void parse_icons();
+
+ private:
+ static constexpr const char* TAG_LABEL_LAYOUT{"<label-layout>"};
+ static constexpr const char* TAG_LABEL_INDICATOR{"<label-indicator>"};
+ static constexpr const char* FORMAT_DEFAULT{"<label-layout> <label-indicator>"};
+ static constexpr const char* DEFAULT_LAYOUT_ICON{"layout-icon-default"};
+ static constexpr const char* DEFAULT_INDICATOR_ICON{"indicator-icon-default"};
+
+ connection& m_connection;
+ event_timer m_xkb_newkb_notify{};
+ event_timer m_xkb_state_notify{};
+ event_timer m_xkb_indicator_notify{};
+ unique_ptr<keyboard> m_keyboard;
+
+ label_t m_layout;
+ label_t m_indicator_state_on;
+ label_t m_indicator_state_off;
+ map<keyboard::indicator::type, label_t> m_indicators;
+ map<keyboard::indicator::type, label_t> m_indicator_on_labels;
+ map<keyboard::indicator::type, label_t> m_indicator_off_labels;
+
+ vector<string> m_blacklist;
+ layouticonset_t m_layout_icons;
+ iconset_t m_indicator_icons_on;
+ iconset_t m_indicator_icons_off;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+ class active_window : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ explicit active_window(xcb_connection_t* conn, xcb_window_t win);
+ ~active_window();
+
+ bool match(xcb_window_t win) const;
+ string title() const;
+ string instance_name() const;
+ string class_name() const;
+
+ private:
+ xcb_connection_t* m_connection{nullptr};
+ xcb_window_t m_window{XCB_NONE};
+ };
+
+ /**
+ * Module used to display information about the
+ * currently active X window.
+ */
+ class xwindow_module : public static_module<xwindow_module>, public event_handler<evt::property_notify> {
+ public:
+ enum class state { NONE, ACTIVE, EMPTY };
+ explicit xwindow_module(const bar_settings&, string, const config&);
+
+ void update();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = XWINDOW_TYPE;
+
+ protected:
+ void handle(const evt::property_notify& evt) override;
+
+ void reset_active_window();
+
+ private:
+ static constexpr const char* TAG_LABEL{"<label>"};
+
+ connection& m_connection;
+ unique_ptr<active_window> m_active;
+ map<state, label_t> m_statelabels;
+ label_t m_label;
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "modules/meta/types.hpp"
+#include "x11/ewmh.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+ enum class desktop_state {
+ NONE,
+ ACTIVE,
+ URGENT,
+ EMPTY,
+ OCCUPIED,
+ };
+
+ enum class viewport_state {
+ NONE,
+ FOCUSED,
+ UNFOCUSED,
+ };
+
+ struct desktop {
+ explicit desktop(unsigned int index, desktop_state state, label_t&& label)
+ : index(index), state(state), label(label) {}
+ unsigned int index;
+ desktop_state state;
+ label_t label;
+ };
+
+ struct viewport {
+ position pos;
+ string name;
+ vector<unique_ptr<desktop>> desktops;
+ viewport_state state;
+ label_t label;
+ };
+
+ /**
+ * Module used to display EWMH desktops
+ */
+ class xworkspaces_module : public static_module<xworkspaces_module>, public event_handler<evt::property_notify> {
+ public:
+ explicit xworkspaces_module(const bar_settings& bar, string name_, const config&);
+
+ void update();
+ string get_output();
+ bool build(builder* builder, const string& tag) const;
+
+ static constexpr auto TYPE = XWORKSPACES_TYPE;
+
+ static constexpr auto EVENT_FOCUS = "focus";
+ static constexpr auto EVENT_NEXT = "next";
+ static constexpr auto EVENT_PREV = "prev";
+
+ protected:
+ void handle(const evt::property_notify& evt) override;
+
+ void rebuild_clientlist();
+ void rebuild_urgent_hints();
+ void rebuild_desktops();
+ void rebuild_desktop_states();
+ void update_current_desktop();
+
+ void action_focus(const string& data);
+ void action_next();
+ void action_prev();
+
+ void focus_direction(bool next);
+ void focus_desktop(unsigned new_desktop);
+
+ private:
+ static vector<string> get_desktop_names();
+
+ static constexpr const char* DEFAULT_ICON{"icon-default"};
+ static constexpr const char* DEFAULT_LABEL_STATE{"%icon% %name%"};
+ static constexpr const char* DEFAULT_LABEL_MONITOR{"%name%"};
+
+ static constexpr const char* TAG_LABEL_MONITOR{"<label-monitor>"};
+ static constexpr const char* TAG_LABEL_STATE{"<label-state>"};
+
+ connection& m_connection;
+ ewmh_util::ewmh_connection& m_ewmh;
+
+ vector<monitor_t> m_monitors;
+
+ vector<string> m_desktop_names;
+ vector<bool> m_urgent_desktops;
+ unsigned int m_current_desktop;
+
+ /**
+ * Maps an xcb window to its desktop number
+ */
+ map<xcb_window_t, unsigned int> m_clients;
+ map<unsigned int, unsigned int> m_windows;
+ vector<unique_ptr<viewport>> m_viewports;
+ map<desktop_state, label_t> m_labels;
+ label_t m_monitorlabel;
+ iconset_t m_icons;
+ bool m_pinworkspaces{false};
+ bool m_click{true};
+ bool m_scroll{true};
+ bool m_revscroll{false};
+ bool m_group_by_monitor{true};
+ size_t m_index{0};
+ };
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cstdio>
+#include <string>
+#include <vector>
+
+extern const char* const APP_NAME;
+extern const char* const APP_VERSION;
+
+#cmakedefine01 ENABLE_ALSA
+#cmakedefine01 ENABLE_MPD
+#cmakedefine01 ENABLE_NETWORK
+#cmakedefine01 WITH_LIBNL
+#cmakedefine01 ENABLE_I3
+#cmakedefine01 ENABLE_CURL
+#cmakedefine01 ENABLE_PULSEAUDIO
+
+#define WITH_XRANDR 1
+#define WITH_XCOMPOSITE 1
+#cmakedefine01 WITH_XKB
+#cmakedefine01 WITH_XRM
+#cmakedefine01 WITH_XCURSOR
+
+#if WITH_XRANDR
+#cmakedefine01 WITH_XRANDR_MONITORS
+#else
+#define WITH_XRANDR_MONITORS 0
+#endif
+
+#if WITH_XKB
+#cmakedefine01 ENABLE_XKEYBOARD
+#else
+#define ENABLE_XKEYBOARD 0
+#endif
+
+#cmakedefine XPP_EXTENSION_LIST @XPP_EXTENSION_LIST@
+
+#cmakedefine DEBUG_LOGGER
+
+#if DEBUG
+#cmakedefine DEBUG_LOGGER_VERBOSE
+#cmakedefine DEBUG_HINTS
+#cmakedefine DEBUG_WHITESPACE
+#cmakedefine DEBUG_FONTCONFIG
+#endif
+
+static const int SIGN_PRIORITY_CONTROLLER{1};
+static const int SIGN_PRIORITY_SCREEN{2};
+static const int SIGN_PRIORITY_BAR{3};
+static const int SIGN_PRIORITY_RENDERER{4};
+static const int SIGN_PRIORITY_TRAY{5};
+
+extern const int SINK_PRIORITY_BAR;
+extern const int SINK_PRIORITY_SCREEN;
+extern const int SINK_PRIORITY_TRAY;
+extern const int SINK_PRIORITY_MODULE;
+
+extern const char* const ALSA_SOUNDCARD;
+extern const char* const BSPWM_SOCKET_PATH;
+extern const char* const BSPWM_STATUS_PREFIX;
+extern const char* const CONNECTION_TEST_IP;
+extern const char* const PATH_ADAPTER;
+extern const char* const PATH_BACKLIGHT;
+extern const char* const PATH_BATTERY;
+extern const char* const PATH_CPU_INFO;
+extern const char* const PATH_MEMORY_INFO;
+extern const char* const PATH_MESSAGING_FIFO;
+extern const char* const PATH_TEMPERATURE_INFO;
+extern const char* const PATH_THERMAL_ZONE_WILDCARD;
+extern const char* const WIRELESS_LIB;
+
+bool version_details(const std::vector<std::string>& args);
+
+void print_build_info(bool extended = false);
+
+// vim:ft=cpp
--- /dev/null
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "components/types.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+ /**
+ * An identifier for an action block.
+ *
+ * A value of NO_ACTION denotes an undefined identifier and is guaranteed to
+ * be smaller (<) than any valid identifier.
+ *
+ * If two action blocks overlap, the action with the higher identifier will
+ * be above.
+ *
+ * Except for NO_ACTION, negative values are not allowed
+ */
+ using action_t = int;
+
+ static constexpr action_t NO_ACTION = -1;
+
+ /**
+ * Defines a clickable (or scrollable) action block.
+ *
+ * An action block is an area on the bar that executes some command when clicked.
+ */
+ struct action_block {
+ action_block(const string&& cmd, mousebtn button, alignment align, bool is_open)
+ : cmd(std::move(cmd)), button(button), align(align), is_open(is_open){};
+
+ string cmd;
+ /**
+ * Start position of the action block (inclusive), relative to the alignment.
+ */
+ double start_x{0};
+
+ /**
+ * End position of the action block (exclusive), relative to the alignment.
+ *
+ * May be incrementally updated while the action block is still open
+ */
+ double end_x{0};
+ mousebtn button;
+ alignment align;
+ /**
+ * Tracks whether this block is still open or whether it already has a
+ * corresponding closing tag.
+ *
+ * After rendering, all action blocks should be closed.
+ */
+ bool is_open;
+
+ unsigned int width() const {
+ return static_cast<unsigned int>(end_x - start_x + 0.5);
+ }
+
+ /**
+ * Tests whether a given point is inside this block.
+ *
+ * This additionally needs the position of the start of the alignment
+ * because the given point is relative to the bar window.
+ */
+ bool test(double align_start, int point) const {
+ return static_cast<int>(start_x + align_start) <= point && static_cast<int>(end_x + align_start) > point;
+ }
+ };
+
+ /**
+ * Stores information about all action blocks on the bar.
+ *
+ * This class is used during rendering to open and close action blocks and
+ * in between render cycles to look up actions at certain positions.
+ */
+ class action_context {
+ public:
+ void reset();
+
+ action_t action_open(mousebtn btn, const string&& cmd, alignment align, double x);
+ std::pair<action_t, mousebtn> action_close(mousebtn btn, alignment align, double x);
+
+ /**
+ * Compensate for the current position moving backwards in the renderer.
+ *
+ * This can happen if negative offsets are used.
+ *
+ * The policy is that an action block's start is the smallest x position observed while the block is open.
+ * And its end is the largest x position observed while it is open.
+ *
+ * @param a The current alignment
+ * @param old_x The x position before the backwards move
+ * @param new_x The x position after the backwards move new_x < old_x
+ */
+ void compensate_for_negative_move(alignment a, double old_x, double new_x);
+
+ void set_alignment_start(const alignment a, const double x);
+
+ std::map<mousebtn, tags::action_t> get_actions(int x) const;
+ action_t has_action(mousebtn btn, int x) const;
+
+ string get_action(action_t id) const;
+ bool has_double_click() const;
+
+ size_t num_actions() const;
+ size_t num_unclosed() const;
+
+ const std::vector<action_block>& get_blocks() const;
+
+ protected:
+ void set_start(action_t id, double x);
+ void set_end(action_t id, double x);
+
+ /**
+ * Stores all currently known action blocks.
+ *
+ * The action_t type is an index into this vector.
+ */
+ std::vector<action_block> m_action_blocks;
+
+ /**
+ * Stores the x-coordinate for the start of all the alignment blocks.
+ *
+ * This is needed because the action block coordinates are relative to the
+ * alignment blocks and thus need the alignment block coordinates for
+ * intersection tests.
+ */
+ std::map<alignment, double> m_align_start{
+ {alignment::NONE, 0}, {alignment::LEFT, 0}, {alignment::CENTER, 0}, {alignment::RIGHT, 0}};
+ };
+
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "tags/types.hpp"
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+ /**
+ * Stores all the formatting data which comes from formatting tags needed to
+ * render a formatting string.
+ */
+ class context {
+ public:
+ context() = delete;
+ context(const bar_settings& settings);
+
+ /**
+ * Resets to the initial state.
+ *
+ * The initial state depends on the colors set on the bar itself.
+ */
+ void reset();
+
+ void apply_bg(color_value c);
+ void apply_fg(color_value c);
+ void apply_ol(color_value c);
+ void apply_ul(color_value c);
+ void apply_font(int font);
+ void apply_reverse();
+ void apply_alignment(alignment align);
+ void apply_attr(attr_activation act, attribute attr);
+ void apply_reset();
+ void store_tray_position(int x_pos);
+
+ rgba get_bg() const;
+ rgba get_fg() const;
+ rgba get_ol() const;
+ rgba get_ul() const;
+ int get_font() const;
+ bool has_overline() const;
+ bool has_underline() const;
+ alignment get_alignment() const;
+
+ std::pair<alignment, int> get_relative_tray_position() const;
+
+ protected:
+ /**
+ * Background color
+ */
+ rgba m_bg{};
+ /**
+ * Foreground color
+ */
+ rgba m_fg{};
+ /**
+ * Overline color
+ */
+ rgba m_ol{};
+ /**
+ * Underline color
+ */
+ rgba m_ul{};
+ /**
+ * Font index (1-based)
+ */
+ int m_font{0};
+ /**
+ * Is overline enabled?
+ */
+ bool m_attr_overline{false};
+ /**
+ * Is underline enabled?
+ */
+ bool m_attr_underline{false};
+ /**
+ * Alignment block
+ */
+ alignment m_align{alignment::NONE};
+
+ std::pair<alignment, int> m_relative_tray_position{alignment::NONE, 0};
+
+ private:
+ const bar_settings &m_settings;
+ };
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/renderer_interface.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+class signal_emitter;
+
+enum class mousebtn;
+struct bar_settings;
+class logger;
+
+namespace tags {
+ enum class attribute;
+ enum class controltag;
+
+ /**
+ * Calls into the tag parser to parse the given formatting string and then
+ * sends the right signals for each tag.
+ */
+ class dispatch {
+ public:
+ using make_type = unique_ptr<dispatch>;
+ static make_type make(action_context& action_ctxt);
+
+ explicit dispatch(const logger& logger, action_context& action_ctxt);
+ void parse(const bar_settings& bar, renderer_interface&, const string&& data);
+
+ protected:
+ void handle_text(renderer_interface& renderer, string&& data);
+ void handle_action(renderer_interface& renderer, mousebtn btn, bool closing, const string&& cmd);
+ void handle_offset(renderer_interface& renderer, extent_val offset);
+ void handle_alignment(renderer_interface& renderer, alignment a);
+ void handle_control(renderer_interface& renderer, controltag ctrl);
+
+ private:
+ const logger& m_log;
+
+ unique_ptr<context> m_ctxt;
+ action_context& m_action_ctxt;
+ };
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "tags/types.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+
+ static constexpr char EOL = '\0';
+
+ class error : public application_error {
+ public:
+ using application_error::application_error;
+
+ explicit error(const string& msg) : application_error(msg), msg(msg) {}
+
+ /**
+ * Context string that contains the text region where the parser error
+ * happened.
+ */
+ void set_context(const string& ctxt) {
+ msg.append(" (Context: '" + ctxt + "')");
+ }
+
+ virtual const char* what() const noexcept override {
+ return msg.c_str();
+ }
+
+ private:
+ string msg;
+ };
+
+#define DEFINE_INVALID_ERROR(class_name, name) \
+ class class_name : public error { \
+ public: \
+ explicit class_name(const string& val) : error("Invalid " name ": '" + val + "'") {} \
+ explicit class_name(const string& val, const string& what) \
+ : error("Invalid " name ": '" + val + "' (reason: '" + what + "')") {} \
+ }
+
+ DEFINE_INVALID_ERROR(color_error, "color");
+ DEFINE_INVALID_ERROR(font_error, "font index");
+ DEFINE_INVALID_ERROR(control_error, "control tag");
+ DEFINE_INVALID_ERROR(offset_error, "offset");
+ DEFINE_INVALID_ERROR(btn_error, "button id");
+#undef DEFINE_INVALID_ERROR
+
+ class token_error : public error {
+ public:
+ explicit token_error(char token, char expected) : token_error(string{token}, string{expected}) {}
+ explicit token_error(char token, const string& expected) : token_error(string{token}, expected) {}
+ explicit token_error(const string& token, const string& expected)
+ : error("Expected '" + expected + "' but found '" +
+ (token.size() == 1 && token.at(0) == EOL ? "<End Of Line>" : token) + "'") {}
+ };
+
+ class unrecognized_tag : public error {
+ public:
+ explicit unrecognized_tag(char tag) : error("Unrecognized formatting tag '%{" + string{tag} + "}'") {}
+ };
+
+ class unrecognized_attr : public error {
+ public:
+ explicit unrecognized_attr(char attr) : error("Unrecognized attribute '" + string{attr} + "'") {}
+ };
+
+ /**
+ * Thrown when we expect the end of a tag (either } or a space in a compound
+ * tag.
+ */
+ class tag_end_error : public error {
+ public:
+ explicit tag_end_error(char token)
+ : error("Expected the end of a tag ('}' or ' ') but found '" +
+ (token == EOL ? "<End Of Line>" : string{token}) + "'") {}
+ };
+
+ /**
+ * Recursive-descent parser for polybar's formatting tags.
+ *
+ * An input string is parsed into a list of elements, each element is either
+ * a piece of text or a single formatting tag.
+ *
+ * The elements can either be retrieved one-by-one with next_element() or all
+ * at once with parse().
+ */
+ class parser {
+ public:
+ /**
+ * Resets the parser state and sets the new string to parse
+ */
+ void set(const string&& input);
+
+ /**
+ * Whether a call to next_element() suceeds.
+ */
+ bool has_next_element();
+
+ /**
+ * Parses at least one element (if available) and returns the first parsed
+ * element.
+ */
+ element next_element();
+
+ /**
+ * Parses the remaining string and returns all parsed elements.
+ */
+ format_string parse();
+
+ protected:
+ void parse_step();
+
+ bool has_next() const;
+ char next();
+ char peek() const;
+ void revert();
+
+ void consume(char c);
+ void consume_space();
+
+ void parse_tag();
+
+ void parse_single_tag_content();
+
+ color_value parse_color();
+ int parse_fontindex();
+ extent_val parse_offset();
+ controltag parse_control();
+ std::pair<action_value, string> parse_action();
+ mousebtn parse_action_btn();
+ string parse_action_cmd();
+ attribute parse_attribute();
+
+ void push_char(char c);
+ void push_text(string&& text);
+
+ string get_tag_value();
+
+ private:
+ string input;
+ size_t pos = 0;
+
+ /**
+ * Buffers elements that have been parsed but not yet returned to the user.
+ */
+ format_string buf{};
+ /**
+ * Index into buf so that we don't have to call vector.erase everytime.
+ *
+ * Only buf[buf_pos, buf.end()) contain valid elements
+ */
+ size_t buf_pos;
+ };
+
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+
+ enum class attribute { NONE = 0, UNDERLINE, OVERLINE };
+
+ enum class attr_activation { NONE, ON, OFF, TOGGLE };
+
+ enum class syntaxtag {
+ A, // mouse action
+ B, // background color
+ F, // foreground color
+ T, // font index
+ O, // pixel offset
+ R, // flip colors
+ o, // overline color
+ u, // underline color
+ P, // Polybar control tag
+ l, // Left alignment
+ r, // Right alignment
+ c, // Center alignment
+ };
+
+ /**
+ * Values for polybar control tags
+ *
+ * %{P...} tags are tags for internal polybar control commands, they are not
+ * part of the public interface
+ */
+ enum class controltag {
+ NONE = 0,
+ R, // Reset all open tags (B, F, T, o, u). Used at module edges
+ t // special tag for trays
+ };
+
+ enum class color_type { RESET = 0, COLOR };
+
+ struct color_value {
+ /**
+ * ARGB color.
+ *
+ * Only used if type == COLOR
+ */
+ rgba val{};
+ color_type type;
+ };
+
+ /**
+ * Stores information about an action
+ *
+ * The actual command string is stored in element.data
+ */
+ struct action_value {
+ /**
+ * NONE is only allowed for closing tags
+ */
+ mousebtn btn;
+ bool closing;
+ };
+
+ enum class tag_type { ATTR, FORMAT };
+
+ union tag_subtype {
+ syntaxtag format;
+ attr_activation activation;
+ };
+
+ struct tag {
+ tag_type type;
+ tag_subtype subtype;
+ union {
+ /**
+ * Used for 'F', 'G', 'o', 'u' formatting tags.
+ */
+ color_value color;
+ /**
+ * For for 'A' tags
+ */
+ action_value action;
+ /**
+ * For for 'T' tags
+ */
+ int font;
+ /**
+ * For for 'O' tags
+ */
+ extent_val offset;
+ /**
+ * For for 'P' tags
+ */
+ controltag ctrl;
+
+ /**
+ * For attribute activations ((-|+|!)(o|u))
+ */
+ attribute attr;
+ };
+ };
+
+ struct element {
+ element(){};
+ element(const string&& text) : data{std::move(text)}, is_tag{false} {};
+
+ string data{};
+ tag tag_data{};
+ bool is_tag{false};
+ };
+
+ using format_string = vector<element>;
+
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cassert>
+#include <stdexcept>
+#include <unordered_map>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+/**
+ * Maps action names to lambdas and invokes them.
+ *
+ * Each module has one instance of this class and uses it to register action.
+ * For each action the module has to register the name, whether it can take
+ * additional data, and a callback that is called whenever the action is triggered.
+ *
+ * The input() function in the base class uses this for invoking the actions
+ * of that module.
+ *
+ * Any module that does not reimplement that function will automatically use
+ * this class for action routing.
+ */
+class action_router {
+ using callback = std::function<void(void)>;
+ using callback_data = std::function<void(const std::string&)>;
+
+ public:
+ void register_action(const string& name, callback func);
+ void register_action_with_data(const string& name, callback_data func);
+ bool has_action(const string& name);
+ void invoke(const string& name, const string& data);
+
+ protected:
+ struct entry {
+ union {
+ callback without;
+ callback_data with;
+ };
+ bool with_data;
+
+ entry(callback func);
+ entry(callback_data func);
+ ~entry();
+ };
+
+ template <typename F>
+ void register_entry(const string& name, const F& e) {
+ if (has_action(name)) {
+ throw std::invalid_argument("Tried to register action '" + name + "' twice. THIS IS A BUG!");
+ }
+
+ callbacks.emplace(name, std::move(e));
+ }
+
+ private:
+ std::unordered_map<string, entry> callbacks;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+namespace modules {
+ struct module_interface;
+} // namespace modules
+
+POLYBAR_NS
+
+namespace actions_util {
+
+ using action = std::tuple<string, string, string>;
+
+ string get_action_string(const modules::module_interface& module, string action, string data);
+ string get_action_string(const string& module_name, string action, string data);
+
+ /**
+ * Parses an action string of the form "#name.action[.data]".
+ *
+ * Only call this function with an action string that begins with '#'.
+ *
+ * @returns a triple (name, action, data)
+ * If no data exists, the third string will be empty.
+ * This means "#name.action." and "#name.action" will be produce the
+ * same result.
+ * @throws runtime_error If the action string is malformed
+ */
+ action parse_action_string(string action);
+} // namespace actions_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "utils/restack.hpp"
+#include "utils/socket.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace bspwm_util {
+using connection_t = unique_ptr<socket_util::unix_connection>;
+
+struct payload {
+ char data[BUFSIZ]{'\0'};
+ size_t len = 0;
+};
+
+using payload_t = unique_ptr<payload>;
+
+restack_util::params get_restack_params(connection& conn);
+
+string get_socket_path();
+
+payload_t make_payload(const string& cmd);
+connection_t make_connection();
+connection_t make_subscriber();
+} // namespace bspwm_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cstdlib>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+/**
+ * Represents immutable 32-bit color values.
+ */
+class rgba {
+ public:
+ enum class type { NONE = 0, ARGB, ALPHA_ONLY };
+
+ explicit rgba();
+ explicit rgba(uint32_t value, type type = type::ARGB);
+ explicit rgba(string hex);
+
+ operator string() const;
+ operator uint32_t() const;
+ operator bool() const;
+ bool operator==(const rgba& other) const;
+ bool operator!=(const rgba& other) const;
+
+ uint32_t value() const;
+ type get_type() const;
+
+ double alpha_d() const;
+ double red_d() const;
+ double green_d() const;
+ double blue_d() const;
+
+ uint8_t alpha_i() const;
+ uint8_t red_i() const;
+ uint8_t green_i() const;
+ uint8_t blue_i() const;
+
+ bool has_color() const;
+ bool is_transparent() const;
+ rgba apply_alpha_to(rgba other) const;
+ rgba try_apply_alpha_to(rgba other) const;
+
+ private:
+ /**
+ * Color value in the form ARGB or A000 depending on the type
+ *
+ * Cannot be const because we have to assign to it in the constructor and initializer lists are not possible.
+ */
+ uint32_t m_value;
+
+ /**
+ * NONE marks this instance as invalid. If such a color is encountered, it
+ * should be treated as if no color was set.
+ *
+ * ALPHA_ONLY is used for color strings that only have an alpha channel (#AA)
+ * these kinds should be combined with another color that has RGB channels
+ * before they are used to render anything.
+ *
+ * Cannot be const because we have to assign to it in the constructor and initializer lists are not possible.
+ */
+ enum type m_type { type::NONE };
+};
+
+namespace color_util {
+ string simplify_hex(string hex);
+} // namespace color_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(command_error);
+
+enum class output_policy {
+ REDIRECTED,
+ IGNORED,
+};
+
+/**
+ * Wrapper used to execute command in a subprocess.
+ * In-/output streams are opened to enable ipc.
+ * If the command is created using command<output_policy::REDIRECTED>, the child streams are
+ * redirected and you can read the program output or write into the program input.
+ *
+ * If the command is created using command<output_policy::IGNORED>, the output is not redirected and
+ * you can't communicate with the child program.
+ *
+ * Example usage:
+ *
+ * @code cpp
+ * command<output_policy::REDIRECTED>auto(m_log, "cat /etc/rc.local");
+ * cmd->exec();
+ * cmd->tail([](string s) { std::cout << s << std::endl; });
+ * @endcode
+ *
+ * @code cpp
+ * command<output_policy::REDIRECTED>auto(m_log, "for i in 1 2 3; do echo $i; done");
+ * cmd->exec();
+ * cout << cmd->readline(); // 1
+ * cout << cmd->readline() << cmd->readline(); // 23
+ * @endcode
+ *
+ * @code cpp
+ * command<output_policy::IGNORED>auto(m_log, "ping kernel.org");
+ * int status = cmd->exec();
+ * @endcode
+ */
+template <output_policy>
+class command;
+
+template <>
+class command<output_policy::IGNORED> {
+ public:
+ explicit command(const logger& logger, string cmd);
+ command(const command&) = delete;
+ ~command();
+
+ command& operator=(const command&) = delete;
+
+ int exec(bool wait_for_completion = true);
+ void terminate();
+ bool is_running();
+ int wait();
+
+ pid_t get_pid();
+ int get_exit_status();
+
+ protected:
+ const logger& m_log;
+
+ string m_cmd;
+
+ pid_t m_forkpid{-1};
+ int m_forkstatus{-1};
+};
+
+template <>
+class command<output_policy::REDIRECTED> : private command<output_policy::IGNORED> {
+ public:
+ explicit command(const logger& logger, string cmd);
+ command(const command&) = delete;
+ ~command();
+
+ command& operator=(const command&) = delete;
+
+ int exec(bool wait_for_completion = true, const vector<pair<string, string>>& env = {});
+ using command<output_policy::IGNORED>::terminate;
+ using command<output_policy::IGNORED>::is_running;
+ using command<output_policy::IGNORED>::wait;
+
+ using command<output_policy::IGNORED>::get_pid;
+ using command<output_policy::IGNORED>::get_exit_status;
+
+ void tail(std::function<void(string)> cb);
+ string readline();
+
+ int get_stdout(int c);
+ int get_stdin(int c);
+
+ protected:
+ int m_stdout[2]{0, 0};
+ int m_stdin[2]{0, 0};
+
+ unique_ptr<fd_stream<std::istream>> m_stdout_reader{nullptr};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <mutex>
+#include <thread>
+
+#include "common.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+using namespace std::chrono_literals;
+namespace this_thread = std::this_thread;
+
+using std::mutex;
+using std::thread;
+
+/**
+ * Types wrapped in this type can be used as locks (e.g. for lock_guard).
+ */
+template <typename T>
+class mutex_wrapper : public T {
+ public:
+ template <typename... Args>
+ explicit mutex_wrapper(Args&&... args) : T(forward<Args>(args)...) {}
+
+ void lock() const noexcept {
+ m_mtx.lock();
+ }
+ void unlock() const noexcept {
+ m_mtx.unlock();
+ };
+
+ private:
+ mutable mutex m_mtx;
+};
+
+namespace concurrency_util {
+ size_t thread_id(const thread::id id);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace env_util {
+ bool has(const string& var);
+ string get(const string& var, string fallback = "");
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <unistd.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace factory_util {
+ template <class T, class... Deps>
+ shared_ptr<T> singleton(Deps&&... deps) {
+ static shared_ptr<T> instance{make_shared<T>(forward<Deps>(deps)...)};
+ return instance;
+ }
+} // namespace factory_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <streambuf>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class file_descriptor {
+ public:
+ explicit file_descriptor(const string& path, int flags = 0, bool autoclose = true);
+ explicit file_descriptor(int fd, bool autoclose = true);
+ ~file_descriptor();
+
+ file_descriptor& operator=(const int);
+
+ explicit operator bool();
+ operator bool() const;
+
+ explicit operator int();
+ operator int() const;
+
+ protected:
+ void close();
+
+ private:
+ int m_fd{-1};
+ bool m_autoclose{true};
+};
+
+class fd_streambuf : public std::streambuf {
+ public:
+ using traits_type = std::streambuf::traits_type;
+
+ template <typename... Args>
+ explicit fd_streambuf(Args&&... args) : m_fd(forward<Args>(args)...) {
+ setg(m_in, m_in + BUFSIZE_IN, m_in + BUFSIZE_IN);
+ setp(m_out, m_out + BUFSIZE_OUT - 1);
+ }
+ ~fd_streambuf();
+
+ explicit operator int();
+ operator int() const;
+
+ void open(int fd);
+ void close();
+
+ protected:
+ int sync() override;
+ int overflow(int c) override;
+ int underflow() override;
+
+ private:
+ static constexpr int BUFSIZE_OUT = 1024;
+ static constexpr int BUFSIZE_IN = 1024;
+ file_descriptor m_fd;
+ char m_out[BUFSIZE_OUT];
+ char m_in[BUFSIZE_IN];
+};
+
+template <typename StreamType>
+class fd_stream : public StreamType {
+ public:
+ using type = fd_stream<StreamType>;
+
+ template <typename... Args>
+ explicit fd_stream(Args&&... args) : StreamType(nullptr), m_buf(forward<Args>(args)...) {
+ StreamType::rdbuf(&m_buf);
+ }
+
+ explicit operator int() {
+ return static_cast<const type&>(*this);
+ }
+
+ operator int() const {
+ return m_buf;
+ }
+
+ protected:
+ fd_streambuf m_buf;
+};
+
+namespace file_util {
+ bool exists(const string& filename);
+ bool is_file(const string& filename);
+ bool is_dir(const string& filename);
+ string pick(const vector<string>& filenames);
+ string contents(const string& filename);
+ void write_contents(const string& filename, const string& contents);
+ bool is_fifo(const string& filename);
+ vector<string> glob(string pattern);
+ string expand(const string& path, const string& relative_to = {});
+ string get_config_path();
+ vector<string> list_files(const string& dirname);
+ string dirname(const string& path);
+
+ template <typename... Args>
+ decltype(auto) make_file_descriptor(Args&&... args) {
+ return std::make_unique<file_descriptor>(forward<Args>(args)...);
+ }
+} // namespace file_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+class http_downloader : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ http_downloader(int connection_timeout = 5);
+ ~http_downloader();
+
+ string get(const string& url, const string& user = "", const string& password = "");
+ long response_code();
+
+ protected:
+ static size_t write(void* p, size_t size, size_t bytes, void* stream);
+
+ private:
+ void* m_curl;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <i3ipc++/ipc.hpp>
+
+#include "common.hpp"
+#include "utils/restack.hpp"
+#include "x11/extensions/randr.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace i3_util {
+ using connection_t = i3ipc::connection;
+ using workspace_t = i3ipc::workspace_t;
+
+ const auto ws_numsort = [](shared_ptr<workspace_t> a, shared_ptr<workspace_t> b) { return a->num < b->num; };
+
+ vector<shared_ptr<workspace_t>> workspaces(const connection_t& conn, const string& output = "", const bool show_urgent = false);
+ shared_ptr<workspace_t> focused_workspace(const connection_t&);
+
+ vector<xcb_window_t> root_windows(connection& conn, const string& output_name = "");
+ restack_util::params get_restack_params(connection& conn);
+}
+
+namespace {
+ inline bool operator==(i3_util::workspace_t& a, i3_util::workspace_t& b) {
+ return a.num == b.num && a.output == b.output;
+ }
+ inline bool operator!=(i3_util::workspace_t& a, i3_util::workspace_t& b) {
+ return !(a == b);
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <poll.h>
+#include <sys/inotify.h>
+
+#include <cstdio>
+
+#include "common.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+struct inotify_event {
+ bool is_valid = false;
+ string filename;
+ bool is_dir;
+ int wd = 0;
+ int cookie = 0;
+ int mask = 0;
+};
+
+class inotify_watch : public non_copyable_mixin {
+ public:
+ explicit inotify_watch(string path);
+ ~inotify_watch();
+
+ inotify_watch(inotify_watch&& other) noexcept;
+ inotify_watch& operator=(inotify_watch&& other) noexcept;
+
+ void attach(int mask = IN_MODIFY);
+ void remove(bool force = false);
+ bool poll(int wait_ms = 1000) const;
+ inotify_event get_event() const;
+ string path() const;
+ int get_file_descriptor() const;
+
+ protected:
+ string m_path;
+ int m_fd{-1};
+ int m_wd{-1};
+ int m_mask{0};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace io_util {
+ void tail(int read_fd, const function<void(string)>& callback);
+
+ bool poll(int fd, short int events, int timeout_ms = 0);
+ bool poll_read(int fd, int timeout_ms = 0);
+} // namespace io_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace math_util {
+ /**
+ * Limit value T by min and max bounds
+ */
+ template <typename ValueType>
+ ValueType cap(ValueType value, ValueType min_value, ValueType max_value) {
+ value = std::min<ValueType>(value, max_value);
+ value = std::max<ValueType>(value, min_value);
+ return value;
+ }
+
+ /**
+ * Calculate the percentage for a value
+ * between min_value and max_value
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType unbounded_percentage(ValueType value, ValueType min_value, ValueType max_value) {
+ auto upper = (max_value - min_value);
+ auto lower = static_cast<float>(value - min_value);
+ float percentage = (lower / upper) * 100.0f;
+ if (std::is_integral<ReturnType>()) {
+ return static_cast<ReturnType>(std::round(percentage));
+ } else {
+ return percentage;
+ }
+ }
+
+ /**
+ * Calculates percentage for a value and
+ * clamps it to a percentage between 0 and 100
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType percentage(ValueType value, ValueType min_value, ValueType max_value) {
+ auto raw_percentage = unbounded_percentage<ValueType, ReturnType>(value, min_value, max_value);
+ return cap<ReturnType>(raw_percentage, 0.0f, 100.0f);
+ }
+
+ /**
+ * Calculates percentage for a value and
+ * clamps it to a percentage between 0 and 100
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType percentage(ValueType value, ValueType max_value) {
+ return percentage<ValueType, ReturnType>(value, max_value - max_value, max_value);
+ }
+
+ /**
+ * Get value for signed percentage of `max_value` (cap between -max_value and max_value)
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType signed_percentage_to_value(ValueType signed_percentage, ValueType max_value) {
+ if (std::is_integral<ReturnType>())
+ return cap<ReturnType>(signed_percentage * max_value / 100.0f + 0.5f, -max_value, max_value);
+ else
+ return cap<ReturnType>(signed_percentage * max_value / 100.0f, -max_value, max_value);
+ }
+
+ /**
+ * Get value for percentage of `max_value` (cap between 0 and max_value)
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType percentage_to_value(ValueType percentage, ValueType max_value) {
+ if (std::is_integral<ReturnType>())
+ return cap<ReturnType>(percentage * max_value / 100.0f + 0.5f, 0, max_value);
+ else
+ return cap<ReturnType>(percentage * max_value / 100.0f, 0.0f, max_value);
+ }
+
+ /**
+ * Get value for percentage of `min_value` to `max_value`
+ */
+ template <typename ValueType, typename ReturnType = int>
+ ReturnType percentage_to_value(ValueType percentage, ValueType min_value, ValueType max_value) {
+ if (std::is_integral<ReturnType>())
+ return cap<ReturnType>(percentage * (max_value - min_value) / 100.0f + 0.5f, 0, max_value - min_value) +
+ min_value;
+ else
+ return cap<ReturnType>(percentage * (max_value - min_value) / 100.0f, 0.0f, max_value - min_value) + min_value;
+ }
+
+ template <typename ReturnType = int>
+ ReturnType nearest_10(double value) {
+ return static_cast<ReturnType>(static_cast<int>(value / 10.0 + 0.5) * 10.0);
+ }
+
+ template <typename ReturnType = int>
+ ReturnType nearest_5(double value) {
+ return static_cast<ReturnType>(static_cast<int>(value / 5.0 + 0.5) * 5.0);
+ }
+
+ inline int ceil(double value, int step = 1) {
+ return static_cast<int>((value * 10 + step * 10 - 1) / (step * 10)) * step;
+ }
+} // namespace math_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cstdlib>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+template <typename T>
+using malloc_unique_ptr = unique_ptr<T, decltype(free)*>;
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+/**
+ * Base class for non copyable objects
+ */
+class non_copyable_mixin {
+ public:
+ non_copyable_mixin(const non_copyable_mixin&) = delete;
+ non_copyable_mixin& operator=(const non_copyable_mixin&) = delete;
+
+ protected:
+ non_copyable_mixin() = default;
+ ~non_copyable_mixin() = default;
+};
+
+/**
+ * Base class for non movable objects
+ */
+class non_movable_mixin {
+ public:
+ non_movable_mixin(non_movable_mixin&&) = delete;
+ non_movable_mixin& operator=(non_movable_mixin&&) = delete;
+
+ protected:
+ non_movable_mixin() = default;
+ ~non_movable_mixin() = default;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <sys/types.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace process_util {
+ bool in_parent_process(pid_t pid);
+ bool in_forked_process(pid_t pid);
+
+ void redirect_stdio_to_dev_null();
+
+ pid_t spawn_async(std::function<void()> const& lambda);
+ void fork_detached(std::function<void()> const& lambda);
+
+ void exec(char* cmd, char** args);
+ void exec_sh(const char* cmd, const vector<pair<string, string>>& env = {});
+
+ int wait(pid_t pid);
+
+ pid_t wait_for_completion(pid_t process_id, int* status_addr = nullptr, int waitflags = 0);
+ pid_t wait_for_completion(int* status_addr, int waitflags = 0);
+ pid_t wait_for_completion_nohang(pid_t process_id, int* status);
+ pid_t wait_for_completion_nohang(int* status);
+ pid_t wait_for_completion_nohang();
+
+ bool notify_childprocess();
+} // namespace process_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+
+POLYBAR_NS
+
+namespace restack_util {
+using params = std::pair<xcb_window_t, xcb_stack_mode_t>;
+
+static constexpr params NONE_PARAMS = {XCB_NONE, XCB_STACK_MODE_ABOVE};
+
+void restack_relative(connection& conn, xcb_window_t win, xcb_window_t sibling, xcb_stack_mode_t stack_mode);
+string stack_mode_to_string(xcb_stack_mode_t mode);
+bool are_siblings(connection& conn, xcb_window_t win, xcb_window_t sibling);
+params get_bottom_params(connection& conn, xcb_window_t bar_window);
+params get_ewmh_params(connection& conn);
+params get_generic_params(connection& conn, xcb_window_t bar_window);
+} // namespace restack_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace scope_util {
+ /**
+ * Creates a wrapper that will trigger given callback when
+ * leaving the object's scope (i.e, when it gets destroyed)
+ *
+ * Example usage:
+ * @code cpp
+ * {
+ * on_exit handler([]{ ... });
+ * ...
+ * }
+ * @endcode
+ */
+ class on_exit {
+ public:
+ on_exit(const function<void(void)>& fn) : m_callback(fn) {}
+
+ virtual ~on_exit() {
+ m_callback();
+ }
+
+ protected:
+ function<void(void)> m_callback;
+ };
+} // namespace scope_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <poll.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace socket_util {
+ class unix_connection {
+ public:
+ explicit unix_connection(string&& path);
+
+ ~unix_connection() noexcept;
+
+ int disconnect();
+
+ ssize_t send(const void* data, size_t len, int flags = 0);
+ ssize_t send(const string& data, int flags = 0);
+
+ string receive(const ssize_t receive_bytes, int flags = 0);
+ string receive(const ssize_t receive_bytes, ssize_t* bytes_received, int flags = 0);
+
+ bool peek(const size_t peek_bytes);
+ bool poll(short int events = POLLIN, int timeout_ms = -1);
+
+ protected:
+ int m_fd = -1;
+ string m_socketpath;
+ };
+
+ /**
+ * Creates a wrapper for a unix socket connection
+ *
+ * Example usage:
+ * @code cpp
+ * auto conn = socket_util::make_unix_connection("/tmp/socket");
+ * conn->send(...);
+ * conn->receive(...);
+ * @endcode
+ */
+ inline unique_ptr<unix_connection> make_unix_connection(string&& path) {
+ return std::make_unique<unix_connection>(forward<string>(path));
+ }
+} // namespace socket_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <sstream>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class sstream {
+ public:
+ template <typename T>
+ sstream& operator<<(const T& object) {
+ m_stream << object;
+ return *this;
+ }
+
+ sstream& operator<<(const char* cz) {
+ m_stream << cz;
+ return *this;
+ }
+
+ operator string() const {
+ return m_stream.str();
+ }
+
+ string to_string() const {
+ return m_stream.str();
+ }
+
+ private:
+ std::stringstream m_stream;
+};
+
+namespace string_util {
+/**
+ * Hash type
+ */
+using hash_type = unsigned long;
+
+/**
+ * @brief Unicode character containing converted codepoint
+ * and details on where its position in the source string
+ */
+struct unicode_character {
+ /**
+ * The numerical codepoint. Between U+0000 and U+10FFFF
+ */
+ uint32_t codepoint{0};
+ /**
+ * Byte offset of this character in the original string
+ */
+ int offset{0};
+ /**
+ * Number of bytes used by this character in the original string
+ */
+ int length{0};
+};
+using unicode_charlist = std::vector<unicode_character>;
+
+bool contains(const string& haystack, const string& needle);
+bool contains_ignore_case(const string& haystack, const string& needle);
+bool ends_with(const string& haystack, const string& suffix);
+string upper(const string& s);
+string lower(const string& s);
+bool compare(const string& s1, const string& s2);
+
+string replace(const string& haystack, const string& needle, const string& replacement, size_t start = 0,
+ size_t end = string::npos);
+string replace_all(const string& haystack, const string& needle, const string& replacement, size_t start = 0,
+ size_t end = string::npos);
+
+string squeeze(const string& haystack, char needle);
+
+string strip(const string& haystack, char needle);
+string strip_trailing_newline(const string& haystack);
+
+string ltrim(string value, function<bool(char)> pred);
+string rtrim(string value, function<bool(char)> pred);
+string trim(string value, function<bool(char)> pred);
+
+string ltrim(string&& value, const char& needle = ' ');
+string rtrim(string&& value, const char& needle = ' ');
+string trim(string&& value, const char& needle = ' ');
+
+size_t char_len(const string& value);
+string utf8_truncate(string&& value, size_t len);
+[[nodiscard]] bool utf8_to_ucs4(const string& src, unicode_charlist& result_list);
+size_t ucs4_to_utf8(std::array<char, 5>& utf8, unsigned int ucs);
+
+string join(const vector<string>& strs, const string& delim);
+vector<string> split(const string& s, char delim);
+std::vector<std::string> tokenize(const string& str, char delimiters);
+
+size_t find_nth(const string& haystack, size_t pos, const string& needle, size_t nth);
+
+string floating_point(double value, size_t precision, bool fixed = false, const string& locale = "");
+string filesize_mib(unsigned long long kibibytes, size_t precision = 0, const string& locale = "");
+string filesize_gib(unsigned long long kibibytes, size_t precision = 0, const string& locale = "");
+string filesize_gib_mib(
+ unsigned long long kibibytes, size_t precision_mib = 0, size_t precision_gib = 0, const string& locale = "");
+string filesize(unsigned long long kbytes, size_t precision = 0, bool fixed = false, const string& locale = "");
+
+hash_type hash(const string& src);
+} // namespace string_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <chrono>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+namespace time_util {
+ using clock_t = chrono::high_resolution_clock;
+
+ template <typename Duration = chrono::milliseconds, typename T>
+ auto measure(const T& expr) noexcept {
+ auto start = clock_t::now();
+ expr();
+ auto finish = clock_t::now();
+ return chrono::duration_cast<Duration>(finish - start).count();
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <cassert>
+#include <cmath>
+#include <stdexcept>
+#include <string>
+
+#include "components/types.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace units_utils {
+ int point_to_pixel(double point, double dpi);
+
+ int extent_to_pixel(const extent_val size, double dpi);
+ unsigned extent_to_pixel_nonnegative(const extent_val size, double dpi);
+
+ extent_type parse_extent_unit(const string& str);
+ extent_val parse_extent(const string& str);
+
+ string extent_to_string(extent_val extent);
+
+ int percentage_with_offset_to_pixel(percentage_with_offset g_format, double max, double dpi);
+ unsigned percentage_with_offset_to_pixel_nonnegative(percentage_with_offset g_format, double max, double dpi);
+
+ spacing_type parse_spacing_unit(const string& str);
+ spacing_val parse_spacing(const string& str);
+
+ extent_val spacing_to_extent(spacing_val spacing);
+
+} // namespace units_utils
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb_atom.h>
+
+#include <array>
+#include <string>
+
+struct cached_atom {
+ const std::string name;
+ xcb_atom_t& atom;
+};
+
+extern std::array<cached_atom, 38> ATOMS;
+
+extern xcb_atom_t _NET_SUPPORTED;
+extern xcb_atom_t _NET_CURRENT_DESKTOP;
+extern xcb_atom_t _NET_ACTIVE_WINDOW;
+extern xcb_atom_t _NET_WM_NAME;
+extern xcb_atom_t _NET_WM_DESKTOP;
+extern xcb_atom_t _NET_WM_VISIBLE_NAME;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
+extern xcb_atom_t _NET_WM_PID;
+extern xcb_atom_t _NET_WM_STATE;
+extern xcb_atom_t _NET_WM_STATE_STICKY;
+extern xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
+extern xcb_atom_t _NET_WM_STATE_ABOVE;
+extern xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
+extern xcb_atom_t _NET_WM_STRUT;
+extern xcb_atom_t _NET_WM_STRUT_PARTIAL;
+extern xcb_atom_t WM_PROTOCOLS;
+extern xcb_atom_t WM_DELETE_WINDOW;
+extern xcb_atom_t _XEMBED;
+extern xcb_atom_t _XEMBED_INFO;
+extern xcb_atom_t MANAGER;
+extern xcb_atom_t WM_STATE;
+extern xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
+extern xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
+extern xcb_atom_t _NET_SYSTEM_TRAY_VISUAL;
+extern xcb_atom_t _NET_SYSTEM_TRAY_COLORS;
+extern xcb_atom_t WM_TAKE_FOCUS;
+extern xcb_atom_t Backlight;
+extern xcb_atom_t BACKLIGHT;
+extern xcb_atom_t _XROOTPMAP_ID;
+extern xcb_atom_t _XSETROOT_ID;
+extern xcb_atom_t ESETROOT_PMAP_ID;
+extern xcb_atom_t _COMPTON_SHADOW;
+extern xcb_atom_t _NET_WM_WINDOW_OPACITY;
+extern xcb_atom_t WM_HINTS;
+extern xcb_atom_t WM_NAME;
+extern xcb_atom_t WM_CLASS;
--- /dev/null
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "common.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "x11/extensions/fwd.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+class logger;
+
+namespace cairo {
+ class surface;
+ class xcb_surface;
+} // namespace cairo
+
+class bg_slice {
+ public:
+ ~bg_slice();
+ // copying bg_slices is not allowed
+ bg_slice(const bg_slice&) = delete;
+ bg_slice& operator=(const bg_slice&) = delete;
+
+ cairo::surface* get_surface() const;
+
+ void clear();
+ void copy(xcb_pixmap_t root_pixmap, int depth, xcb_rectangle_t geom, xcb_visualtype_t* visual);
+
+ private:
+ bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window);
+
+ connection& m_connection;
+ const logger& m_log;
+
+ /**
+ * Area covered by this slice
+ *
+ * Area is relative to given window
+ */
+ xcb_rectangle_t m_rect{0, 0, 0U, 0U};
+ xcb_window_t m_window;
+
+ /**
+ * Cache for the root window background at this slice's position
+ */
+ xcb_pixmap_t m_pixmap{XCB_NONE};
+ unique_ptr<cairo::xcb_surface> m_surface;
+ xcb_gcontext_t m_gcontext{XCB_NONE};
+ int m_depth{0};
+
+ void ensure_resources(int depth, xcb_visualtype_t* visual);
+ void allocate_resources(xcb_visualtype_t* visual);
+ void free_resources();
+
+ friend class background_manager;
+};
+
+/**
+ * @brief Class to keep track of the desktop background used to support pseudo-transparency
+ *
+ * For pseudo-transparency that bar needs access to the desktop background.
+ * We only need to store the slice of the background image which is covered by the bar window,
+ * so this class takes a rectangle that limits what part of the background is stored.
+ */
+class background_manager : public signal_receiver<SIGN_PRIORITY_SCREEN, signals::ui::update_geometry>,
+ public xpp::event::sink<evt::property_notify> {
+ public:
+ using make_type = background_manager&;
+ static make_type make();
+
+ /**
+ * Initializes a new background_manager that by default does not observe anything.
+ *
+ * To observe a slice of the background you need to call background_manager::activate.
+ */
+ explicit background_manager(connection& conn, signal_emitter& sig, const logger& log);
+ ~background_manager();
+
+ /**
+ * Starts observing a rectangular slice of the desktop background.
+ *
+ * After calling this function, you can obtain the current slice of the desktop background
+ * by calling get_surface on the returned bg_slice object.
+ * Whenever the background slice changes (for example, due to bar position changes or because
+ * the user changed the desktop background) the class emits a signals::ui::update_background event.
+ *
+ * You should only call this function once and then re-use the returned bg_slice because the bg_slice
+ * caches the background. If you don't need the background anymore, destroy the shared_ptr to free up
+ * resources.
+ *
+ * @param rect Slice of the background to observe (coordinates relative to window).
+ * @param window Coordinates are interpreted relative to this window
+ */
+ std::shared_ptr<bg_slice> observe(xcb_rectangle_t rect, xcb_window_t window);
+
+ void handle(const evt::property_notify& evt) override;
+ bool on(const signals::ui::update_geometry&) override;
+
+ private:
+ void activate();
+ void deactivate();
+
+ void attach();
+ void detach();
+ /**
+ * True if we are currently attached as a listener for desktop background changes
+ */
+ bool m_attached{false};
+
+ // references to standard components
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+
+ /**
+ * List of slices that need to be filled with the desktop background
+ */
+ std::vector<std::weak_ptr<bg_slice>> m_slices;
+
+ void allocate_resources();
+ void free_resources();
+ void on_background_change();
+
+ void update_slice(bg_slice& slice);
+
+ bool has_pixmap() const;
+ void ensure_pixmap();
+ void load_pixmap();
+ void clear_pixmap();
+
+ /**
+ * The loaded root pixmap
+ */
+ xcb_pixmap_t m_pixmap{XCB_NONE};
+ int m_pixmap_depth{0};
+ xcb_rectangle_t m_pixmap_geom{0, 0, 0, 0};
+
+ /**
+ * Tracks whether we were able to load a pixmap.
+ */
+ bool m_pixmap_load_failed{false};
+
+ /**
+ * Visual matching the root pixmap's depth.
+ *
+ * Only valid if m_pixmap is set
+ */
+ xcb_visualtype_t* m_visual{nullptr};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include <cstdlib>
+#include <xpp/core.hpp>
+#include <xpp/generic/factory.hpp>
+#include <xpp/proto/x.hpp>
+
+#include "common.hpp"
+#include "components/screen.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+namespace detail {
+template <typename Connection, typename... Extensions>
+class interfaces : public xpp::x::extension::interface<interfaces<Connection, Extensions...>, Connection>,
+ public Extensions::template interface<interfaces<Connection, Extensions...>, Connection>... {
+ public:
+ const Connection& connection() const {
+ return static_cast<const Connection&>(*this);
+ }
+};
+
+template <typename Derived, typename... Extensions>
+class connection_base : public xpp::core,
+ public xpp::generic::error_dispatcher,
+ public detail::interfaces<connection_base<Derived, Extensions...>, Extensions...>,
+ private xpp::x::extension,
+ private xpp::x::extension::error_dispatcher,
+ private Extensions...,
+ private Extensions::error_dispatcher... {
+ public:
+ explicit connection_base(xcb_connection_t* c, int s)
+ : xpp::core(c)
+ , interfaces<connection_base<Derived, Extensions...>, Extensions...>(*this)
+ , Extensions(m_c.get())...
+ , Extensions::error_dispatcher(static_cast<Extensions&>(*this).get())... {
+ core::m_screen = s;
+ m_root_window = screen_of_display(s)->root;
+ }
+
+ void operator()(const shared_ptr<xcb_generic_error_t>& error) const override {
+ check<xpp::x::extension, Extensions...>(error);
+ }
+
+ template <typename Extension>
+ const Extension& extension() const {
+ return static_cast<const Extension&>(*this);
+ }
+
+ xcb_window_t root() const {
+ return m_root_window;
+ }
+
+ shared_ptr<xcb_generic_event_t> wait_for_event() const override {
+ try {
+ return core::wait_for_event();
+ } catch (const shared_ptr<xcb_generic_error_t>& error) {
+ check<xpp::x::extension, Extensions...>(error);
+ }
+ throw; // re-throw exception
+ }
+
+ shared_ptr<xcb_generic_event_t> wait_for_special_event(xcb_special_event_t* se) const override {
+ try {
+ return core::wait_for_special_event(se);
+ } catch (const shared_ptr<xcb_generic_error_t>& error) {
+ check<xpp::x::extension, Extensions...>(error);
+ }
+ throw; // re-throw exception
+ }
+
+ private:
+ xcb_window_t m_root_window;
+
+ template <typename Extension, typename Next, typename... Rest>
+ void check(const shared_ptr<xcb_generic_error_t>& error) const {
+ check<Extension>(error);
+ check<Next, Rest...>(error);
+ }
+
+ template <typename Extension>
+ void check(const shared_ptr<xcb_generic_error_t>& error) const {
+ using error_dispatcher = typename Extension::error_dispatcher;
+ auto& dispatcher = static_cast<const error_dispatcher&>(*this);
+ dispatcher(error);
+ }
+};
+} // namespace detail
+
+class connection : public detail::connection_base<connection&, XPP_EXTENSION_LIST> {
+ public:
+ using base_type = detail::connection_base<connection&, XPP_EXTENSION_LIST>;
+
+ using make_type = connection&;
+ static make_type make(xcb_connection_t* conn = nullptr, int default_screen = 0);
+
+ explicit connection(xcb_connection_t* c, int default_screen);
+ ~connection();
+
+ const connection& operator=(const connection& o) {
+ return o;
+ }
+
+ static void pack_values(uint32_t mask, const void* src, std::array<uint32_t, 32>& dest);
+
+ void reset_screen();
+ xcb_screen_t* screen();
+
+ string id(xcb_window_t w) const;
+
+ void ensure_event_mask(xcb_window_t win, unsigned int event);
+ void clear_event_mask(xcb_window_t win);
+
+ xcb_client_message_event_t make_client_message(xcb_atom_t type, xcb_window_t target) const;
+ void send_client_message(const xcb_client_message_event_t& message, xcb_window_t target,
+ unsigned int event_mask = 0xFFFFFF, bool propagate = false) const;
+
+ xcb_visualtype_t* visual_type(xcb_visual_class_t class_, int match_depth);
+ xcb_visualtype_t* visual_type_for_id(xcb_visualid_t visual_id);
+
+ bool root_pixmap(xcb_pixmap_t* pixmap, int* depth, xcb_rectangle_t* rect);
+
+ static string error_str(int error_code);
+
+ void dispatch_event(const shared_ptr<xcb_generic_event_t>& evt) const;
+
+ template <typename Sink>
+ void attach_sink(Sink&& sink, registry::priority prio = 0) {
+ m_registry.attach(prio, forward<Sink>(sink));
+ }
+
+ template <typename Sink>
+ void detach_sink(Sink&& sink, registry::priority prio = 0) {
+ m_registry.detach(prio, forward<Sink>(sink));
+ }
+
+ protected:
+ registry m_registry{*this};
+ xcb_screen_t* m_screen{nullptr};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XCURSOR
+#error "Not built with support for xcb-cursor..."
+#endif
+
+#include <xcb/xcb_cursor.h>
+
+#include "common.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace cursor_util {
+ static const std::map<string, vector<string>> cursors = {
+ {"pointer", {"pointing_hand", "pointer", "hand", "hand1", "hand2", "e29285e634086352946a0e7090d73106",
+ "9d800788f1b08800ae810202380a0822"}},
+ {"default", {"left_ptr", "arrow", "dnd-none", "op_left_arrow"}},
+ {"ns-resize", {"size_ver", "sb_v_double_arrow", "v_double_arrow", "n-resize", "s-resize", "col-resize",
+ "top_side", "bottom_side", "base_arrow_up", "base_arrow_down", "based_arrow_down",
+ "based_arrow_up", "00008160000006810000408080010102"}}};
+ bool valid(const string& name);
+ bool set_cursor(xcb_connection_t* c, xcb_screen_t* screen, xcb_window_t w, const string& name);
+} // namespace cursor_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb_ewmh.h>
+
+#include "common.hpp"
+#include "utils/memory.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+struct position;
+
+namespace ewmh_util {
+ class ewmh_connection : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ ewmh_connection();
+ ~ewmh_connection();
+
+ xcb_ewmh_connection_t* operator->();
+ operator xcb_ewmh_connection_t*();
+
+ private:
+ xcb_ewmh_connection_t c;
+ };
+
+ ewmh_connection& initialize();
+
+ bool supports(xcb_atom_t atom, int screen = 0);
+
+ string get_wm_name(xcb_window_t win);
+ string get_visible_name(xcb_window_t win);
+ string get_icon_name(xcb_window_t win);
+ string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply);
+
+ vector<position> get_desktop_viewports(int screen = 0);
+ vector<string> get_desktop_names(int screen = 0);
+ unsigned int get_current_desktop(int screen = 0);
+ unsigned int get_number_of_desktops(int screen = 0);
+ xcb_window_t get_active_window(int screen = 0);
+
+ void change_current_desktop(unsigned int desktop);
+ unsigned int get_desktop_from_window(xcb_window_t window);
+
+ void set_wm_window_type(xcb_window_t win, vector<xcb_atom_t> types);
+
+ void set_wm_state(xcb_window_t win, vector<xcb_atom_t> states);
+ vector<xcb_atom_t> get_wm_state(xcb_window_t win);
+
+ void set_wm_pid(xcb_window_t win);
+ void set_wm_pid(xcb_window_t win, unsigned int pid);
+
+ void set_wm_desktop(xcb_window_t win, unsigned int desktop = -1u);
+ void set_wm_window_opacity(xcb_window_t win, unsigned long int values);
+
+ vector<xcb_window_t> get_client_list(int screen = 0);
+
+ xcb_window_t get_supporting_wm_check(xcb_window_t win);
+ xcb_window_t get_ewmh_meta_window(xcb_window_t root);
+} // namespace ewmh_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+#if WITH_XRANDR
+#include "x11/extensions/randr.hpp"
+#endif
+#if WITH_XCOMPOSITE
+#include "x11/extensions/composite.hpp"
+#endif
+#if WITH_XKB
+#include "x11/extensions/xkb.hpp"
+#endif
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XCOMPOSITE
+#error "X Composite extension is disabled..."
+#endif
+
+#include <xcb/composite.h>
+#include <xpp/proto/composite.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+// fwd
+class connection;
+
+namespace composite_util {
+ void query_extension(connection& conn);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+namespace xpp {
+#if WITH_XRANDR
+ namespace randr {
+ class extension;
+ }
+#endif
+#if WITH_XCOMPOSITE
+ namespace composite {
+ class extension;
+ }
+#endif
+#if WITH_XKB
+ namespace xkb {
+ class extension;
+ }
+#endif
+}
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XRANDR
+#error "X RandR extension is disabled..."
+#endif
+
+#include <xcb/randr.h>
+
+#include <xpp/proto/randr.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class connection;
+struct position;
+
+namespace evt {
+ using randr_notify = xpp::randr::event::notify<connection&>;
+ using randr_screen_change_notify = xpp::randr::event::screen_change_notify<connection&>;
+} // namespace evt
+
+struct backlight_values {
+ unsigned int atom{0};
+ double min{0.0};
+ double max{0.0};
+ double val{0.0};
+};
+
+struct randr_output {
+ string name;
+ unsigned short int w{0U};
+ unsigned short int h{0U};
+ short int x{0};
+ short int y{0};
+ xcb_randr_output_t output;
+ backlight_values backlight;
+ bool primary{false};
+
+ bool match(const string& o, bool exact = true) const;
+ bool match(const position& p) const;
+
+ bool contains(const position& p) const;
+ bool contains(const randr_output& output) const;
+
+ bool equals(const randr_output& output) const;
+};
+
+using monitor_t = shared_ptr<randr_output>;
+
+namespace randr_util {
+ void query_extension(connection& conn);
+
+ bool check_monitor_support();
+
+ monitor_t make_monitor(xcb_randr_output_t randr, string name, unsigned short int w, unsigned short int h, short int x,
+ short int y, bool primary);
+ vector<monitor_t> get_monitors(connection& conn, bool connected_only = false, bool purge_clones = true);
+ monitor_t match_monitor(vector<monitor_t> monitors, const string& name, bool exact_match);
+
+ void get_backlight_range(connection& conn, const monitor_t& mon, backlight_values& dst);
+ void get_backlight_value(connection& conn, const monitor_t& mon, backlight_values& dst);
+} // namespace randr_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <map>
+
+#include "settings.hpp"
+
+#if not WITH_XKB
+#error "X xkb extension is disabled..."
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wkeyword-macro"
+#endif
+#define explicit mask_cxx_explicit_keyword
+#include <xcb/xkb.h>
+#undef explicit
+#include <xpp/proto/xkb.hpp>
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+using std::map;
+
+// fwd
+class connection;
+
+namespace evt {
+ using xkb_new_keyboard_notify = xpp::xkb::event::new_keyboard_notify<connection&>;
+ using xkb_map_notify = xpp::xkb::event::map_notify<connection&>;
+ using xkb_state_notify = xpp::xkb::event::state_notify<connection&>;
+ using xkb_controls_notify = xpp::xkb::event::controls_notify<connection&>;
+ using xkb_indicator_state_notify = xpp::xkb::event::indicator_state_notify<connection&>;
+ using xkb_indicator_map_notify = xpp::xkb::event::indicator_map_notify<connection&>;
+ using xkb_names_notify = xpp::xkb::event::names_notify<connection&>;
+ using xkb_compat_map_notify = xpp::xkb::event::compat_map_notify<connection&>;
+ using xkb_bell_notify = xpp::xkb::event::bell_notify<connection&>;
+ using xkb_action_message = xpp::xkb::event::action_message<connection&>;
+ using xkb_access_x_notify = xpp::xkb::event::access_x_notify<connection&>;
+ using xkb_extension_device_notify = xpp::xkb::event::extension_device_notify<connection&>;
+} // namespace evt
+
+class keyboard {
+ public:
+ struct indicator {
+ enum class type { NONE = 0U, CAPS_LOCK, NUM_LOCK, SCROLL_LOCK };
+ xcb_atom_t atom{};
+ unsigned char mask{0};
+ string name{};
+ bool enabled{false};
+ };
+
+ struct layout {
+ string group_name{};
+ vector<string> symbols{};
+ };
+
+ explicit keyboard(vector<layout>&& layouts_, map<indicator::type, indicator>&& indicators_, unsigned char group)
+ : layouts(forward<decltype(layouts)>(layouts_))
+ , indicators(forward<decltype(indicators)>(indicators_))
+ , current_group(group) {}
+
+ const indicator& get(const indicator::type& i) const;
+ void set(unsigned int state);
+ bool on(const indicator::type&) const;
+ void current(unsigned char group);
+ unsigned char current() const;
+ const string group_name(size_t index = 0) const;
+ const string layout_name(size_t index = 0) const;
+ const string variant_name(size_t index = 0) const;
+ const string indicator_name(const indicator::type&) const;
+ size_t size() const;
+
+ private:
+ vector<layout> layouts;
+ map<indicator::type, indicator> indicators;
+ unsigned char current_group{0};
+};
+
+namespace xkb_util {
+ static constexpr const char* LAYOUT_SYMBOL_BLACKLIST{";group;inet;pc;"};
+
+ void query_extension(connection& conn);
+
+ void switch_layout(connection& conn, xcb_xkb_device_spec_t device, unsigned char index);
+ unsigned char get_current_group(connection& conn, xcb_xkb_device_spec_t device);
+ vector<keyboard::layout> get_layouts(connection& conn, xcb_xkb_device_spec_t device);
+ map<keyboard::indicator::type, keyboard::indicator> get_indicators(connection& conn, xcb_xkb_device_spec_t device);
+ string parse_layout_symbol(string&& name);
+} // namespace xkb_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb_icccm.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace icccm_util {
+ string get_wm_name(xcb_connection_t* c, xcb_window_t w);
+ pair<string, string> get_wm_class(xcb_connection_t* c, xcb_window_t w);
+ string get_reply_string(xcb_icccm_get_text_property_reply_t* reply);
+
+ void set_wm_name(xcb_connection_t* c, xcb_window_t w, const char* wmname, size_t l, const char* wmclass, size_t l2);
+ void set_wm_protocols(xcb_connection_t* c, xcb_window_t w, vector<xcb_atom_t> flags);
+ bool get_wm_urgency(xcb_connection_t* c, xcb_window_t w);
+
+ void set_wm_size_hints(xcb_connection_t* c, xcb_window_t w, int x, int y, int width, int height);
+} // namespace icccm_util
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/xembed.hpp"
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define TRAY_WM_NAME "Polybar tray window"
+#define TRAY_WM_CLASS "tray\0Polybar"
+
+POLYBAR_NS
+
+// fwd declarations
+class connection;
+class background_manager;
+class bg_slice;
+
+namespace legacy_tray {
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+using std::atomic;
+
+enum class tray_postition { NONE = 0, LEFT, CENTER, RIGHT, MODULE };
+
+struct tray_settings {
+ tray_postition tray_position{tray_postition::NONE};
+ bool running{false};
+ int rel_x{0};
+ int rel_y{0};
+ int orig_x{0};
+ int orig_y{0};
+ int configured_x{0};
+ int configured_y{0};
+ unsigned int configured_w{0U};
+ unsigned int configured_h{0U};
+ unsigned int configured_slots{0U};
+ unsigned int width{0U};
+ unsigned int width_max{0U};
+ unsigned int height{0U};
+ unsigned int height_fill{0U};
+ unsigned int spacing{0U};
+ unsigned int sibling{0U};
+ rgba background{};
+ rgba foreground{};
+ bool transparent{false};
+ bool detached{false};
+};
+
+class tray_client {
+ public:
+ explicit tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h);
+ tray_client(const tray_client& c) = delete;
+ tray_client& operator=(tray_client& c) = delete;
+
+ ~tray_client();
+
+ unsigned int width() const;
+ unsigned int height() const;
+ void clear_window() const;
+
+ bool match(const xcb_window_t& win) const;
+ bool mapped() const;
+ void mapped(bool state);
+
+ xcb_window_t window() const;
+
+ void query_xembed();
+ bool is_xembed_supported() const;
+ const xembed::info& get_xembed() const;
+
+ void ensure_state() const;
+ void reconfigure(int x, int y) const;
+ void configure_notify(int x, int y) const;
+
+ protected:
+ connection& m_connection;
+ xcb_window_t m_window{0};
+
+ /**
+ * Whether the client window supports XEMBED.
+ *
+ * A tray client can still work when it doesn't support XEMBED.
+ */
+ bool m_xembed_supported{false};
+
+ /**
+ * _XEMBED_INFO of the client window
+ */
+ xembed::info m_xembed;
+
+ bool m_mapped{false};
+
+ unsigned int m_width;
+ unsigned int m_height;
+};
+
+class tray_manager
+ : public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message, evt::configure_request,
+ evt::resize_request, evt::selection_clear, evt::property_notify, evt::reparent_notify, evt::destroy_notify,
+ evt::map_notify, evt::unmap_notify>,
+ public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window,
+ signals::ui::update_background, signals::ui_tray::tray_pos_change, signals::ui_tray::tray_visibility>,
+ public non_copyable_mixin,
+ public non_movable_mixin {
+ public:
+ using make_type = unique_ptr<tray_manager>;
+ static make_type make(const bar_settings& settings);
+
+ explicit tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back,
+ const bar_settings& settings);
+
+ ~tray_manager();
+
+ const tray_settings settings() const;
+
+ void setup(const config&, const string& tray_module_name);
+ void activate();
+ void activate_delayed(chrono::duration<double, std::milli> delay = 1s);
+ void deactivate(bool clear_selection = true);
+ void reconfigure();
+
+ protected:
+ void reconfigure_window();
+ void reconfigure_clients();
+ void reconfigure_bg(bool realloc = false);
+ void refresh_window();
+ void redraw_window(bool realloc_bg = false);
+
+ void query_atom();
+ void create_window();
+ void create_bg(bool realloc = false);
+ void restack_window();
+ void set_wm_hints();
+ void set_tray_colors();
+
+ void acquire_selection();
+ void notify_clients();
+ void notify_clients_delayed();
+
+ void track_selection_owner(xcb_window_t owner);
+ void process_docking_request(xcb_window_t win);
+
+ int calculate_x(unsigned width) const;
+ int calculate_y(bool abspos = true) const;
+
+ unsigned short int calculate_w() const;
+ unsigned short int calculate_h() const;
+
+ int calculate_client_x(const xcb_window_t& win);
+ int calculate_client_y();
+
+ bool is_embedded(const xcb_window_t& win) const;
+ shared_ptr<tray_client> find_client(const xcb_window_t& win) const;
+ void remove_client(shared_ptr<tray_client>& client, bool reconfigure = true);
+ void remove_client(xcb_window_t win, bool reconfigure = true);
+ unsigned int mapped_clients() const;
+ bool change_visibility(bool visible);
+
+ void handle(const evt::expose& evt) override;
+ void handle(const evt::visibility_notify& evt) override;
+ void handle(const evt::client_message& evt) override;
+ void handle(const evt::configure_request& evt) override;
+ void handle(const evt::resize_request& evt) override;
+ void handle(const evt::selection_clear& evt) override;
+ void handle(const evt::property_notify& evt) override;
+ void handle(const evt::reparent_notify& evt) override;
+ void handle(const evt::destroy_notify& evt) override;
+ void handle(const evt::map_notify& evt) override;
+ void handle(const evt::unmap_notify& evt) override;
+
+ bool on(const signals::ui::visibility_change& evt) override;
+ bool on(const signals::ui::dim_window& evt) override;
+ bool on(const signals::ui::update_background& evt) override;
+ bool on(const signals::ui_tray::tray_pos_change& evt) override;
+ bool on(const signals::ui_tray::tray_visibility& evt) override;
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+ background_manager& m_background_manager;
+ std::shared_ptr<bg_slice> m_bg_slice;
+ vector<shared_ptr<tray_client>> m_clients;
+
+ tray_settings m_opts{};
+
+ xcb_gcontext_t m_gc{0};
+ xcb_pixmap_t m_pixmap{0};
+ unique_ptr<cairo::surface> m_surface;
+ unique_ptr<cairo::context> m_context;
+
+ unsigned int m_prevwidth{0U};
+ unsigned int m_prevheight{0U};
+
+ xcb_atom_t m_atom{0};
+ xcb_window_t m_tray{0};
+ xcb_window_t m_othermanager{0};
+
+ atomic<bool> m_activated{false};
+ atomic<bool> m_mapped{false};
+ atomic<bool> m_hidden{false};
+ atomic<bool> m_acquired_selection{false};
+
+ thread m_delaythread;
+
+ mutex m_mtx{};
+
+ bool m_firstactivation{true};
+
+ const bar_settings& m_bar_opts;
+};
+
+} // namespace legacy_tray
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "x11/extensions/fwd.hpp"
+
+// fwd
+namespace xpp {
+ namespace event {
+ template <typename Connection, typename... Extensions>
+ class registry;
+ }
+}
+
+POLYBAR_NS
+
+class connection;
+
+class registry : public xpp::event::registry<connection&, XPP_EXTENSION_LIST> {
+ public:
+ using priority = unsigned int;
+
+ explicit registry(connection& conn);
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "common.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/xembed.hpp"
+
+/*
+ * Manages the lifecycle of a tray client according to the XEMBED protocol
+ *
+ * Ref: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ */
+
+POLYBAR_NS
+
+// fwd declarations
+class connection;
+
+namespace tray {
+
+class client : public non_copyable_mixin, public non_movable_mixin {
+ public:
+ explicit client(
+ const logger& log, connection& conn, xcb_window_t parent, xcb_window_t win, size s, rgba desired_background);
+ ~client();
+
+ string name() const;
+
+ unsigned int width() const;
+ unsigned int height() const;
+ void clear_window() const;
+
+ void update_client_attributes() const;
+ void reparent() const;
+
+ bool match(const xcb_window_t& win) const;
+ bool mapped() const;
+ void mapped(bool state);
+
+ void hidden(bool state);
+
+ bool should_be_mapped() const;
+
+ xcb_window_t embedder() const;
+ xcb_window_t client_window() const;
+
+ void query_xembed();
+ bool is_xembed_supported() const;
+ const xembed::info& get_xembed() const;
+
+ void notify_xembed() const;
+
+ void add_to_save_set() const;
+
+ void ensure_state() const;
+ void set_position(int x, int y);
+ void configure_notify() const;
+
+ void update_bg() const;
+
+ protected:
+ void observe_background();
+
+ const logger& m_log;
+
+ connection& m_connection;
+
+ /**
+ * Name of the client window for debugging.
+ */
+ string m_name{};
+
+ /**
+ * Embedder window.
+ *
+ * The docking client window is reparented to this window.
+ * This window is itself a child of the main tray window.
+ *
+ * This class owns this window and is responsible for creating/destroying it.
+ */
+ xcb_window_t m_wrapper{XCB_NONE};
+
+ /**
+ * Client window.
+ *
+ * The window itself is owned by the application providing it.
+ * This class is responsible for correctly mapping and reparenting it in accordance with the XEMBED protocol.
+ */
+ xcb_window_t m_client{XCB_NONE};
+
+ /**
+ * Whether the client window supports XEMBED.
+ *
+ * A tray client can still work when it doesn't support XEMBED.
+ */
+ bool m_xembed_supported{false};
+
+ /**
+ * _XEMBED_INFO of the client window
+ *
+ * Only valid if m_xembed_supported == true
+ */
+ xembed::info m_xembed{};
+
+ /**
+ * Background pixmap of wrapper window
+ */
+ xcb_pixmap_t m_pixmap{XCB_NONE};
+
+ /**
+ * Whether the wrapper window is currently mapped.
+ */
+ bool m_mapped{false};
+
+ /**
+ * Whether the
+ */
+ bool m_hidden{false};
+
+ size m_size;
+ position m_pos{0, 0};
+
+ const rgba m_desired_background;
+ const bool m_transparent{m_desired_background.is_transparent()};
+ background_manager& m_background_manager;
+ shared_ptr<bg_slice> m_bg_slice{nullptr};
+ unique_ptr<cairo::context> m_context;
+ unique_ptr<cairo::xcb_surface> m_surface;
+};
+
+} // namespace tray
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/concurrency.hpp"
+#include "utils/mixins.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/tray_client.hpp"
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+POLYBAR_NS
+
+// fwd declarations
+class connection;
+class bg_slice;
+
+namespace tray {
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+using std::atomic;
+
+struct tray_settings {
+ /**
+ * Dimensions for client windows.
+ */
+ size client_size{0, 0};
+
+ /**
+ * Number of pixels added between tray icons
+ */
+ unsigned spacing{0U};
+
+ /**
+ * Number of pixels added before and after each tray icon
+ */
+ unsigned padding{0U};
+
+ /**
+ * Background color used in the client wrapper window
+ *
+ * If transparent, pseudo-transparency is used for the icon.
+ */
+ rgba background{};
+
+ /**
+ * Used for `_NET_SYSTEM_TRAY_COLORS` atom
+ *
+ * Is only a hint to the tray applications.
+ */
+ rgba foreground{};
+
+ /**
+ * Window ID of tray selection owner.
+ *
+ * Matches the bar window
+ */
+ xcb_window_t selection_owner{XCB_NONE};
+};
+
+using on_update = std::function<void(void)>;
+
+class manager : public xpp::event::sink<evt::expose, evt::client_message, evt::configure_request, evt::resize_request,
+ evt::selection_clear, evt::property_notify, evt::reparent_notify, evt::destroy_notify,
+ evt::map_notify, evt::unmap_notify>,
+ public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::update_background,
+ signals::ui_tray::tray_pos_change, signals::ui_tray::tray_visibility>,
+ non_copyable_mixin,
+ non_movable_mixin {
+ public:
+ explicit manager(connection& conn, signal_emitter& emitter, const logger& logger, const bar_settings& bar_opts,
+ on_update on_update);
+
+ ~manager() override;
+
+ unsigned get_width() const;
+
+ void setup(const config& conf, const string& module_section_name);
+ void activate();
+ void wait_for_selection(xcb_window_t other);
+ void deactivate();
+ void reconfigure();
+
+ bool is_active() const;
+ bool is_inactive() const;
+ bool is_waiting() const;
+
+ bool is_visible() const;
+
+ protected:
+ void recalculate_width();
+ void reconfigure_clients();
+ void redraw_clients();
+
+ void query_atom();
+ void set_tray_colors();
+ void set_tray_orientation();
+ void set_tray_visual();
+
+ bool acquire_selection(xcb_window_t& other_owner);
+ void notify_clients();
+
+ void track_selection_owner(xcb_window_t owner);
+ void process_docking_request(xcb_window_t win);
+
+ int calculate_x() const;
+
+ unsigned calculate_w() const;
+
+ int calculate_client_y();
+
+ bool is_embedded(const xcb_window_t& win);
+ client* find_client(const xcb_window_t& win);
+ void remove_client(const client& client);
+ void remove_client(xcb_window_t win);
+ void clean_clients();
+ bool change_visibility(bool visible);
+
+ void handle(const evt::expose& evt) override;
+ void handle(const evt::client_message& evt) override;
+ void handle(const evt::configure_request& evt) override;
+ void handle(const evt::resize_request& evt) override;
+ void handle(const evt::selection_clear& evt) override;
+ void handle(const evt::property_notify& evt) override;
+ void handle(const evt::reparent_notify& evt) override;
+ void handle(const evt::destroy_notify& evt) override;
+ void handle(const evt::map_notify& evt) override;
+ void handle(const evt::unmap_notify& evt) override;
+
+ bool on(const signals::ui::update_background& evt) override;
+ bool on(const signals::ui_tray::tray_pos_change& evt) override;
+ bool on(const signals::ui_tray::tray_visibility& evt) override;
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+ vector<unique_ptr<client>> m_clients;
+
+ tray_settings m_opts{};
+ const bar_settings& m_bar_opts;
+
+ const on_update m_on_update;
+
+ enum class state {
+ /**
+ * The tray manager is completely deactivated and doesn't own any resources.
+ */
+ INACTIVE = 0,
+ /**
+ * There is currently another application owning the systray selection and the tray manager waits until the
+ * selection becomes available again.
+ *
+ * The signal receiver is detached in m_othermanager is set
+ */
+ WAITING,
+ /**
+ * The tray manager owns the systray selection.
+ *
+ * The signal receiver is attached
+ */
+ ACTIVE,
+ };
+ atomic<state> m_state{state::INACTIVE};
+
+ /**
+ * Systray selection atom _NET_SYSTEM_TRAY_Sn
+ */
+ xcb_atom_t m_atom{XCB_NONE};
+
+ /**
+ * Owner of systray selection (if we don't own it)
+ */
+ xcb_window_t m_othermanager{XCB_NONE};
+
+ /**
+ * Specifies the top-left corner of the tray area relative to inner area of the bar.
+ */
+ position m_pos{0, 0};
+
+ /**
+ * Current width of the tray.
+ *
+ * Caches the value calculated from all mapped tray clients.
+ */
+ unsigned m_tray_width{0};
+
+ /**
+ * Whether the tray is visible
+ */
+ bool m_hidden{false};
+};
+
+} // namespace tray
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xpp/event.hpp>
+
+#include "common.hpp"
+
+namespace xpp {
+ template <typename Connection, template <typename, typename> class...>
+ class gcontext;
+ template <typename Connection, template <typename, typename> class...>
+ class pixmap;
+ template <typename Connection, template <typename, typename> class...>
+ class drawable;
+ template <typename Connection, template <typename, typename> class...>
+ class colormap;
+ template <typename Connection, template <typename, typename> class...>
+ class atom;
+ template <typename Connection, template <typename, typename> class...>
+ class font;
+ template <typename Connection, template <typename, typename> class...>
+ class cursor;
+ namespace event {
+ template <class Event, class... Events>
+ class sink;
+ }
+}
+
+POLYBAR_NS
+
+class connection;
+class registry;
+
+using gcontext = xpp::gcontext<connection&>;
+using pixmap = xpp::pixmap<connection&>;
+using drawable = xpp::drawable<connection&>;
+using colormap = xpp::colormap<connection&>;
+using atom = xpp::atom<connection&>;
+using font = xpp::font<connection&>;
+using cursor = xpp::cursor<connection&>;
+
+namespace evt {
+ // window focus events
+ using focus_in = xpp::x::event::focus_in<connection&>;
+ using focus_out = xpp::x::event::focus_out<connection&>;
+ // cursor events
+ using enter_notify = xpp::x::event::enter_notify<connection&>;
+ using leave_notify = xpp::x::event::leave_notify<connection&>;
+ using motion_notify = xpp::x::event::motion_notify<connection&>;
+ // keyboard events
+ using button_press = xpp::x::event::button_press<connection&>;
+ using button_release = xpp::x::event::button_release<connection&>;
+ using key_press = xpp::x::event::key_press<connection&>;
+ using key_release = xpp::x::event::key_release<connection&>;
+ using keymap_notify = xpp::x::event::keymap_notify<connection&>;
+ // render events
+ using circulate_notify = xpp::x::event::circulate_notify<connection&>;
+ using circulate_request = xpp::x::event::circulate_request<connection&>;
+ using colormap_notify = xpp::x::event::colormap_notify<connection&>;
+ using configure_notify = xpp::x::event::configure_notify<connection&>;
+ using configure_request = xpp::x::event::configure_request<connection&>;
+ using create_notify = xpp::x::event::create_notify<connection&>;
+ using destroy_notify = xpp::x::event::destroy_notify<connection&>;
+ using expose = xpp::x::event::expose<connection&>;
+ using graphics_exposure = xpp::x::event::graphics_exposure<connection&>;
+ using gravity_notify = xpp::x::event::gravity_notify<connection&>;
+ using map_notify = xpp::x::event::map_notify<connection&>;
+ using map_request = xpp::x::event::map_request<connection&>;
+ using mapping_notify = xpp::x::event::mapping_notify<connection&>;
+ using no_exposure = xpp::x::event::no_exposure<connection&>;
+ using reparent_notify = xpp::x::event::reparent_notify<connection&>;
+ using resize_request = xpp::x::event::resize_request<connection&>;
+ using unmap_notify = xpp::x::event::unmap_notify<connection&>;
+ using visibility_notify = xpp::x::event::visibility_notify<connection&>;
+ // data events
+ using client_message = xpp::x::event::client_message<connection&>;
+ using ge_generic = xpp::x::event::ge_generic<connection&>;
+ using property_notify = xpp::x::event::property_notify<connection&>;
+ // selection events
+ using selection_clear = xpp::x::event::selection_clear<connection&>;
+ using selection_notify = xpp::x::event::selection_notify<connection&>;
+ using selection_request = xpp::x::event::selection_request<connection&>;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include <xpp/window.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+class window : public xpp::window<connection&> {
+ public:
+ window(const window&) = default;
+ using xpp::window<class connection&>::window;
+
+ window reconfigure_geom(unsigned short int w, unsigned short int h, short int x = 0, short int y = 0);
+ window reconfigure_pos(short int x, short int y);
+ window reconfigure_struts(uint32_t w, uint32_t strut, uint32_t x, bool bottom = false);
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+struct cw_size {
+ explicit cw_size(unsigned short int w, unsigned short int h) : w(w), h(h) {}
+ explicit cw_size(struct size size) : w(size.w), h(size.h) {}
+ unsigned short int w{1};
+ unsigned short int h{1};
+};
+struct cw_pos {
+ explicit cw_pos(short int x, short int y) : x(x), y(y) {}
+ explicit cw_pos(struct position pos) : x(pos.x), y(pos.y) {}
+ short int x{0};
+ short int y{0};
+};
+struct cw_border {
+ explicit cw_border(unsigned short int border_width) : border_width(border_width) {}
+ unsigned short int border_width{0};
+};
+struct cw_class {
+ explicit cw_class(unsigned short int class_) : class_(class_) {}
+ unsigned short int class_{XCB_COPY_FROM_PARENT};
+};
+struct cw_parent {
+ explicit cw_parent(xcb_window_t parent) : parent(parent) {}
+ xcb_window_t parent{XCB_NONE};
+};
+struct cw_depth {
+ explicit cw_depth(unsigned char depth) : depth(depth) {}
+ unsigned char depth{XCB_COPY_FROM_PARENT};
+};
+struct cw_visual {
+ explicit cw_visual(xcb_visualid_t visualid) : visualid(visualid) {}
+ xcb_visualid_t visualid{XCB_COPY_FROM_PARENT};
+};
+struct cw_mask {
+ explicit cw_mask(unsigned int mask) : mask(mask) {}
+ const unsigned int mask{0};
+};
+struct cw_params {
+ explicit cw_params(const xcb_params_cw_t* params) : params(params) {}
+ const xcb_params_cw_t* params{nullptr};
+};
+struct cw_params_back_pixel {
+ explicit cw_params_back_pixel(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_back_pixmap {
+ explicit cw_params_back_pixmap(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_backing_pixel {
+ explicit cw_params_backing_pixel(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_backing_planes {
+ explicit cw_params_backing_planes(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_backing_store {
+ explicit cw_params_backing_store(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_bit_gravity {
+ explicit cw_params_bit_gravity(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_border_pixel {
+ explicit cw_params_border_pixel(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_border_pixmap {
+ explicit cw_params_border_pixmap(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_colormap {
+ explicit cw_params_colormap(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_cursor {
+ explicit cw_params_cursor(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_dont_propagate {
+ explicit cw_params_dont_propagate(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_event_mask {
+ explicit cw_params_event_mask(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_override_redirect {
+ explicit cw_params_override_redirect(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_save_under {
+ explicit cw_params_save_under(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_params_win_gravity {
+ explicit cw_params_win_gravity(unsigned int value) : value(value) {}
+ unsigned int value{0};
+};
+struct cw_flush {
+ explicit cw_flush(bool checked = false) : checked(checked) {}
+ bool checked{false};
+};
+
+/**
+ * Create X window
+ *
+ * Example usage:
+ * @code cpp
+ * auto win = winspec(m_connection)
+ * << cw_size(100, 200)
+ * << cw_pos(10, -20)
+ * << cw_border(9)
+ * << cw_class(XCB_WINDOW_CLASS_INPUT_ONLY)
+ * << cw_parent(0x000110a);
+ * << cw_flush(false);
+ * @endcode
+ */
+class winspec {
+ public:
+ explicit winspec(connection& conn);
+ explicit winspec(connection& conn, const xcb_window_t& window);
+
+ explicit operator xcb_window_t() const;
+ explicit operator xcb_rectangle_t() const;
+
+ xcb_window_t operator<<(const cw_flush& f);
+
+ winspec& operator<<(const cw_size& size);
+ winspec& operator<<(const cw_pos& p);
+ winspec& operator<<(const cw_border& b);
+ winspec& operator<<(const cw_class& c);
+ winspec& operator<<(const cw_parent& p);
+ winspec& operator<<(const cw_depth& d);
+ winspec& operator<<(const cw_visual& v);
+ winspec& operator<<(const cw_params_back_pixel& p);
+ winspec& operator<<(const cw_params_back_pixmap& p);
+ winspec& operator<<(const cw_params_backing_pixel& p);
+ winspec& operator<<(const cw_params_backing_planes& p);
+ winspec& operator<<(const cw_params_backing_store& p);
+ winspec& operator<<(const cw_params_bit_gravity& p);
+ winspec& operator<<(const cw_params_border_pixel& p);
+ winspec& operator<<(const cw_params_border_pixmap& p);
+ winspec& operator<<(const cw_params_colormap& p);
+ winspec& operator<<(const cw_params_cursor& p);
+ winspec& operator<<(const cw_params_dont_propagate& p);
+ winspec& operator<<(const cw_params_event_mask& p);
+ winspec& operator<<(const cw_params_override_redirect& p);
+ winspec& operator<<(const cw_params_save_under& p);
+ winspec& operator<<(const cw_params_win_gravity& p);
+
+ protected:
+ connection& m_connection;
+
+ xcb_window_t m_window{XCB_NONE};
+ unsigned int m_parent{XCB_NONE};
+ unsigned char m_depth{XCB_COPY_FROM_PARENT};
+ unsigned short int m_class{XCB_COPY_FROM_PARENT};
+ xcb_visualid_t m_visual{XCB_COPY_FROM_PARENT};
+ short int m_x{0};
+ short int m_y{0};
+ unsigned short int m_width{1U};
+ unsigned short int m_height{1U};
+ unsigned short int m_border{0};
+ uint32_t m_mask{0};
+ xcb_params_cw_t m_params{};
+};
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "common.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+static constexpr uint32_t XEMBED_VERSION = 0;
+static constexpr uint32_t XEMBED_MAPPED = (1 << 0);
+
+static constexpr uint32_t XEMBED_EMBEDDED_NOTIFY = 0;
+static constexpr uint32_t XEMBED_WINDOW_ACTIVATE = 1;
+static constexpr uint32_t XEMBED_WINDOW_DEACTIVATE = 2;
+static constexpr uint32_t XEMBED_REQUEST_FOCUS = 3;
+static constexpr uint32_t XEMBED_FOCUS_IN = 3;
+static constexpr uint32_t XEMBED_FOCUS_OUT = 4;
+static constexpr uint32_t XEMBED_FOCUS_NEXT = 5;
+static constexpr uint32_t XEMBED_FOCUS_PREV = 6;
+
+static constexpr uint32_t XEMBED_FOCUS_CURRENT = 0;
+static constexpr uint32_t XEMBED_FOCUS_FIRST = 1;
+static constexpr uint32_t XEMBED_FOCUS_LAST = 2;
+
+/**
+ * Max XEMBED version supported.
+ */
+static constexpr uint32_t XEMBED_MAX_VERSION = 0;
+
+/**
+ * Implementation of parts of the XEMBED spec (as much as needed to get the tray working).
+ *
+ * Ref: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
+ */
+namespace xembed {
+
+class info {
+ public:
+ void set(const uint32_t* data);
+
+ uint32_t get_version() const;
+ uint32_t get_flags() const;
+
+ bool is_mapped() const;
+
+ string to_string() const;
+
+ protected:
+ uint32_t version;
+ uint32_t flags;
+};
+
+bool query(connection& conn, xcb_window_t win, info& data);
+void send_message(connection& conn, xcb_window_t target, uint32_t message, uint32_t d1, uint32_t d2, uint32_t d3);
+void send_focus_event(connection& conn, xcb_window_t target);
+void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, uint32_t version);
+void notify_activated(connection& conn, xcb_window_t win);
+void notify_deactivated(connection& conn, xcb_window_t win);
+void notify_focused(connection& conn, xcb_window_t win, uint32_t focus_type);
+void notify_unfocused(connection& conn, xcb_window_t win);
+void unembed(connection& conn, xcb_window_t win, xcb_window_t root);
+} // namespace xembed
+
+POLYBAR_NS_END
--- /dev/null
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XRM
+#error "Not built with support for xcb-xrm..."
+#endif
+
+#include <xcb/xcb_xrm.h>
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(xresource_error);
+
+class xresource_manager {
+ public:
+ explicit xresource_manager(xcb_connection_t* conn) : m_xrm(xcb_xrm_database_from_default(conn)) {
+ if (m_xrm == nullptr) {
+ throw application_error("xcb_xrm_database_from_default()");
+ }
+ }
+
+ ~xresource_manager() {
+ xcb_xrm_database_free(m_xrm);
+ }
+
+ operator bool() const {
+ return m_xrm != nullptr;
+ }
+
+ template <typename T>
+ T require(const char* name) const {
+ char* result{nullptr};
+ if (xcb_xrm_resource_get_string(m_xrm, string_util::replace(name, "*", ".").c_str(), nullptr, &result) == -1) {
+ throw xresource_error(sstream() << "X resource \"" << name << "\" not found");
+ } else if (result == nullptr) {
+ throw xresource_error(sstream() << "X resource \"" << name << "\" not found");
+ }
+ return convert<T>(string(result));
+ }
+
+ template <typename T>
+ T get(const char* name, const T& fallback) const {
+ try {
+ return convert<T>(require<T>(name));
+ } catch (const xresource_error&) {
+ return fallback;
+ }
+ }
+
+ protected:
+ template <typename T>
+ T convert(string&& value) const;
+
+ private:
+ xcb_xrm_database_t* m_xrm;
+};
+
+POLYBAR_NS_END
--- /dev/null
+#
+# Configure libs
+#
+
+# Library: xpp {{{
+
+set(XCB_PROTOS xproto)
+
+list(APPEND XCB_PROTOS randr)
+list(APPEND XCB_PROTOS composite)
+if(WITH_XKB)
+ list(APPEND XCB_PROTOS xkb)
+endif()
+
+add_subdirectory(xpp)
+if(NOT TARGET xpp)
+ message(FATAL_ERROR "Target xpp not generated")
+else()
+ get_target_property(_xpp_includes xpp INCLUDE_DIRECTORIES)
+ set_target_properties(xpp PROPERTIES INCLUDE_DIRECTORIES "")
+ target_include_directories(xpp SYSTEM PUBLIC ${_xpp_includes})
+endif()
+
+# }}}
+# Library: i3ipcpp {{{
+
+if(ENABLE_I3)
+ add_subdirectory(i3ipcpp)
+ if(NOT TARGET i3ipc++)
+ message(FATAL_ERROR "Target i3ipcpp not generated")
+ else()
+ get_target_property(_i3ipcpp_includes i3ipc++ INCLUDE_DIRECTORIES)
+ set_target_properties(i3ipc++ PROPERTIES INCLUDE_DIRECTORIES "")
+ target_include_directories(i3ipc++ SYSTEM PUBLIC ${_i3ipcpp_includes})
+ endif()
+endif()
+
+# }}}
--- /dev/null
+build
+doc
+*.sublime-workspace
+*.log
--- /dev/null
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
\ No newline at end of file
--- /dev/null
+# AutoStringStream 
+
+Simple header-only wrapper on `std::stringstream` with automatic casting to `std::string`
+
+## Usage
+
+```c++
+#include <auss.hpp>
+```
+```c++
+auss_t() << "Hello, " << user_name
+```
+```c++
+throw std::runtime_error(auss_t() << "Something gone wrong, See " << log_path)
+```
+
+### Own namespace
+If you wouldn't pollute global namespace just define `AUSS_USE_OWN_NAMESPACE`. Either before `#include` or in compiler flags (`-DAUSS_USE_OWN_NAMESPACE` for GCC).
+
+Also you can specifiy the name of namespace with `AUSS_OWN_NAMESPACE_NAME`:
+```
+-DAUSS_OWN_NAMESPACE_NAME="theauss"
+```
+
+## License
+
+Licensed under Unlicense. See `LICENSE` file for more info.
--- /dev/null
+#pragma once
+
+#define AUSS_HPP
+
+#include <sstream>
+
+#ifdef AUSS_USE_OWN_NAMESPACE
+ #ifndef AUSS_OWN_NAMESPACE_NAME
+ #define AUSS_OWN_NAMESPACE_NAME auss
+ #endif
+namespace AUSS_OWN_NAMESPACE_NAME {
+#endif
+
+class AutoStringStream {
+public:
+ AutoStringStream() : m_stream() {}
+ virtual ~AutoStringStream() {}
+
+ template<typename T>
+ AutoStringStream& operator<<(const T& arg) {
+ m_stream << arg;
+ return *this;
+ }
+
+ AutoStringStream& operator<<(const bool arg) {
+ m_stream << (arg ? "true" : "false");
+ return *this;
+ }
+
+ operator std::string() const {
+ return m_stream.str();
+ }
+
+ const std::string to_string() const {
+ return m_stream.str();
+ }
+private:
+ std::stringstream m_stream;
+};
+
+#ifndef AUSS_CUSTOM_TYPEDEF
+typedef AutoStringStream auss_t;
+#endif
+
+#ifdef AUSS_USE_OWN_NAMESPACE
+}
+#endif
--- /dev/null
+0.4
+ + Added support of GET_BARCONFIG and barconfig_update event and examples for them
+ + Added getters form main and event sockets
+ + Added i3ipc::get_version()
+
+ ~ Calling i3ipc::connection::prepare_to_event_handling() is no more necessary
+ ~ Using i3ipc::errno_error when calling c-functions instead of std::runtime_error
+ ~ Logging-subsystem became public
+ ~ Refactoring of CMakeLists.txt
+
+ - i3ipc::connection::get_file_descriptor() removed
+
+0.3
+ + Added support of BINDING event
+
+ ~ Removed submodules
+
+ * Bug fixies
+
+0.2.1
+ + Added example of event handling
+ * Fixed SIGSEGV in parse_*_from_json functions
+
+0.2.0
+ + Implemented GET_TREE (i3ipc::connection::get_tree())
+
+ ~ Shipping all available payload with workspace and window events (issue #2)
+ ~ i3ipc::I3Connection renamed to i3ipc::connection
+
+ ~ Internal refreactoring
+
+ * Fixing failed build: Parts of a struct were initialised in wrong order, C99-style designated initialisers did not prevent this from causing an error [mox]
+ * Minor documentation and code fixies
--- /dev/null
+# Project setup
+
+cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
+project(i3ipc++ CXX)
+
+option(WITH_LOGGING "Build with log support" OFF)
+option(WITH_TESTS "Build unit tests executables" OFF)
+option(BUILD_EXAMPLES "Build example executables" OFF)
+
+add_library(${PROJECT_NAME} STATIC
+ ${PROJECT_SOURCE_DIR}/3rd/auss/include/auss.hpp
+ ${PROJECT_SOURCE_DIR}/include/i3ipc++/ipc.hpp
+ ${PROJECT_SOURCE_DIR}/include/i3ipc++/ipc-util.hpp
+ ${PROJECT_SOURCE_DIR}/src/ipc.cpp
+ ${PROJECT_SOURCE_DIR}/src/ipc-util.cpp)
+
+target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/3rd/auss/include)
+target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
+
+target_compile_options(${PROJECT_NAME} PRIVATE -std=c++11 -Wall -Wextra -Wno-unused-parameter -Wno-deprecated-declarations)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Debug>:-g3 -DDEBUG>)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Release>:-O2>)
+
+# External library: jsoncpp-1.7.7 {{{
+
+find_package(PkgConfig)
+pkg_check_modules(JSONCPP REQUIRED jsoncpp)
+
+target_link_libraries(${PROJECT_NAME} PUBLIC ${JSONCPP_LIBRARIES})
+target_include_directories(${PROJECT_NAME} PUBLIC ${JSONCPP_INCLUDEDIR})
+
+# }}}
+# Export lists to the parent scope if there are any {{{
+
+get_directory_property(HAS_PARENT PARENT_DIRECTORY)
+if(HAS_PARENT)
+ set(I3IPCPP_LIBRARIES ${PROJECT_NAME} PARENT_SCOPE)
+ set(I3IPCPP_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include ${JSONCPP_INCLUDEDIR} PARENT_SCOPE)
+endif()
+
+# }}}
+# Build examples if the option was given {{{
+
+if(BUILD_EXAMPLES)
+ add_subdirectory("${PROJECT_SOURCE_DIR}/examples")
+endif()
+
+# }}}
+# Build cpp tests if the option was given {{{
+
+if(WITH_TESTS)
+ find_package(CxxTest)
+ if(CXXTEST_FOUND)
+ include_directories(${CXXTEST_INCLUDE_DIR} "${PROJECT_SOURCE_DIR}/src")
+ add_definitions("-DTEST_SRC_ROOT=${PROJECT_SOURCE_DIR}/test")
+ enable_testing()
+ file(GLOB SRC_TEST "${PROJECT_SOURCE_DIR}/test/*.hpp")
+ CXXTEST_ADD_TEST(i3ipcpp_check test.cpp ${SRC_TEST})
+ target_link_libraries(i3ipcpp_check ${I3IPCPP_LIBRARIES})
+ else()
+ message(WARNING "CxxTest not found. Unable to run unit-tests")
+ endif()
+endif()
+
+# }}}
--- /dev/null
+# Doxyfile 1.8.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = i3ipc++
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "An C++ implementaiton of the i3 IPC"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = YES
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE = doxygen-warning.log
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = include
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS = *.hpp \
+ *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = *.cpp
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# compiled with the --with-libclang option.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra stylesheet files is of importance (e.g. the last
+# stylesheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
+# for the replacement values of the other commands the user is refered to
+# HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+PLANTUML_JAR_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
--- /dev/null
+The MIT License (MIT)
+
+Original work Copyright (c) 2015, Sergey Naumov
+Modified work Copyright 2018, Michael Carlberg (jaagr) and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+[](http://doge.mit-license.org)
+
+i3ipc++
+=======
+An implementation of i3 IPC in C++11.
+
+## Requirements
+
+* cmake (>= 3.0)
+* C++11 compiler
+* sigc++ 2.0
+* jsoncpp
+
+## Using
+Yet the only way of using is to add this repo as a submodule
+
+```bash
+git submodule add https://github.com/drmgc/i3ipcpp.git ./i3ipc++/
+```
+
+Then just type this in your `CMakeLists.txt`:
+
+```cmake
+...
+add_subdirectory(i3ipc++)
+
+include_directories(${I3IPCpp_INCLUDE_DIRS})
+link_directories(${I3IPCpp_LIBRARY_DIRS})
+...
+```
+
+And then just link:
+
+```cmake
+...
+target_link_libraries(someapp ${I3IPCpp_LIBRARIES})
+...
+```
+
+## Usage
+
+See also examples in `examples/` directory.
+
+### Connecting
+
+```c++
+#include <i3ipc++/ipc.hpp>
+
+i3ipc::connection conn;
+```
+
+The connection will be established automaticly.
+
+### Event handling
+
+First of all you need to declare the events you want to handle. As example we want to handle an binding and workspace events:
+```c++
+conn.subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_BINDING);
+```
+
+Then we need to connect to the signal handlers:
+```c++
+// Handler of WORKSPACE EVENT
+conn.signal_workspace_event.connect([](const i3ipc::workspace_event_t& ev) {
+ std::cout << "workspace_event: " << (char)ev.type << std::endl;
+ if (ev.current) {
+ std::cout << "\tSwitched to #" << ev.current->num << " - \"" << ev.current->name << '"' << std::endl;
+ }
+});
+
+// Handler of binding event
+conn.signal_binding_event.connect([](const i3ipc::binding_t& b) {
+ std::cout << "binding_event:" << std::endl
+ << "\tcommand = \"" << b.command << '"' << std::endl
+ << "\tinput_code = " << b.input_code << std::endl
+ << "\tsymbol = " << b.symbol << std::endl
+ << "\tinput_type = " << static_cast<char>(b.input_type) << std::endl
+ << "\tevent_state_mask =" << std::endl;
+ for (const std::string& s : b.event_state_mask) {
+ std::cout << "\t\t\"" << s << '"' << std::endl;
+ }
+});
+```
+
+Then we starting the event-handling loop
+```c++
+while (true) {
+ conn.handle_event();
+}
+```
+
+**Note:** If you want to interract with event_socket or just want to prepare manually you can call `conn.connect_event_socket()` (if you want to reconnect `conn.connect_event_socket(true)`), but if by default `connect_event_socket()` called on first `handle_event()` call.
+
+### Requesting
+
+Also you can request some data from i3, as example barconfigs:
+```c++
+std::vector<std::string> bar_configs = conn.get_bar_configs_list();
+```
+
+And then do with them something:
+```c++
+for (auto& name : bar_configs) {
+ std::shared_ptr<i3ipc::bar_config_t> bc = conn.get_bar_config(name);
+
+ // ... handling
+}
+```
+
+### Sending commands
+
+And, of course, you can command i3:
+```c++
+if (!conn.send_command("exit")) {
+ throw std::string("Failed to exit via command");
+}
+```
+
+## Version i3 support
+It is written according to the *current* specification, so some of new features in IPC can be not-implemented. If there is some of them, please notice at issues page.
+
+## Documentation
+The latest documentation you can find [**here**](http://drmgc.github.io/docs/api-ref/i3ipc++/latest/)
+
+## Licensing
+This library is licensed under under the MIT license, but it also uses [`JsonCpp`](https://github.com/open-source-parsers/jsoncpp) (*only for parsing i3's replies*) and my header-only library [`auss`](https://github.com/drmgc/auss)
+
+## Backward compatibility note
+While version is `0.x` there can be a lack of backward compatibility between minor releases, please see release notes.
--- /dev/null
+cmake_minimum_required(VERSION 3.0)
+project(i3ipc++-examples)
+
+include_directories(
+ ${I3IPCpp_INCLUDE_DIRS}
+)
+
+link_directories(
+ ${I3IPCpp_LIBRARY_DIRS}
+)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wno-unused-parameter")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -DDEBUG")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
+
+add_executable(workspaces workspaces.cpp)
+target_link_libraries(workspaces ${I3IPCpp_LIBRARIES})
+
+add_executable(events events.cpp)
+target_link_libraries(events ${I3IPCpp_LIBRARIES})
+
+add_executable(bar-configs bar-configs.cpp)
+target_link_libraries(bar-configs ${I3IPCpp_LIBRARIES})
--- /dev/null
+/**
+ * This program dumps all available barconfigs
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+static void dump_bar_config(const i3ipc::bar_config_t& bc) {
+ std::cout << '"' << bc.id << '"' << std::endl
+ << "\tmode = " << static_cast<char>(bc.mode) << std::endl
+ << "\tposition = " << static_cast<char>(bc.position) << std::endl
+ << "\tstatus_command = \"" << bc.status_command << '"' << std::endl
+ << "\tfont = \"" << bc.font << '"' << std::endl
+ << "\tworkspace_buttons = " << (bc.workspace_buttons ? "true" : "false") << std::endl
+ << "\tbinding_mode_indicator = " << (bc.binding_mode_indicator ? "true" : "false") << std::endl
+ << "\tverbose = " << (bc.verbose ? "true" : "false") << std::endl
+ << "\tcolors:" << std::endl;
+
+ std::cout << std::hex;
+ for (auto iter = bc.colors.begin(); iter != bc.colors.end(); iter++) {
+ std::cout << "\t\t\"" << iter->first << "\" = #" << iter->second << std::endl;
+ }
+ std::cout << std::dec;
+}
+
+
+int main() {
+ // First of all needs to create a connection
+ i3ipc::connection conn;
+
+ // Then request a list of barconfigs
+ std::vector<std::string> bar_configs = conn.get_bar_configs_list();
+
+ // And dump 'em all!!!!!
+ for (auto& name : bar_configs) {
+ std::shared_ptr<i3ipc::bar_config_t> bc = conn.get_bar_config(name);
+ dump_bar_config(*bc);
+ }
+
+ return 0;
+}
--- /dev/null
+/**
+ * This programs handle events and dump them to console
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+int main() {
+ // First of all we need to connect to an i3 process
+ i3ipc::connection conn;
+
+ // Then we subscribing on events (see i3ipc::EVENT_TYPE)
+ conn.subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_WINDOW | i3ipc::ET_BINDING);
+
+ // Handler of workspace_event
+ conn.signal_workspace_event.connect([](const i3ipc::workspace_event_t& ev) {
+ std::cout << "workspace_event: " << (char)ev.type << std::endl;
+ });
+
+ // Handler of window_event
+ conn.signal_window_event.connect([](const i3ipc::window_event_t& ev) {
+ std::cout << "window_event: " << (char)ev.type << std::endl;
+ });
+
+ // Handler of binding event
+ conn.signal_binding_event.connect([](const i3ipc::binding_t& b) {
+ std::cout << "binding_event:" << std::endl
+ << "\tcommand = \"" << b.command << '"' << std::endl
+ << "\tinput_code = " << b.input_code << std::endl
+ << "\tsymbol = " << b.symbol << std::endl
+ << "\tinput_type = " << static_cast<char>(b.input_type) << std::endl
+ << "\tevent_state_mask =" << std::endl;
+ for (const std::string& s : b.event_state_mask) {
+ std::cout << "\t\t\"" << s << '"' << std::endl;
+ }
+ });
+
+ // And starting an event-handling loop
+ while (true) {
+ conn.handle_event();
+ }
+
+ return 0;
+}
--- /dev/null
+/**
+ * This program dumps a tree of windows and workspaces to console
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+/**
+ * Reqursively dump containers of a tree
+ * @param c a root container
+ * @param prefix an alignment
+ */
+void dump_tree_container(const i3ipc::container_t& c, std::string& prefix) {
+ std::cout << prefix << "ID: " << c.id << " (i3's; X11's - " << c.xwindow_id << ")" << std::endl;
+ prefix.push_back('\t');
+ std::cout << prefix << "name = \"" << c.name << "\"" << std::endl;
+ std::cout << prefix << "type = \"" << c.type << "\"" << std::endl;
+ std::cout << prefix << "border = \"" << c.border_raw << "\"" << std::endl;
+ std::cout << prefix << "current_border_width = " << c.current_border_width << std::endl;
+ std::cout << prefix << "layout = \"" << c.layout_raw << "\"" << std::endl;
+ std::cout << prefix << "percent = " << c.percent << std::endl;
+ if (c.urgent) {
+ std::cout << prefix << "urgent" << std::endl;
+ }
+ if (c.focused) {
+ std::cout << prefix << "focused" << std::endl;
+ }
+ prefix.push_back('\t');
+ for (auto& n : c.nodes) {
+ dump_tree_container(*n, prefix);
+ }
+ prefix.pop_back();
+ prefix.pop_back();
+}
+
+
+int main() {
+ // First of all needs to create a connection
+ i3ipc::connection conn;
+
+ // Then we dump workspaces
+ for (auto& w : conn.get_workspaces()) {
+ std::cout << '#' << std::hex << w->num << std::dec
+ << "\n\tName: " << w->name
+ << "\n\tVisible: " << w->visible
+ << "\n\tFocused: " << w->focused
+ << "\n\tUrgent: " << w->urgent
+ << "\n\tRect: "
+ << "\n\t\tX: " << w->rect.x
+ << "\n\t\tY: " << w->rect.y
+ << "\n\t\tWidth: " << w->rect.width
+ << "\n\t\tHeight: " << w->rect.height
+ << "\n\tOutput: " << w->output
+ << std::endl;
+ }
+
+ // Then we dump the tree
+ std::string prefix_buf;
+ dump_tree_container(*conn.get_tree(), prefix_buf);
+
+ return 0;
+}
--- /dev/null
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <stdexcept>
+#include <string>
+
+// extern "C" {
+// #include <i3/ipc.h>
+// }
+
+namespace i3ipc {
+
+/** @defgroup i3ipc_util i3 IPC internal utilities
+ * Stuff for internal usage in I3Connection
+ * @{
+ */
+
+// extern "C" {
+
+/**
+ * i3 IPC header
+ */
+struct header_t {
+ /* 6 = strlen(I3_IPC_MAGIC) */
+ char magic[6]; ///< Magic string @see I3_IPC_MAGIC
+ uint32_t size; ///< Size of payload
+ uint32_t type; ///< Message type
+} __attribute__((packed));
+
+/**
+ * @brief Base class of i3 IPC errors
+ */
+class ipc_error : public std::runtime_error {
+ using std::runtime_error::runtime_error;
+};
+
+/**
+ * @brief Something wrong in message header (wrong magic number, message type etc.)
+ */
+class invalid_header_error : public ipc_error {
+ using ipc_error::ipc_error;
+};
+
+/**
+ * @brief Socket return EOF, but expected a data
+ */
+class eof_error : public ipc_error {
+ using ipc_error::ipc_error;
+};
+
+/**
+ * @brief If something wrong in a payload of i3's reply
+ */
+class invalid_reply_payload_error : public ipc_error {
+ using ipc_error::ipc_error;
+};
+
+/**
+ * @brief If any error occured, while using C-functions
+ */
+class errno_error : public ipc_error {
+ public:
+ errno_error();
+ errno_error(const std::string& msg);
+};
+
+/**
+ * @brief Messages (requests), that can be sended from the client
+ */
+enum class ClientMessageType : uint32_t {
+ COMMAND = 0,
+ GET_WORKSPACES = 1,
+ SUBSCRIBE = 2,
+ GET_OUTPUTS = 3,
+ GET_TREE = 4,
+ GET_MARKS = 5,
+ GET_BAR_CONFIG = 6,
+ GET_VERSION = 7,
+};
+
+/**
+ * @brief Replies, that can be sended from the i3 to the client
+ */
+enum class ReplyType : uint32_t {
+ COMMAND = 0,
+ WORKSPACES = 1,
+ SUBSCRIBE = 2,
+ OUTPUTS = 3,
+ TREE = 4,
+ MARKS = 5,
+ BAR_CONFIG = 6,
+ VERSION = 7,
+};
+
+/**
+ * @brief i3 IPC message buffer
+ */
+struct buf_t {
+ uint32_t size; ///< @brief Size of whole buffer
+ uint8_t* data; ///< @brief Pointer to the message
+
+ /**
+ * @brief i3 IPC message header
+ *
+ * Pointing on the begining
+ */
+ header_t* header;
+
+ /**
+ * @brief Message payload
+ *
+ * Pointing on the byte after the header
+ */
+ char* payload;
+
+ buf_t(uint32_t payload_size);
+ ~buf_t();
+
+ /**
+ * @brief Resize payload to the payload_size in the header
+ */
+ void realloc_payload_to_header();
+};
+
+/**
+ * Connect to the i3 socket
+ * @param socket_path a socket path
+ * @return socket id
+ */
+int32_t i3_connect(const std::string& socket_path);
+
+/**
+ * @brief Close the connection
+ * @param sockfd socket
+ */
+void i3_disconnect(const int32_t sockfd);
+
+/**
+ * @brief Send message to the socket
+ * @param sockfd a socket
+ * @param buff a message
+ */
+void i3_send(const int32_t sockfd, const buf_t& buff);
+
+/**
+ * @brief Recive a message from i3
+ * @param sockfd a socket
+ * @return a buffer of the message
+ */
+std::shared_ptr<buf_t> i3_recv(const int32_t sockfd);
+
+/**
+ * @brief Pack a buffer of message
+ */
+std::shared_ptr<buf_t> i3_pack(const ClientMessageType type, const std::string& payload);
+
+/**
+ * @brief Pack, send a message and receiv a reply
+ *
+ * Almost same to:
+ * @code{.cpp}
+ * i3_send(sockfd, i3_pack(type, payload));
+ * auto reply = i3_recv(sockfd);
+ * @endcode
+ */
+std::shared_ptr<buf_t> i3_msg(
+ const int32_t sockfd, const ClientMessageType type, const std::string& payload = std::string());
+
+/**
+ * @}
+ */
+
+} // namespace i3ipc
--- /dev/null
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <i3/ipc.h>
+}
+
+/**
+ * @addtogroup i3ipc i3 IPC C++ binding
+ * @{
+ */
+namespace i3ipc {
+
+/**
+ * Get path to the i3 IPC socket
+ * @return Path to a socket
+ */
+std::string get_socketpath();
+
+/**
+ * Primitive of rectangle
+ */
+struct rect_t {
+ int64_t x; ///< Position on X axis
+ int64_t y; ///< Position on Y axis
+ uint32_t width; ///< Width of rectangle
+ uint32_t height; ///< Height of rectangle
+};
+
+/**
+ * i3's workspace
+ */
+struct workspace_t {
+ int num; ///< Index of the worksapce
+ std::string name; ///< Name of the workspace
+ bool visible; ///< Is the workspace visible
+ bool focused; ///< Is the workspace is currently focused
+ bool urgent; ///< Is the workspace is urgent
+ rect_t rect; ///< A size of the workspace
+ std::string output; ///< An output of the workspace
+};
+
+/**
+ * i3's output
+ */
+struct output_t {
+ std::string name; ///< Name of the output
+ bool active; ///< Is the output currently active
+ std::string current_workspace; ///< Name of current workspace
+ rect_t rect; ///< Size of the output
+};
+
+/**
+ * Version of i3
+ */
+struct version_t {
+ std::string human_readable; ///< Human redable version string
+ std::string loaded_config_file_name; ///< Path to current config of i3
+ uint32_t major; ///< Major version of i3
+ uint32_t minor; ///< Minor version of i3
+ uint32_t patch; ///< Patch number of i3
+};
+
+/**
+ * Types of the events of i3
+ */
+enum EventType {
+ ET_WORKSPACE = (1 << 0), ///< Workspace event
+ ET_OUTPUT = (1 << 1), ///< Output event
+ ET_MODE = (1 << 2), ///< Output mode event
+ ET_WINDOW = (1 << 3), ///< Window event
+ ET_BARCONFIG_UPDATE = (1 << 4), ///< Bar config update event @attention Yet is not implemented as signal in connection
+ ET_BINDING = (1 << 5), ///< Binding event
+};
+
+/**
+ * Types of workspace events
+ */
+enum class WorkspaceEventType : char {
+ FOCUS = 'f', ///< Focused
+ INIT = 'i', ///< Initialized
+ EMPTY = 'e', ///< Became empty
+ URGENT = 'u', ///< Became urgent
+ RENAME = 'r', ///< Renamed
+ RELOAD = 'l', ///< Reloaded
+ RESTORED = 's', ///< Restored
+};
+
+/**
+ * Types of window events
+ */
+enum class WindowEventType : char {
+ NEW = 'n', ///< Window created
+ CLOSE = 'c', ///< Window closed
+ FOCUS = 'f', ///< Window got focus
+ TITLE = 't', ///< Title of window has been changed
+ FULLSCREEN_MODE = 'F', ///< Window toggled to fullscreen mode
+ MOVE = 'M', ///< Window moved
+ FLOATING = '_', ///< Window toggled floating mode
+ URGENT = 'u', ///< Window became urgent
+};
+
+/**
+ * A style of a container's border
+ */
+enum class BorderStyle : char {
+ UNKNOWN = '?', //< If got an unknown border style in reply
+ NONE = 'N',
+ NORMAL = 'n',
+ PIXEL = 'P',
+ ONE_PIXEL = '1',
+};
+
+/**
+ * A type of a container's layout
+ */
+enum class ContainerLayout : char {
+ UNKNOWN = '?', //< If got an unknown border style in reply
+ SPLIT_H = 'h',
+ SPLIT_V = 'v',
+ STACKED = 's',
+ TABBED = 't',
+ DOCKAREA = 'd',
+ OUTPUT = 'o',
+};
+
+/**
+ * A type of the input of bindings
+ */
+enum class InputType : char {
+ UNKNOWN = '?', //< If got an unknown input_type in binding_event
+ KEYBOARD = 'k',
+ MOUSE = 'm',
+};
+
+/**
+ * A mode of a bar
+ */
+enum class BarMode : char {
+ UNKNOWN = '?',
+ DOCK = 'd', ///< The bar sets the dock window type
+ HIDE = 'h', ///< The bar does not show unless a specific key is pressed
+};
+
+/**
+ * A position (of a bar?)
+ */
+enum class Position : char {
+ UNKNOWN = '?',
+ TOP = 't',
+ BOTTOM = 'b',
+};
+
+/**
+ * A node of tree of windows
+ */
+struct container_t {
+ uint64_t id; ///< The internal ID (actually a C pointer value) of this container. Do not make any assumptions about
+ ///< it. You can use it to (re-)identify and address containers when talking to i3
+ uint64_t xwindow_id; ///< The X11 window ID of the actual client window inside this container. This field is set to
+ ///< null for split containers or otherwise empty containers. This ID corresponds to what
+ ///< xwininfo(1) and other X11-related tools display (usually in hex)
+ std::string name; ///< The internal name of this container. For all containers which are part of the tree structure
+ ///< down to the workspace contents, this is set to a nice human-readable name of the container. For
+ ///< containers that have an X11 window, the content is the title (_NET_WM_NAME property) of that
+ ///< window. For all other containers, the content is not defined (yet)
+ std::string type; ///< Type of this container
+ BorderStyle border; ///< A style of the container's border
+ std::string border_raw; ///< A "border" field of TREE reply. NOT empty only if border equals BorderStyle::UNKNOWN
+ uint32_t current_border_width; ///< Number of pixels of the border width
+ ContainerLayout layout; ///< A type of the container's layout
+ std::string layout_raw; ///< A "layout" field of TREE reply. NOT empty only if layout equals ContainerLayout::UNKNOWN
+ float percent; ///< The percentage which this container takes in its parent. A value of < 0 means that the percent
+ ///< property does not make sense for this container, for example for the root container.
+ rect_t rect; ///< The absolute display coordinates for this container
+ rect_t window_rect; ///< The coordinates of the actual client window inside its container. These coordinates are
+ ///< relative to the container and do not include the window decoration (which is actually
+ ///< rendered on the parent container)
+ rect_t deco_rect; ///< The coordinates of the window decoration inside its container. These coordinates are relative
+ ///< to the container and do not include the actual client window
+ rect_t geometry; ///< The original geometry the window specified when i3 mapped it. Used when switching a window to
+ ///< floating mode, for example
+ bool urgent;
+ bool focused;
+
+ std::list<std::shared_ptr<container_t>> nodes;
+};
+
+/**
+ * A workspace event
+ */
+struct workspace_event_t {
+ WorkspaceEventType type;
+ std::shared_ptr<workspace_t> current; ///< Current focused workspace
+ std::shared_ptr<workspace_t> old; ///< Old (previous) workspace @note With some WindowEventType could be null
+};
+
+/**
+ * A window event
+ */
+struct window_event_t {
+ WindowEventType type;
+ std::shared_ptr<container_t>
+ container; ///< A container event associated with @note With some WindowEventType could be null
+};
+
+/**
+ * A binding
+ */
+struct binding_t {
+ std::string command; ///< The i3 command that is configured to run for this binding
+ std::vector<std::string> event_state_mask; ///< The group and modifier keys that were configured with this binding
+ int32_t input_code; ///< If the binding was configured with bindcode, this will be the key code that was given for the
+ ///< binding. If the binding is a mouse binding, it will be the number of the mouse button that
+ ///< was pressed. Otherwise it will be 0
+ std::string symbol; ///< If this is a keyboard binding that was configured with bindsym, this field will contain the
+ ///< given symbol. Otherwise it will be null
+ InputType input_type;
+};
+
+/**
+ * A mode
+ */
+struct mode_t {
+ std::string change; ///< The current mode in use
+ bool pango_markup; ///< Should pango markup be used for displaying this mode
+};
+
+/**
+ * A bar configuration
+ */
+struct bar_config_t {
+ std::string id; ///< The ID for this bar. Included in case you request multiple configurations and want to
+ ///< differentiate the different replies.
+ BarMode mode;
+ Position position;
+ std::string status_command; ///< Command which will be run to generate a statusline. Each line on stdout of this
+ ///< command will be displayed in the bar. At the moment, no formatting is supported
+ std::string font; ///< The font to use for text on the bar
+ bool workspace_buttons; ///< Display workspace buttons or not? Defaults to true.
+ bool binding_mode_indicator; ///< Display the mode indicator or not? Defaults to true.
+ bool verbose; ///< Should the bar enable verbose output for debugging? Defaults to false.
+ std::map<std::string, uint32_t>
+ colors; ///< Contains key/value pairs of colors. Each value is a color code in format 0xRRGGBB
+};
+
+struct buf_t;
+/**
+ * Connection to the i3
+ */
+class connection {
+ public:
+ /**
+ * Connect to the i3
+ * @param socket_path path to a i3 IPC socket
+ */
+ connection(const std::string& socket_path = get_socketpath());
+ ~connection();
+
+ /**
+ * Send a command to i3
+ * @param command command
+ * @return Is command successfully executed
+ */
+ bool send_command(const std::string& command) const;
+
+ /**
+ * Request a list of workspaces
+ * @return List of workspaces
+ */
+ std::vector<std::shared_ptr<workspace_t>> get_workspaces() const;
+
+ /**
+ * Request a list of outputs
+ * @return List of outputs
+ */
+ std::vector<std::shared_ptr<output_t>> get_outputs() const;
+
+ /**
+ * Request a version of i3
+ * @return Version of i3
+ */
+ version_t get_version() const;
+
+ /**
+ * Request a tree of windows
+ * @return A root container
+ */
+ std::shared_ptr<container_t> get_tree() const;
+
+ /**
+ * Request a list of names of available barconfigs
+ * @return A list of names of barconfigs
+ */
+ std::vector<std::string> get_bar_configs_list() const;
+
+ /**
+ * Request a barconfig
+ * @param name name of barconfig
+ * @return The barconfig
+ */
+ std::shared_ptr<bar_config_t> get_bar_config(const std::string& name) const;
+
+ /**
+ * Subscribe on an events of i3
+ *
+ * If connection isn't handling events at the moment, event numer will be added to subscription list.
+ * Else will also send subscripe request to i3
+ *
+ * Example:
+ * @code{.cpp}
+ * connection conn;
+ * conn.subscribe(i3ipc::ipc::ET_WORKSPACE | i3ipc::ipc::ET_WINDOW);
+ * @endcode
+ *
+ * @param events event type (EventType enum)
+ * @return Is successfully subscribed. If connection isn't handling events at the moment, then always true.
+ */
+ bool subscribe(const int32_t events);
+
+ /**
+ * Handle an event from i3
+ * @note Used only in main()
+ */
+ bool handle_event();
+
+ /**
+ * Get the fd of the main socket
+ * @return the file descriptor of the main socket.
+ */
+ int32_t get_main_socket_fd();
+
+ /**
+ * Get the fd of the event socket
+ * @return the file descriptor of the event socket.
+ */
+ int32_t get_event_socket_fd();
+
+ /**
+ * Connect the event socket to IPC
+ * @param reconnect if true the event socket will be disconnected and connected again
+ * @note Automaticly called, when calling handle_event();
+ */
+ void connect_event_socket(const bool reconnect = false);
+
+ /**
+ * Disconnect the event socket
+ */
+ void disconnect_event_socket();
+
+ std::function<void(const workspace_event_t&)> on_workspace_event; ///< Workspace event signal
+ std::function<void()> on_output_event; ///< Output event signal
+ std::function<void(const mode_t&)> on_mode_event; ///< Output mode event signal
+ std::function<void(const window_event_t&)> on_window_event; ///< Window event signal
+ std::function<void(const bar_config_t&)> on_barconfig_update_event; ///< Barconfig update event signal
+ std::function<void(const binding_t&)> on_binding_event; ///< Binding event signal
+ std::function<void(EventType, const std::shared_ptr<const buf_t>&)>
+ on_event; ///< i3 event signal @note Default handler routes event to signal according to type
+ private:
+ const int32_t m_main_socket;
+ int32_t m_event_socket;
+ int32_t m_subscriptions;
+ const std::string m_socket_path;
+};
+
+/**
+ * Get version of i3ipc++
+ * @return the version of i3ipc++
+ */
+const version_t& get_version();
+
+} // namespace i3ipc
+
+/**
+ * @}
+ */
--- /dev/null
+#pragma once
+
+#include <auss.hpp>
+#include <ostream>
+#include <vector>
+
+/**
+ * @addtogroup logging Logging
+ * @{
+ */
+
+namespace i3ipc {
+
+/**
+ * @brief Common logging outputs
+ */
+extern std::vector<std::ostream*> g_logging_outs;
+
+/**
+ * @brief Logging outputs for error messages
+ */
+extern std::vector<std::ostream*> g_logging_err_outs;
+
+/**
+ * @brief Put to a logging outputs some dtat
+ * @param data data, that you want to put to the logging outputs
+ * @param err is your information is error report or something that must be putted to the error logging outputs
+ */
+template <typename T>
+inline void log(const T& data, const bool err = false) {
+ for (auto out : (err ? g_logging_err_outs : g_logging_outs)) {
+ *out << data << std::endl;
+ }
+}
+
+template <>
+inline void log(const auss_t& data, const bool err) {
+ log(data.to_string());
+}
+
+} // namespace i3ipc
+
+#ifdef WITH_LOGGING
+
+/**
+ * Internal macro used in I3IPC_*-logging macros
+ */
+#define I3IPC_LOG(T, ERR) ::i3ipc::log((T), (ERR));
+
+/**
+ * Put information message to log
+ * @param T message
+ */
+#define I3IPC_INFO(T) I3IPC_LOG(auss_t() << "i: " << T, false)
+
+/**
+ * Put error message to log
+ * @param T message
+ */
+#define I3IPC_ERR(T) I3IPC_LOG(auss_t() << "E: " << T, true)
+
+/**
+ * Put warning message to log
+ * @param T message
+ */
+#define I3IPC_WARN(T) I3IPC_LOG(auss_t() << "W: " << T, true)
+
+#ifdef DEBUG
+
+/**
+ * Put debug message to log
+ * @param T message
+ */
+#define I3IPC_DEBUG(T) I3IPC_LOG(auss_t() << "D: " << T, true)
+
+#else
+
+/**
+ * Put debug message to log
+ * @param T message
+ */
+#define I3IPC_DEBUG(T)
+#endif
+
+#else
+#define I3IPC_LOG(T, ERR)
+#define I3IPC_INFO(T)
+#define I3IPC_ERR(T)
+#define I3IPC_WARN(T)
+#define I3IPC_DEBUG(T)
+#define I3IPC_DEBUG(T)
+#endif
+
+/**
+ * @}
+ */
--- /dev/null
+extern "C" {
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+}
+
+#include <auss.hpp>
+#include <cstring>
+#include <ios>
+
+#include "i3ipc++/ipc-util.hpp"
+
+namespace i3ipc {
+
+static std::string format_errno(const std::string& msg = std::string()) {
+ auss_t a;
+ if (msg.size() > 0)
+ a << msg << ": ";
+ a << "errno " << errno << " (" << strerror(errno) << ')';
+ return a;
+}
+
+errno_error::errno_error() : ipc_error(format_errno()) {}
+errno_error::errno_error(const std::string& msg) : ipc_error(format_errno(msg)) {}
+
+static const std::string g_i3_ipc_magic = "i3-ipc";
+
+buf_t::buf_t(uint32_t payload_size) : size(sizeof(header_t) + payload_size) {
+ data = new uint8_t[size];
+ header = (header_t*)data;
+ payload = (char*)(data + sizeof(header_t));
+ memcpy(header->magic, g_i3_ipc_magic.c_str(), sizeof(header->magic));
+ header->size = payload_size;
+ header->type = 0x0;
+}
+buf_t::~buf_t() {
+ delete[] data;
+}
+
+void buf_t::realloc_payload_to_header() {
+ uint8_t* new_data = new uint8_t[sizeof(header_t) + header->size];
+ memcpy(new_data, header, sizeof(header_t));
+ delete[] data;
+ data = new_data;
+ header = (header_t*)data;
+ payload = (char*)(data + sizeof(header_t));
+}
+
+int32_t i3_connect(const std::string& socket_path) {
+ int32_t sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (sockfd == -1) {
+ throw errno_error("Could not create a socket");
+ }
+
+ (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); // What for?
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_LOCAL;
+ strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
+ if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
+ throw errno_error("Failed to connect to i3 at '" + (socket_path.empty() ? "<empty>" : socket_path) + "'");
+ }
+
+ return sockfd;
+}
+
+void i3_disconnect(const int32_t sockfd) {
+ close(sockfd);
+}
+
+std::shared_ptr<buf_t> i3_pack(const ClientMessageType type, const std::string& payload) {
+ buf_t* buff = new buf_t(payload.length());
+ buff->header->type = static_cast<uint32_t>(type);
+ strncpy(buff->payload, payload.c_str(), buff->header->size);
+ return std::shared_ptr<buf_t>(buff);
+}
+
+ssize_t writeall(int fd, const uint8_t* buf, size_t count) {
+ size_t written = 0;
+ ssize_t n = 0;
+
+ while (written < count) {
+ n = write(fd, buf + written, count - written);
+ if (n == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return n;
+ }
+ written += (size_t)n;
+ }
+
+ return written;
+}
+
+ssize_t swrite(int fd, const uint8_t* buf, size_t count) {
+ ssize_t n;
+
+ n = writeall(fd, buf, count);
+ if (n == -1)
+ throw errno_error(auss_t() << "Failed to write " << std::hex << fd);
+ else
+ return n;
+}
+
+void i3_send(const int32_t sockfd, const buf_t& buff) {
+ swrite(sockfd, buff.data, buff.size);
+}
+
+std::shared_ptr<buf_t> i3_recv(const int32_t sockfd) {
+ auto buff = std::make_shared<buf_t>(0);
+ const uint32_t header_size = sizeof(header_t);
+
+ {
+ uint8_t* header = (uint8_t*)buff->header;
+ uint32_t readed = 0;
+ while (readed < header_size) {
+ int n = read(sockfd, header + readed, header_size - readed);
+ if (n == -1) {
+ throw errno_error(auss_t() << "Failed to read header from socket 0x" << std::hex << sockfd);
+ }
+ if (n == 0) {
+ throw eof_error("Unexpected EOF while reading header");
+ }
+
+ readed += n;
+ }
+ }
+
+ if (g_i3_ipc_magic != std::string(buff->header->magic, g_i3_ipc_magic.length())) {
+ throw invalid_header_error("Invalid magic in reply");
+ }
+
+ buff->realloc_payload_to_header();
+
+ {
+ uint32_t readed = 0;
+ int n;
+ while (readed < buff->header->size) {
+ if ((n = read(sockfd, buff->payload + readed, buff->header->size - readed)) == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ throw errno_error(auss_t() << "Failed to read payload from socket 0x" << std::hex << sockfd);
+ }
+
+ readed += n;
+ }
+ }
+
+ return buff;
+}
+
+std::shared_ptr<buf_t> i3_msg(const int32_t sockfd, const ClientMessageType type, const std::string& payload) {
+ auto send_buff = i3_pack(type, payload);
+ i3_send(sockfd, *send_buff);
+ auto recv_buff = i3_recv(sockfd);
+ if (send_buff->header->type != recv_buff->header->type) {
+ throw invalid_header_error(auss_t() << "Invalid reply type: Expected 0x" << std::hex << send_buff->header->type
+ << ", got 0x" << recv_buff->header->type);
+ }
+ return recv_buff;
+}
+
+} // namespace i3ipc
--- /dev/null
+#include "i3ipc++/ipc.hpp"
+
+#include <json/json.h>
+
+#include <auss.hpp>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+
+#include "i3ipc++/ipc-util.hpp"
+#include "i3ipc++/log.hpp"
+
+namespace i3ipc {
+
+// For log.hpp
+std::vector<std::ostream*> g_logging_outs = {
+ &std::cout,
+};
+std::vector<std::ostream*> g_logging_err_outs = {
+ &std::cerr,
+};
+
+#define IPC_JSON_READ(ROOT) \
+ { \
+ Json::CharReaderBuilder b; \
+ const std::unique_ptr<Json::CharReader> reader(b.newCharReader()); \
+ JSONCPP_STRING error; \
+ if (!reader->parse(buf->payload, buf->payload + buf->header->size, &ROOT, &error)) { \
+ throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " << error); \
+ } \
+ }
+
+#define IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, TYPE_CHECK, TYPE_NAME) \
+ { \
+ if (!(OBJ).TYPE_CHECK()) { \
+ throw invalid_reply_payload_error( \
+ auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " OBJ_DESCR " expected to be " TYPE_NAME); \
+ } \
+ }
+#define IPC_JSON_ASSERT_TYPE_OBJECT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isObject, "an object")
+#define IPC_JSON_ASSERT_TYPE_ARRAY(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isArray, "an array")
+#define IPC_JSON_ASSERT_TYPE_BOOL(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isBool, "a bool")
+#define IPC_JSON_ASSERT_TYPE_INT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isInt, "an integer")
+
+inline rect_t parse_rect_from_json(const Json::Value& value) {
+ rect_t r{};
+ r.x = value["x"].asInt64();
+ r.y = value["y"].asInt64();
+ r.width = value["width"].asUInt();
+ r.height = value["height"].asUInt();
+ return r;
+}
+
+static std::shared_ptr<container_t> parse_container_from_json(const Json::Value& o) {
+#define i3IPC_TYPE_STR "PARSE CONTAINER FROM JSON"
+ if (o.isNull())
+ return std::shared_ptr<container_t>();
+ std::shared_ptr<container_t> container(new container_t());
+ IPC_JSON_ASSERT_TYPE_OBJECT(o, "o")
+
+ container->id = o["id"].asUInt64();
+ container->xwindow_id = o["window"].asUInt64();
+ container->name = o["name"].asString();
+ container->type = o["type"].asString();
+ container->current_border_width = o["current_border_width"].asInt();
+ container->percent = o["percent"].asFloat();
+ container->rect = parse_rect_from_json(o["rect"]);
+ container->window_rect = parse_rect_from_json(o["window_rect"]);
+ container->deco_rect = parse_rect_from_json(o["deco_rect"]);
+ container->geometry = parse_rect_from_json(o["geometry"]);
+ container->urgent = o["urgent"].asBool();
+ container->focused = o["focused"].asBool();
+
+ container->border = BorderStyle::UNKNOWN;
+ std::string border = o["border"].asString();
+ if (border == "normal") {
+ container->border = BorderStyle::NORMAL;
+ } else if (border == "none") {
+ container->border = BorderStyle::NONE;
+ } else if (border == "pixel") {
+ container->border = BorderStyle::PIXEL;
+ } else if (border == "1pixel") {
+ container->border = BorderStyle::ONE_PIXEL;
+ } else {
+ container->border_raw = border;
+ I3IPC_WARN("Got a unknown \"border\" property: \""
+ << border
+ << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+ }
+
+ container->layout = ContainerLayout::UNKNOWN;
+ std::string layout = o["layout"].asString();
+
+ if (layout == "splith") {
+ container->layout = ContainerLayout::SPLIT_H;
+ } else if (layout == "splitv") {
+ container->layout = ContainerLayout::SPLIT_V;
+ } else if (layout == "stacked") {
+ container->layout = ContainerLayout::STACKED;
+ } else if (layout == "tabbed") {
+ container->layout = ContainerLayout::TABBED;
+ } else if (layout == "dockarea") {
+ container->layout = ContainerLayout::DOCKAREA;
+ } else if (layout == "output") {
+ container->layout = ContainerLayout::OUTPUT;
+ } else {
+ container->layout_raw = border;
+ I3IPC_WARN("Got a unknown \"layout\" property: \""
+ << layout
+ << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+ }
+
+ Json::Value nodes = o["nodes"];
+ if (!nodes.isNull()) {
+ IPC_JSON_ASSERT_TYPE_ARRAY(nodes, "nodes")
+ for (Json::ArrayIndex i = 0; i < nodes.size(); i++) {
+ container->nodes.push_back(parse_container_from_json(nodes[i]));
+ }
+ }
+
+ return container;
+#undef i3IPC_TYPE_STR
+}
+
+static std::shared_ptr<workspace_t> parse_workspace_from_json(const Json::Value& value) {
+ if (value.isNull())
+ return std::shared_ptr<workspace_t>();
+ Json::Value num = value["num"];
+ Json::Value name = value["name"];
+ Json::Value visible = value["visible"];
+ Json::Value focused = value["focused"];
+ Json::Value urgent = value["urgent"];
+ Json::Value rect = value["rect"];
+ Json::Value output = value["output"];
+
+ std::shared_ptr<workspace_t> p(new workspace_t());
+ p->num = num.asInt();
+ p->name = name.asString();
+ p->visible = visible.asBool();
+ p->focused = focused.asBool();
+ p->urgent = urgent.asBool();
+ p->rect = parse_rect_from_json(rect);
+ p->output = output.asString();
+ return p;
+}
+
+static std::shared_ptr<output_t> parse_output_from_json(const Json::Value& value) {
+ if (value.isNull())
+ return std::shared_ptr<output_t>();
+ Json::Value name = value["name"];
+ Json::Value active = value["active"];
+ Json::Value current_workspace = value["current_workspace"];
+ Json::Value rect = value["rect"];
+
+ std::shared_ptr<output_t> p(new output_t());
+ p->name = name.asString();
+ p->active = active.asBool();
+ p->current_workspace = (current_workspace.isNull() ? std::string() : current_workspace.asString());
+ p->rect = parse_rect_from_json(rect);
+ return p;
+}
+
+static std::shared_ptr<binding_t> parse_binding_from_json(const Json::Value& value) {
+#define i3IPC_TYPE_STR "PARSE BINDING FROM JSON"
+ if (value.isNull())
+ return std::shared_ptr<binding_t>();
+ IPC_JSON_ASSERT_TYPE_OBJECT(value, "binding")
+ std::shared_ptr<binding_t> b(new binding_t());
+
+ b->command = value["command"].asString();
+ b->symbol = value["symbol"].asString();
+ b->input_code = value["input_code"].asInt();
+
+ Json::Value input_type = value["input_type"].asString();
+ if (input_type == "keyboard") {
+ b->input_type = InputType::KEYBOARD;
+ } else if (input_type == "mouse") {
+ b->input_type = InputType::MOUSE;
+ } else {
+ b->input_type = InputType::UNKNOWN;
+ }
+
+ Json::Value esm_arr = value["event_state_mask"];
+ IPC_JSON_ASSERT_TYPE_ARRAY(esm_arr, "event_state_mask")
+
+ b->event_state_mask.resize(esm_arr.size());
+
+ for (Json::ArrayIndex i = 0; i < esm_arr.size(); i++) {
+ b->event_state_mask[i] = esm_arr[i].asString();
+ }
+
+ return b;
+#undef i3IPC_TYPE_STR
+}
+
+static std::shared_ptr<mode_t> parse_mode_from_json(const Json::Value& value) {
+ if (value.isNull())
+ return std::shared_ptr<mode_t>();
+ Json::Value change = value["change"];
+ Json::Value pango_markup = value["pango_markup"];
+
+ std::shared_ptr<mode_t> p(new mode_t());
+ p->change = change.asString();
+ p->pango_markup = pango_markup.asBool();
+ return p;
+}
+
+static std::shared_ptr<bar_config_t> parse_bar_config_from_json(const Json::Value& value) {
+#define i3IPC_TYPE_STR "PARSE BAR CONFIG FROM JSON"
+ if (value.isNull())
+ return std::shared_ptr<bar_config_t>();
+ IPC_JSON_ASSERT_TYPE_OBJECT(value, "(root)")
+ std::shared_ptr<bar_config_t> bc(new bar_config_t());
+
+ bc->id = value["id"].asString();
+ bc->status_command = value["status_command"].asString();
+ bc->font = value["font"].asString();
+ bc->workspace_buttons = value["workspace_buttons"].asBool();
+ bc->binding_mode_indicator = value["binding_mode_indicator"].asBool();
+ bc->verbose = value["verbose"].asBool();
+
+ std::string mode = value["mode"].asString();
+ if (mode == "dock") {
+ bc->mode = BarMode::DOCK;
+ } else if (mode == "hide") {
+ bc->mode = BarMode::HIDE;
+ } else {
+ bc->mode = BarMode::UNKNOWN;
+ I3IPC_WARN("Got a unknown \"mode\" property: \""
+ << mode
+ << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+ }
+
+ std::string position = value["position"].asString();
+ if (position == "top") {
+ bc->position = Position::TOP;
+ } else if (mode == "bottom") {
+ bc->position = Position::BOTTOM;
+ } else {
+ bc->position = Position::UNKNOWN;
+ I3IPC_WARN("Got a unknown \"position\" property: \""
+ << position
+ << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+ }
+
+ Json::Value colors = value["colors"];
+ IPC_JSON_ASSERT_TYPE_OBJECT(value, "colors")
+ auto colors_list = colors.getMemberNames();
+ for (auto& m : colors_list) {
+ bc->colors[m] = std::stoul(colors[m].asString().substr(1), nullptr, 16);
+ }
+
+ return bc;
+#undef i3IPC_TYPE_STR
+}
+
+std::string get_socketpath() {
+ std::string str;
+ {
+ auss_t str_buf;
+ FILE* in;
+ char buf[512] = {0};
+ if (!(in = popen("i3 --get-socketpath", "r"))) {
+ throw errno_error("Failed to get socket path");
+ }
+
+ while (fgets(buf, sizeof(buf), in) != nullptr) {
+ str_buf << buf;
+ }
+ int ret = WEXITSTATUS(pclose(in));
+ str = str_buf;
+ if (ret != 0) {
+ throw ipc_error("i3 --get-socketpath failed with exit code " + std::to_string(ret) + " and output '" + str + "'");
+ }
+ }
+ if (!str.empty() && str.back() == '\n') {
+ str.pop_back();
+ }
+ return str;
+}
+
+connection::connection(const std::string& socket_path)
+ : m_main_socket(i3_connect(socket_path)), m_event_socket(-1), m_subscriptions(0), m_socket_path(socket_path) {
+#define i3IPC_TYPE_STR "i3's event"
+ on_event = [this](EventType event_type, const std::shared_ptr<const buf_t>& buf) {
+ switch (event_type) {
+ case ET_WORKSPACE: {
+ workspace_event_t ev;
+ Json::Value root;
+ IPC_JSON_READ(root);
+ std::string change = root["change"].asString();
+ if (change == "focus") {
+ ev.type = WorkspaceEventType::FOCUS;
+ } else if (change == "init") {
+ ev.type = WorkspaceEventType::INIT;
+ } else if (change == "empty") {
+ ev.type = WorkspaceEventType::EMPTY;
+ } else if (change == "urgent") {
+ ev.type = WorkspaceEventType::URGENT;
+ } else if (change == "rename") {
+ ev.type = WorkspaceEventType::RENAME;
+ } else if (change == "reload") {
+ ev.type = WorkspaceEventType::RELOAD;
+ } else if (change == "restored") {
+ ev.type = WorkspaceEventType::RESTORED;
+ } else {
+ I3IPC_WARN("Unknown workspace event type " << change)
+ break;
+ }
+ I3IPC_DEBUG("WORKSPACE " << change)
+
+ Json::Value current = root["current"];
+ Json::Value old = root["old"];
+
+ if (!current.isNull()) {
+ ev.current = parse_workspace_from_json(current);
+ }
+ if (!old.isNull()) {
+ ev.old = parse_workspace_from_json(old);
+ }
+
+ if (on_workspace_event) {
+ on_workspace_event(ev);
+ }
+ break;
+ }
+ case ET_OUTPUT:
+ I3IPC_DEBUG("OUTPUT")
+ if (on_output_event) {
+ on_output_event();
+ }
+ break;
+ case ET_MODE: {
+ I3IPC_DEBUG("MODE")
+ Json::Value root;
+ IPC_JSON_READ(root);
+ std::shared_ptr<mode_t> mode_data = parse_mode_from_json(root);
+ if (on_mode_event) {
+ on_mode_event(*mode_data);
+ }
+ break;
+ }
+ case ET_WINDOW: {
+ window_event_t ev;
+ Json::Value root;
+ IPC_JSON_READ(root);
+ std::string change = root["change"].asString();
+ if (change == "new") {
+ ev.type = WindowEventType::NEW;
+ } else if (change == "close") {
+ ev.type = WindowEventType::CLOSE;
+ } else if (change == "focus") {
+ ev.type = WindowEventType::FOCUS;
+ } else if (change == "title") {
+ ev.type = WindowEventType::TITLE;
+ } else if (change == "fullscreen_mode") {
+ ev.type = WindowEventType::FULLSCREEN_MODE;
+ } else if (change == "move") {
+ ev.type = WindowEventType::MOVE;
+ } else if (change == "floating") {
+ ev.type = WindowEventType::FLOATING;
+ } else if (change == "urgent") {
+ ev.type = WindowEventType::URGENT;
+ }
+ I3IPC_DEBUG("WINDOW " << change)
+
+ Json::Value container = root["container"];
+ if (!container.isNull()) {
+ ev.container = parse_container_from_json(container);
+ }
+
+ if (on_window_event) {
+ on_window_event(ev);
+ }
+ break;
+ }
+ case ET_BARCONFIG_UPDATE: {
+ I3IPC_DEBUG("BARCONFIG_UPDATE")
+ Json::Value root;
+ IPC_JSON_READ(root);
+ std::shared_ptr<bar_config_t> barconf = parse_bar_config_from_json(root);
+ if (on_barconfig_update_event) {
+ on_barconfig_update_event(*barconf);
+ }
+ break;
+ }
+ case ET_BINDING: {
+ Json::Value root;
+ IPC_JSON_READ(root);
+ std::string change = root["change"].asString();
+ if (change != "run") {
+ I3IPC_WARN("Got \"" << change << "\" in field \"change\" of binding_event. Expected \"run\"")
+ }
+
+ Json::Value binding_json = root["binding"];
+ std::shared_ptr<binding_t> bptr;
+ if (!binding_json.isNull()) {
+ bptr = parse_binding_from_json(binding_json);
+ }
+
+ if (!bptr) {
+ I3IPC_ERR("Failed to parse field \"binding\" from binding_event")
+ } else {
+ I3IPC_DEBUG("BINDING " << bptr->symbol);
+ if (on_binding_event) {
+ on_binding_event(*bptr);
+ }
+ }
+ break;
+ }
+ };
+ };
+#undef i3IPC_TYPE_STR
+}
+connection::~connection() {
+ i3_disconnect(m_main_socket);
+ if (m_event_socket > 0)
+ this->disconnect_event_socket();
+}
+
+void connection::connect_event_socket(const bool reconnect) {
+ if (m_event_socket > 0) {
+ if (reconnect) {
+ this->disconnect_event_socket();
+ } else {
+ I3IPC_ERR("Trying to initialize event socket secondary")
+ return;
+ }
+ }
+ m_event_socket = i3_connect(m_socket_path);
+ this->subscribe(m_subscriptions);
+}
+
+void connection::disconnect_event_socket() {
+ if (m_event_socket <= 0) {
+ I3IPC_WARN("Trying to disconnect non-connected event socket")
+ return;
+ }
+ i3_disconnect(m_event_socket);
+}
+
+bool connection::handle_event() {
+ if (m_event_socket <= 0) {
+ this->connect_event_socket();
+ }
+
+ auto buf = i3_recv(m_event_socket);
+
+ if (buf && this->on_event) {
+ this->on_event(static_cast<EventType>(1 << (buf->header->type & 0x7f)), std::static_pointer_cast<const buf_t>(buf));
+ } else if (buf) {
+ return true;
+ }
+ return false;
+}
+
+bool connection::subscribe(const int32_t events) {
+#define i3IPC_TYPE_STR "SUBSCRIBE"
+ if (m_event_socket <= 0) {
+ m_subscriptions |= events;
+ return true;
+ }
+ std::string payload;
+ {
+ auss_t payload_auss;
+ if (events & static_cast<int32_t>(ET_WORKSPACE)) {
+ payload_auss << "\"workspace\",";
+ }
+ if (events & static_cast<int32_t>(ET_OUTPUT)) {
+ payload_auss << "\"output\",";
+ }
+ if (events & static_cast<int32_t>(ET_MODE)) {
+ payload_auss << "\"mode\",";
+ }
+ if (events & static_cast<int32_t>(ET_WINDOW)) {
+ payload_auss << "\"window\",";
+ }
+ if (events & static_cast<int32_t>(ET_BARCONFIG_UPDATE)) {
+ payload_auss << "\"barconfig_update\",";
+ }
+ if (events & static_cast<int32_t>(ET_BINDING)) {
+ payload_auss << "\"binding\",";
+ }
+ payload = payload_auss;
+ if (payload.empty()) {
+ return true;
+ }
+ payload.pop_back();
+ }
+ I3IPC_DEBUG("i3 IPC subscriptions: " << payload)
+
+ auto buf = i3_msg(m_event_socket, ClientMessageType::SUBSCRIBE, auss_t() << '[' << payload << ']');
+ Json::Value root;
+ IPC_JSON_READ(root)
+
+ m_subscriptions |= events;
+
+ return root["success"].asBool();
+#undef i3IPC_TYPE_STR
+}
+
+version_t connection::get_version() const {
+#define i3IPC_TYPE_STR "GET_VERSION"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_VERSION);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ IPC_JSON_ASSERT_TYPE_OBJECT(root, "root")
+
+ version_t v{};
+ v.human_readable = root["human_readable"].asString();
+ v.loaded_config_file_name = root["loaded_config_file_name"].asString();
+ v.major = root["major"].asUInt();
+ v.minor = root["minor"].asUInt();
+ v.patch = root["patch"].asUInt();
+ return v;
+#undef i3IPC_TYPE_STR
+}
+
+std::shared_ptr<container_t> connection::get_tree() const {
+#define i3IPC_TYPE_STR "GET_TREE"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_TREE);
+ Json::Value root;
+ IPC_JSON_READ(root);
+ return parse_container_from_json(root);
+#undef i3IPC_TYPE_STR
+}
+
+std::vector<std::shared_ptr<output_t>> connection::get_outputs() const {
+#define i3IPC_TYPE_STR "GET_OUTPUTS"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_OUTPUTS);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+ std::vector<std::shared_ptr<output_t>> outputs;
+
+ for (auto w : root) {
+ outputs.push_back(parse_output_from_json(w));
+ }
+
+ return outputs;
+#undef i3IPC_TYPE_STR
+}
+
+std::vector<std::shared_ptr<workspace_t>> connection::get_workspaces() const {
+#define i3IPC_TYPE_STR "GET_WORKSPACES"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_WORKSPACES);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+ std::vector<std::shared_ptr<workspace_t>> workspaces;
+
+ for (auto w : root) {
+ workspaces.push_back(parse_workspace_from_json(w));
+ }
+
+ return workspaces;
+#undef i3IPC_TYPE_STR
+}
+
+std::vector<std::string> connection::get_bar_configs_list() const {
+#define i3IPC_TYPE_STR "GET_BAR_CONFIG (get_bar_configs_list)"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+ std::vector<std::string> l;
+
+ for (auto w : root) {
+ l.push_back(w.asString());
+ }
+
+ return l;
+#undef i3IPC_TYPE_STR
+}
+
+std::shared_ptr<bar_config_t> connection::get_bar_config(const std::string& name) const {
+#define i3IPC_TYPE_STR "GET_BAR_CONFIG"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG, name);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ return parse_bar_config_from_json(root);
+#undef i3IPC_TYPE_STR
+}
+
+bool connection::send_command(const std::string& command) const {
+#define i3IPC_TYPE_STR "COMMAND"
+ auto buf = i3_msg(m_main_socket, ClientMessageType::COMMAND, command);
+ Json::Value root;
+ IPC_JSON_READ(root)
+ IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+ Json::Value payload = root[0];
+ IPC_JSON_ASSERT_TYPE_OBJECT(payload, " first item of root")
+
+ if (payload["success"].asBool()) {
+ return true;
+ } else {
+ Json::Value error = payload["error"];
+ if (!error.isNull()) {
+ I3IPC_ERR("Failed to execute command: " << error.asString())
+ }
+ return false;
+ }
+#undef i3IPC_TYPE_STR
+}
+
+int32_t connection::get_main_socket_fd() {
+ return m_main_socket;
+}
+
+int32_t connection::get_event_socket_fd() {
+ return m_event_socket;
+}
+
+const version_t& get_version() {
+#define I3IPC_VERSION_MAJOR 0
+#define I3IPC_VERSION_MINOR 4
+#define I3IPC_VERSION_PATCH 0
+ static version_t v{};
+ v.human_readable = auss_t() << I3IPC_VERSION_MAJOR << '.' << I3IPC_VERSION_MINOR << '.' << I3IPC_VERSION_PATCH;
+ v.loaded_config_file_name = std::string();
+ v.major = I3IPC_VERSION_MAJOR;
+ v.minor = I3IPC_VERSION_MINOR;
+ v.patch = I3IPC_VERSION_PATCH;
+ return v;
+}
+
+} // namespace i3ipc
--- /dev/null
+#include <iostream>
+
+#include <auss.hpp>
+
+#include "ipc-util.hpp"
+
+#include <cxxtest/TestSuite.h>
+
+class testsuite_ipc_util : public CxxTest::TestSuite {
+public:
+ void test_pack() {
+ {
+ using namespace i3ipc;
+ auto buff = i3_pack(ClientMessageType::COMMAND, "exit");
+ auss_t auss;
+ auss << std::hex;
+ for (uint32_t i = 0; i < buff->size; i++) {
+ if (buff->data[i] < 0x10) {
+ auss << '0';
+ }
+ auss << static_cast<uint32_t>(buff->data[i]) << ' ';
+ }
+ std::string str = auss;
+ str.pop_back();
+ TS_ASSERT_EQUALS(str, "69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 69 74")
+ }
+ }
+};
--- /dev/null
+build
+.clang_complete
+*.o
+*.swp
+*.gch
+*.d
+*.pyc
--- /dev/null
+cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
+project(xpp)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+
+#
+# Process dependencies
+#
+find_package(PkgConfig)
+pkg_check_modules(XCBPROTO REQUIRED xcb-proto)
+
+# Store the location of the proto xml files inside XCBPROTO_XCBINCLUDEDIR
+pkg_get_variable(XCBPROTO_XCBINCLUDEDIR xcb-proto xcbincludedir)
+
+# Search for a python interpreter, if the user didn't specify any
+if(NOT PYTHON_EXECUTABLE)
+ if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
+ find_package(PythonInterp 3.5 REQUIRED)
+ else()
+ find_package(Python3 REQUIRED COMPONENTS Interpreter)
+ if(Python3_FOUND)
+ set(PYTHON_EXECUTABLE "${Python3_EXECUTABLE}")
+ endif()
+ endif()
+endif()
+
+if(NOT PYTHON_EXECUTABLE)
+ message(FATAL_ERROR "Missing PYTHON_EXECUTABLE")
+endif()
+
+find_package(XCB REQUIRED XCB ICCCM EWMH UTIL IMAGE)
+
+set(GEN_SRC "${CMAKE_CURRENT_BINARY_DIR}/generated-sources")
+
+set(XPP_INCLUDE_DIRS
+ ${PROJECT_SOURCE_DIR}/include
+ ${GEN_SRC}/include
+ ${XCB_XCB_INCLUDE_DIR}
+ ${XCB_EWMH_INCLUDE_DIR}
+ ${XCB_ICCCM_INCLUDE_DIR}
+ ${XCB_UTIL_INCLUDE_DIR}
+ ${XCB_IMAGE_INCLUDE_DIR})
+set(XPP_LIBRARIES
+ ${XCB_XCB_LIBRARY}
+ ${XCB_EWMH_LIBRARY}
+ ${XCB_ICCCM_LIBRARY}
+ ${XCB_UTIL_LIBRARY}
+ ${XCB_IMAGE_LIBRARY})
+
+#
+# Loop through a hardcoded list of python executables to locate the python module "xcbgen"
+#
+# TODO drop python2 once ubuntu and debian ship python3-xcbgen in their
+# maintained distros
+foreach(CURRENT_EXECUTABLE ${PYTHON_EXECUTABLE} python3 python python2 python2.7)
+ message(STATUS "Searching for xcbgen with " ${CURRENT_EXECUTABLE})
+
+ execute_process(COMMAND "${CURRENT_EXECUTABLE}" "-c"
+ "import re,xcbgen;print(re.compile('/xcbgen/__init__.py.*').sub('',xcbgen.__file__))"
+ RESULT_VARIABLE _xcbgen_status
+ OUTPUT_VARIABLE _xcbgen_location
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+ # When a shell script returns successfully its return code is 0
+ if(_xcbgen_status EQUAL 0)
+ set(PYTHON_XCBGEN "${_xcbgen_location}" CACHE STRING "Location of python module: xcbgen ")
+ message(STATUS "Found xcbgen in " ${PYTHON_XCBGEN})
+ break()
+ endif()
+
+endforeach(CURRENT_EXECUTABLE)
+
+if(NOT PYTHON_XCBGEN)
+ message(FATAL_ERROR "Missing required python module: xcbgen")
+endif()
+
+#
+# Include XCB libs depending on what protos we build
+#
+if(NOT XCB_PROTOS)
+ set(XCB_PROTOS
+ "bigreq"
+ "composite"
+ "damage"
+ "dpms"
+ "dri2"
+ "dri3"
+ "glx"
+ "present"
+ "randr"
+ "record"
+ "render"
+ "res"
+ "screensaver"
+ "shape"
+ "shm"
+ "sync"
+ "xc_misc"
+ "xevie"
+ "xf86dri"
+ "xfixes"
+ "xinerama"
+ "xinput"
+ "xkb"
+ "xprint"
+ "xproto"
+ "xselinux"
+ "xtest"
+ "xv"
+ "xvmc")
+endif()
+
+if("randr" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED RANDR)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_RANDR_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_RANDR_LIBRARY})
+endif()
+if("render" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED RENDER)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_RENDER_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_RENDER_LIBRARY})
+endif()
+if("damage" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED DAMAGE)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_DAMAGE_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_DAMAGE_LIBRARY})
+endif()
+if("sync" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED SYNC)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_SYNC_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_SYNC_LIBRARY})
+endif()
+if("composite" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED COMPOSITE)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_COMPOSITE_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_COMPOSITE_LIBRARY})
+endif()
+if("xkb" IN_LIST XCB_PROTOS)
+ find_package(XCB REQUIRED XKB)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_XKB_INCLUDE_DIR})
+ set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_XKB_LIBRARY})
+endif()
+
+set(PROTO_LIST)
+
+file(GLOB PROTO_LIST_RAW RELATIVE ${XCBPROTO_XCBINCLUDEDIR} ${XCBPROTO_XCBINCLUDEDIR}/*.xml)
+
+#
+# Filter glob
+#
+foreach(PROTO_RAW ${PROTO_LIST_RAW})
+ string(REGEX REPLACE "(^xf86vidmode.xml|^ge.xml|.xml)\$" "" PROTO ${PROTO_RAW})
+ if(PROTO AND "${PROTO}" IN_LIST XCB_PROTOS)
+ message(STATUS "${PROJECT_NAME}: including xcb proto ${PROTO_RAW}")
+ set(PROTO_LIST ${PROTO_LIST} ${PROTO})
+ endif()
+endforeach(PROTO_RAW)
+
+#
+# Add commands
+#
+set(PROTO_HEADER_DIR "${GEN_SRC}/include/xpp/proto")
+file(MAKE_DIRECTORY ${PROTO_HEADER_DIR})
+set(PROTO_HEADER_FILES "")
+foreach(PROTO ${PROTO_LIST})
+ string(REGEX REPLACE "proto\$" "" PROTO_OUTPUT ${PROTO})
+ set(OUTPUT_FILE ${PROTO_HEADER_DIR}/${PROTO_OUTPUT}.hpp)
+ add_custom_command(
+ OUTPUT ${OUTPUT_FILE}
+ COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generators/cpp_client.py -p "${PYTHON_XCBGEN}"
+ ${XCBPROTO_XCBINCLUDEDIR}/${PROTO}.xml > ${OUTPUT_FILE})
+ list(APPEND PROTO_HEADER_FILES ${OUTPUT_FILE})
+endforeach(PROTO)
+
+#
+# Create project lib and commands
+#
+file(GLOB_RECURSE HEADER_FILES ${PROJECT_SOURCE_DIR}/include/*.hpp)
+add_library(${PROJECT_NAME} STATIC ${HEADER_FILES} ${PROTO_HEADER_FILES})
+
+target_include_directories(${PROJECT_NAME} PUBLIC ${XPP_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} PRIVATE ${XPP_LIBRARIES})
+
+target_compile_options(${PROJECT_NAME} PRIVATE -std=c++14 -Wall -Wpedantic)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Debug>:-g3 -DDEBUG>)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Release>:-O3 -Wno-unused-variable>)
+target_compile_options(${PROJECT_NAME} PUBLIC ${XCB_DEFINITIONS})
+
+set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+
+#
+# Export lists to the parent scope if there are any
+#
+get_directory_property(HAS_PARENT PARENT_DIRECTORY)
+
+if(HAS_PARENT)
+ set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} PARENT_SCOPE)
+ set(XPP_LIBRARIES ${PROJECT_NAME} PARENT_SCOPE)
+endif()
--- /dev/null
+Original work Copyright (c) 2018, Jochen Keil
+Modified work Copyright 2018, Michael Carlberg (jaagr) and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+# xpp - A C++11 RAII wrapper for XCB
+
+## Synopsis
+
+XPP is a header only C++11
+[RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
+wrapper around [X protocol C-language Binding
+(XCB)](http://xcb.freedesktop.org). Pointers to dynamically allocated memory,
+such as events and errors are wrapped in std::shared_ptr.
+
+Furthermore, interfaces for connection and resource types are provided to
+facilitate the creation of custom classes. For convenience, a connection class
+and several basic resource type classes are readily available.
+
+XPP makes widespread use of the
+[Curiously Recurring Template Pattern (CRTP)](https://en.wikibooks.org/wiki/More_C++_Idioms/Curiously_Recurring_Template_Pattern)
+to avoid overhead through dynamic dispatch. Hence, most interfaces are
+implicitly defined.
+
+## Prerequisites
+
+ * Python 3
+ * GCC >= 4.8 (or Clang >= 3.3, untested)
+ * libxcb
+
+## Quick Start
+
+```
+ git clone https://github.com/jrk-/xpp
+ cd xpp
+ make
+ make examples
+ cd src/examples
+ for demo in demo_*; do ./${demo}; done
+```
+
+## Documentation
+
+### General
+
+The bindings can be generated by calling `make` in the top level directory. If
+this fails, check the [`XCBGEN`](include/proto/Makefile#L38) and
+[`PROTODIR`](include/proto/Makefile#L39) variables in
+[include/proto/Makefile](include/proto/Makefile). These need to point to the `xcbgen`
+python package and the xml protocol description respectively.
+
+Once the bindings are generated they can be used by including
+[include/xpp.hpp](include/xpp.hpp). If an extensions is required, it needs to be
+included additionally. For example, the RandR extension is available through
+`proto/randr.hpp`, the Damage extension through `proto/damage.hpp`, etc.
+
+Recent (and working) examples can be found in [src/examples](src/examples).
+To compile them, call `make examples` in the `xpp` directory or just `make` in
+[src/examples](src/examples).
+
+### Requests
+
+Requests obey this naming scheme: `xpp:: ExtensionName :: RequestName`.
+
+##### Examples:
+
+Core X protocol:
+`MapWindow`: `xcb_map_window{,_checked}` -> `xpp::x::map_window{,_checked}`
+`InternAtom`: `xcb_intern_atom{,_checked}` -> `xpp::x::intern_atom{,_unchecked}`
+
+RandR protocol:
+`SelectInput`: `xcb_randr_select_input{,_checked}` -> `xpp::randr::select_input{,_checked}`
+`QueryVersion`: `xcb_randr_query_version{,_unchecked}` -> `xpp::randr::query_version{,_unchecked}`
+
+##### Default Parameter
+
+All `xcb_timestamp_t` parameters are alternatively available with a default
+value of `XCB_TIME_CURRENT_TIME`.
+
+##### Parameter Lists
+
+Requests which take a list of values as parameters can be used with any STL
+container by passing in Iterators. Example:
+
+```
+std::string string_example = "example string";
+// std::list<char> list_example = { 'a', 'b', 'c' };
+// std::map<int, char> map_example = { {0, 'a'}, {1, 'b'}, {2, 'c'} };
+xpp::x::change_property_checked(c, XCB_PROP_MODE_REPLACE, window,
+ atom, XCB_ATOM_STRING, 8,
+ string_example.begin(), string_example.end());
+ // list_example.begin(), list_example.end());
+ // for associative containers the value (std::pair<..>::second_type) will be used
+ // map_example.begin(), map_example.end());
+```
+
+### Replies
+
+XCB returns replies only when they are explicitely queried. With XPP this is not
+necessary anymore, because the operators for accessing the reply are overloaded.
+
+For example, getting the reply for the `InternAtom` request is as simple as this:
+
+```
+auto reply = xpp::x::intern_atom(connection, true, "MY_ATOM_NAME");
+// do some other stuff ..
+// latency hiding is still effective, because the call to
+// xcb_intern_atom_reply happens but now in operator->()
+xcb_atom_t atom = reply->atom;
+```
+
+#### Member Accessors
+
+##### Simple Types
+
+Primitive types like `xcb_window_t`, `xcb_atom_t`, etc. can be accessed either
+directly through the overloaded `operator->()` or via a method which has the
+same name as the member. These methods are templated with a default template
+type of the native type. Any type which is default constructible from the native
+type or a connection and the native type can be specified as template argument.
+
+Examples:
+
+```
+xcb_window_t w1 = reply->member;
+xcb_window_t w2 = reply.member(); // default template parameter is xcb_window_t
+xpp::window w3 = reply.member<xpp::window>();
+```
+
+##### List Types
+
+Lists (e.g. the result for `QueryTree`) are accessible through iterators. The
+value type is templated, with the default being the native data type.
+
+Example:
+
+```
+auto tree = xpp::x::query_tree(c, window);
+
+// default template type: xcb_window_t
+for (auto && child : tree.children()) {
+ // child has type xcb_window_t
+}
+
+// xpp::window is constructible with a connection and xcb_window_t
+// other types which are default-constructible with either the value type
+// (e.g. xcb_window_t) or a connection & the value type are possible, too
+for (auto && child : tree.children<xpp::window>()) {
+ // child has type xpp::window
+}
+```
+
+Caveat: Some requests (in particular `GetProperty`) return an untyped array of
+bytes (`void *`). To access the desired data type, a template type must be
+specified. For constructible types a type trait must be implemented, like so:
+
+```
+struct my_type {
+ my_type(const xcb_window_t &);
+ // ..
+};
+
+namespace xpp { namespace generic {
+struct traits<my_type> {
+ typedef xcb_atom_t type;
+};
+}; }; // namespace xpp::generic
+```
+
+### Errors
+
+XCB offers four different variants of request functions.
+
+##### Requests without a reply:
+
+* Error delivered through event queue: `xcb_void_cookie_t xcb_request(...)`
+
+* Error can be checked immediately with `xcb_request_check(xcb_connection_t *, xcb_void_cookie_t)`: `xcb_void_cookie_t xcb_request_checked(...)`
+
+##### Requests with reply:
+
+* Error can be checked when getting the reply:
+`xcb_request_reply_t * xcb_request_reply(xcb_connection_t *, xcb_request_cookie_t, xcb_generic_error_t **)`:
+`xcb_request_cookie_t xcb_request(...)`
+
+* Error delivered through event queue: `xcb_request_cookie_t xcb_request_unchecked(...)`
+
+For more information on this, refer to [xcb-requests (3)](http://www.x.org/releases/current/doc/man/man3/xcb-requests.3.xhtml).
+
+With xpp errors are either thrown as `std::shared_ptr<xcb_generic_error_t>` or
+typed as `xpp:: extension ::error:: error_type`, e.g. `xpp::x::error::value`.
+
+The latter are based upon `xpp::generic::error` (which inherits from
+`std::runtime_error`) and come with a textual error description which is
+accessible through the `what()` method.
+
+For typed errors it is necessary to use a connection class which implements the
+appropriate error dispatching. The supplied `xpp::connection` class already does
+this. If no error dispatcher are available (e.g. when used with
+`xcb_connection_t *`), then a simply `std::shared_ptr<xcb_generic_error_t>`
+will be thrown.
+
+### Events
+
+Events returned by the event producing methods (`wait_for_event`,
+`poll_for_event`, etc.) from `xpp::core` and `xpp::connection` are encapsulated
+as `std::shared_ptr<xcb_generic_event_t>`.
+
+For additional convenience typed events are available. An event type is based on
+`xpp::generic::event`. The general structure for a typed event is
+
+`xpp::` Extension `::event::` EventName
+
+Examples:
+
+```
+xpp::x::event::key_press
+xpp::randr::event::notify
+xpp::damage::event::notify
+```
+
+Events can be converted from `std::shared_ptr<xcb_generic_event_t>` to a typed
+event by either using an event dispatcher functor (e.g.
+`xpp::x::event::dispatcher`) or by using the event registry described below.
+
+##### Registry
+
+The event registry `xpp::event::registry<Connection, Extensions ...>` can be
+used to connect events and event handlers.
+
+First, a registry object for the desired `Connection` type and `Extensions` is
+necessary.
+
+Then, arbitrary objects, which implement the `xpp::event::sink<..>` interface
+need to be attached for event handling by calling the `attach()` method.
+It takes two parameters. The first one specifies the priority, in case there are
+more than one event handler for this event. Handlers with lower priorities are
+called first. The second one is a pointer to an object which implements the
+`xpp::event::sink<..>` interface.
+
+For a detailed example, take a look at this [demo](src/examples/demo_01.cpp).
+
+### Interfaces
+
+Interfaces for creating custom types are available.
+
+##### <a name="interface-connection"></a>Connection
+
+For every extension a "connection" interface, called
+`xpp:: ExtensionName ::interface<typename Derived, typename Connection>`
+is available.
+
+These encapsulate every request for a particular extension. The `Derived`
+template parameter specifies the class which wants to derive from the interface.
+The `Derived` class must provide a method `Connection connection();`.
+
+Examples:
+
+```
+xpp::x::interface<typename Derived, typename Connection>
+xpp::randr::interface<typename Derived, typename Connection>
+xpp::damage::interface<typename Derived, typename Connection>
+etc.
+```
+
+For a customizable default implementation, take a look at the `xpp::connection`
+class described [here](#default-type-connection).
+
+##### Resources
+
+In addition, interfaces for basic resource types like `xcb_window_t`,
+`xcb_atom_t`, `xcb_gcontext_t`, etc. are available.
+
+Again, the naming scheme follows the format
+`xpp:: ExtensionName :: XidType <typename Derived, typename Connection>`
+
+Despite the `connection()` method described [here](#interface-connection),
+`Derived` needs to implement a `resource()` method which returns a xid which
+will be passed as parameter to the encapsulated requests.
+
+Examples:
+
+```
+xpp::x::window<typename Derived, typename Connection>
+xpp::randr::output<typename Derived, typename Connection>
+xpp::render::glyphset<typename Derived, typename Connection>
+etc.
+```
+
+### Default Types
+
+##### <a name="default-type-connection"></a>Connection
+
+`xpp::connection<Extensions ...>` provides a default
+implementation of the [core connection methods](include/core.hpp), the core
+X protocol and error handling facilities. In addition, it is implicitly
+convertible to `xcb_connection_t *`, hence it can be used seamlessly with XCB
+functions. The connection can be augmented with additional extension methods, by
+specifying the desired extensions as template parameters.
+
+Example:
+
+`typedef xpp::connection<xpp::randr::extension, xpp::damage::extension> my_connection;`
+
+##### Resources
+
+For the basic resource types like `Drawable`, `Window`, `Pixmap`, `Atom`,
+`Colormap`, `Cursor`, `Font`, `Fontable` and `GContext` wrapper types exist.
+They are named `xpp::drawable`, `xpp::window`, etc.
+
+Each is based upon xpp::generic::resource and provides the core X protocol
+interface for the encapsulated resource type. If the resource can be acquired
+from the X server (e.g. with `CreateWindow`) then a named constructor is
+available (e.g. `create_window` for `xpp::window`).
+
+Resources acquired through the named constructors are reference counted. When
+their lifetime expires, the resource handle will automatically be freed on the
+server. No call to destroy or free functions is necessary.
--- /dev/null
+# Try to find XCB on a Unix system
+#
+# This will define:
+#
+# XCB_FOUND - True if xcb is available
+# XCB_LIBRARIES - Link these to use xcb
+# XCB_INCLUDE_DIRS - Include directory for xcb
+# XCB_DEFINITIONS - Compiler flags for using xcb
+#
+# In addition the following more fine grained variables will be defined:
+#
+# XCB_XCB_FOUND XCB_XCB_INCLUDE_DIR XCB_XCB_LIBRARY
+# XCB_UTIL_FOUND XCB_UTIL_INCLUDE_DIR XCB_UTIL_LIBRARY
+# XCB_COMPOSITE_FOUND XCB_COMPOSITE_INCLUDE_DIR XCB_COMPOSITE_LIBRARY
+# XCB_DAMAGE_FOUND XCB_DAMAGE_INCLUDE_DIR XCB_DAMAGE_LIBRARY
+# XCB_XFIXES_FOUND XCB_XFIXES_INCLUDE_DIR XCB_XFIXES_LIBRARY
+# XCB_RENDER_FOUND XCB_RENDER_INCLUDE_DIR XCB_RENDER_LIBRARY
+# XCB_RANDR_FOUND XCB_RANDR_INCLUDE_DIR XCB_RANDR_LIBRARY
+# XCB_SHAPE_FOUND XCB_SHAPE_INCLUDE_DIR XCB_SHAPE_LIBRARY
+# XCB_DRI2_FOUND XCB_DRI2_INCLUDE_DIR XCB_DRI2_LIBRARY
+# XCB_GLX_FOUND XCB_GLX_INCLUDE_DIR XCB_GLX_LIBRARY
+# XCB_SHM_FOUND XCB_SHM_INCLUDE_DIR XCB_SHM_LIBRARY
+# XCB_XV_FOUND XCB_XV_INCLUDE_DIR XCB_XV_LIBRARY
+# XCB_SYNC_FOUND XCB_SYNC_INCLUDE_DIR XCB_SYNC_LIBRARY
+# XCB_XTEST_FOUND XCB_XTEST_INCLUDE_DIR XCB_XTEST_LIBRARY
+# XCB_ICCCM_FOUND XCB_ICCCM_INCLUDE_DIR XCB_ICCCM_LIBRARY
+# XCB_EWMH_FOUND XCB_EWMH_INCLUDE_DIR XCB_EWMH_LIBRARY
+# XCB_IMAGE_FOUND XCB_IMAGE_INCLUDE_DIR XCB_IMAGE_LIBRARY
+# XCB_RENDERUTIL_FOUND XCB_RENDERUTIL_INCLUDE_DIR XCB_RENDERUTIL_LIBRARY
+# XCB_KEYSYMS_FOUND XCB_KEYSYMS_INCLUDE_DIR XCB_KEYSYMS_LIBRARY
+#
+# Copyright (c) 2011 Fredrik Höglund <fredrik@kde.org>
+# Copyright (c) 2013 Martin Gräßlin <mgraesslin@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+set(knownComponents XCB
+ COMPOSITE
+ DAMAGE
+ DRI2
+ EWMH
+ GLX
+ ICCCM
+ IMAGE
+ KEYSYMS
+ RANDR
+ RENDER
+ RENDERUTIL
+ SHAPE
+ SHM
+ SYNC
+ UTIL
+ XFIXES
+ XTEST
+ XV
+ XINERAMA
+ XKB)
+
+unset(unknownComponents)
+
+set(pkgConfigModules)
+set(requiredComponents)
+
+if (XCB_FIND_COMPONENTS)
+ set(comps ${XCB_FIND_COMPONENTS})
+else()
+ set(comps ${knownComponents})
+endif()
+
+# iterate through the list of requested components, and check that we know them all.
+# If not, fail.
+foreach(comp ${comps})
+ list(FIND knownComponents ${comp} index )
+ if("${index}" STREQUAL "-1")
+ list(APPEND unknownComponents "${comp}")
+ else()
+ if("${comp}" STREQUAL "XCB")
+ list(APPEND pkgConfigModules "xcb")
+ elseif("${comp}" STREQUAL "COMPOSITE")
+ list(APPEND pkgConfigModules "xcb-composite")
+ elseif("${comp}" STREQUAL "DAMAGE")
+ list(APPEND pkgConfigModules "xcb-damage")
+ elseif("${comp}" STREQUAL "DRI2")
+ list(APPEND pkgConfigModules "xcb-dri2")
+ elseif("${comp}" STREQUAL "EWMH")
+ list(APPEND pkgConfigModules "xcb-ewmh")
+ elseif("${comp}" STREQUAL "GLX")
+ list(APPEND pkgConfigModules "xcb-glx")
+ elseif("${comp}" STREQUAL "ICCCM")
+ list(APPEND pkgConfigModules "xcb-icccm")
+ elseif("${comp}" STREQUAL "IMAGE")
+ list(APPEND pkgConfigModules "xcb-image")
+ elseif("${comp}" STREQUAL "KEYSYMS")
+ list(APPEND pkgConfigModules "xcb-keysyms")
+ elseif("${comp}" STREQUAL "RANDR")
+ list(APPEND pkgConfigModules "xcb-randr")
+ elseif("${comp}" STREQUAL "RENDER")
+ list(APPEND pkgConfigModules "xcb-render")
+ elseif("${comp}" STREQUAL "RENDERUTIL")
+ list(APPEND pkgConfigModules "xcb-renderutil")
+ elseif("${comp}" STREQUAL "SHAPE")
+ list(APPEND pkgConfigModules "xcb-shape")
+ elseif("${comp}" STREQUAL "SHM")
+ list(APPEND pkgConfigModules "xcb-shm")
+ elseif("${comp}" STREQUAL "SYNC")
+ list(APPEND pkgConfigModules "xcb-sync")
+ elseif("${comp}" STREQUAL "UTIL")
+ list(APPEND pkgConfigModules "xcb-util")
+ elseif("${comp}" STREQUAL "XFIXES")
+ list(APPEND pkgConfigModules "xcb-xfixes")
+ elseif("${comp}" STREQUAL "XTEST")
+ list(APPEND pkgConfigModules "xcb-xtest")
+ elseif("${comp}" STREQUAL "XV")
+ list(APPEND pkgConfigModules "xcb-xv")
+ elseif("${comp}" STREQUAL "XINERAMA")
+ list(APPEND pkgConfigModules "xcb-xinerama")
+ elseif("${comp}" STREQUAL "XKB")
+ list(APPEND pkgConfigModules "xcb-xkb")
+ endif()
+ endif()
+endforeach()
+
+
+if(DEFINED unknownComponents)
+ set(msgType STATUS)
+ if(XCB_FIND_REQUIRED)
+ set(msgType FATAL_ERROR)
+ endif()
+ if(NOT XCB_FIND_QUIETLY)
+ message(${msgType} "XCB: requested unknown components ${unknownComponents}")
+ endif()
+ return()
+endif()
+
+macro(_xcb_handle_component _comp)
+ set(_header )
+ set(_lib )
+ if("${_comp}" STREQUAL "XCB")
+ set(_header "xcb/xcb.h")
+ set(_lib "xcb")
+ elseif("${_comp}" STREQUAL "COMPOSITE")
+ set(_header "xcb/composite.h")
+ set(_lib "xcb-composite")
+ elseif("${_comp}" STREQUAL "DAMAGE")
+ set(_header "xcb/damage.h")
+ set(_lib "xcb-damage")
+ elseif("${_comp}" STREQUAL "DRI2")
+ set(_header "xcb/dri2.h")
+ set(_lib "xcb-dri2")
+ elseif("${_comp}" STREQUAL "EWMH")
+ set(_header "xcb/xcb_ewmh.h")
+ set(_lib "xcb-ewmh")
+ elseif("${_comp}" STREQUAL "GLX")
+ set(_header "xcb/glx.h")
+ set(_lib "xcb-glx")
+ elseif("${_comp}" STREQUAL "ICCCM")
+ set(_header "xcb/xcb_icccm.h")
+ set(_lib "xcb-icccm")
+ elseif("${_comp}" STREQUAL "IMAGE")
+ set(_header "xcb/xcb_image.h")
+ set(_lib "xcb-image")
+ elseif("${_comp}" STREQUAL "KEYSYMS")
+ set(_header "xcb/xcb_keysyms.h")
+ set(_lib "xcb-keysyms")
+ elseif("${_comp}" STREQUAL "RANDR")
+ set(_header "xcb/randr.h")
+ set(_lib "xcb-randr")
+ elseif("${_comp}" STREQUAL "RENDER")
+ set(_header "xcb/render.h")
+ set(_lib "xcb-render")
+ elseif("${_comp}" STREQUAL "RENDERUTIL")
+ set(_header "xcb/xcb_renderutil.h")
+ set(_lib "xcb-render-util")
+ elseif("${_comp}" STREQUAL "SHAPE")
+ set(_header "xcb/shape.h")
+ set(_lib "xcb-shape")
+ elseif("${_comp}" STREQUAL "SHM")
+ set(_header "xcb/shm.h")
+ set(_lib "xcb-shm")
+ elseif("${_comp}" STREQUAL "SYNC")
+ set(_header "xcb/sync.h")
+ set(_lib "xcb-sync")
+ elseif("${_comp}" STREQUAL "UTIL")
+ set(_header "xcb/xcb_util.h")
+ set(_lib "xcb-util")
+ elseif("${_comp}" STREQUAL "XFIXES")
+ set(_header "xcb/xfixes.h")
+ set(_lib "xcb-xfixes")
+ elseif("${_comp}" STREQUAL "XTEST")
+ set(_header "xcb/xtest.h")
+ set(_lib "xcb-xtest")
+ elseif("${_comp}" STREQUAL "XV")
+ set(_header "xcb/xv.h")
+ set(_lib "xcb-xv")
+ elseif("${_comp}" STREQUAL "XINERAMA")
+ set(_header "xcb/xinerama.h")
+ set(_lib "xcb-xinerama")
+ elseif("${_comp}" STREQUAL "XKB")
+ set(_header "xcb/xkb.h")
+ set(_lib "xcb-xkb")
+ endif()
+
+ find_path(XCB_${_comp}_INCLUDE_DIR NAMES ${_header} HINTS ${PKG_XCB_INCLUDE_DIRS})
+ find_library(XCB_${_comp}_LIBRARY NAMES ${_lib} HINTS ${PKG_XCB_LIBRARY_DIRS})
+
+ if(XCB_${_comp}_INCLUDE_DIR AND XCB_${_comp}_LIBRARY)
+ list(APPEND XCB_INCLUDE_DIRS ${XCB_${_comp}_INCLUDE_DIR})
+ list(APPEND XCB_LIBRARIES ${XCB_${_comp}_LIBRARY})
+ if (NOT XCB_FIND_QUIETLY)
+ message(STATUS "XCB[${_comp}]: Found component ${_comp}")
+ endif()
+ endif()
+
+ if(XCB_FIND_REQUIRED_${_comp})
+ list(APPEND requiredComponents XCB_${_comp}_FOUND)
+ endif()
+
+ # Bypass developer warning that the first argument to find_package_handle_standard_args (XCB_...) does not match
+ # the name of the calling package (XCB)
+ # https://cmake.org/cmake/help/v3.17/module/FindPackageHandleStandardArgs.html
+ set(FPHSA_NAME_MISMATCHED TRUE)
+ find_package_handle_standard_args(XCB_${_comp} REQUIRED_VARS XCB_${_comp}_LIBRARY XCB_${_comp}_INCLUDE_DIR)
+
+ mark_as_advanced(XCB_${_comp}_LIBRARY XCB_${_comp}_INCLUDE_DIR)
+
+ # compatibility for old variable naming
+ set(XCB_${_comp}_INCLUDE_DIRS ${XCB_${_comp}_INCLUDE_DIR})
+ set(XCB_${_comp}_LIBRARIES ${XCB_${_comp}_LIBRARY})
+endmacro()
+
+IF (NOT WIN32)
+ include(FindPackageHandleStandardArgs)
+ # Use pkg-config to get the directories and then use these values
+ # in the FIND_PATH() and FIND_LIBRARY() calls
+ find_package(PkgConfig)
+ pkg_check_modules(PKG_XCB QUIET ${pkgConfigModules})
+
+ set(XCB_DEFINITIONS ${PKG_XCB_CFLAGS})
+
+ foreach(comp ${comps})
+ _xcb_handle_component(${comp})
+ endforeach()
+
+ if(XCB_INCLUDE_DIRS)
+ list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS)
+ endif()
+
+ find_package_handle_standard_args(XCB REQUIRED_VARS XCB_LIBRARIES XCB_INCLUDE_DIRS ${requiredComponents})
+
+ # compatibility for old variable naming
+ set(XCB_INCLUDE_DIR ${XCB_INCLUDE_DIRS})
+
+ENDIF (NOT WIN32)
--- /dev/null
+TODO:
+
+* Build with python 3
+
+* valueparams
+
+* serialized fields (e.g. xcb_sync_create_alarm_value_list_serialize)
+ (is this necessary?)
+
+* specialize iterator for non-vector data structures:
+ Instead of converting to vector, check if it is possible to send the data
+ directly through the socket (e.g. map { key, value }:
+ for (k,v : map) { socket_send(v); } ...
+
+* XInput Event handling: Am I doing this right? (Multiple switches etc.)
+
+* Adapter classes for drawable, window, pixmap, atom, font, etc.
+
+$ grep xidtype *.xml
+damage.xml: <xidtype name="DAMAGE" />
+glx.xml: <xidtype name="PIXMAP" />
+glx.xml: <xidtype name="CONTEXT" />
+glx.xml: <xidtype name="PBUFFER" />
+glx.xml: <xidtype name="WINDOW" />
+glx.xml: <xidtype name="FBCONFIG" />
+present.xml: <xidtype name="EVENT"/>
+randr.xml: <xidtype name="MODE" />
+randr.xml: <xidtype name="CRTC" />
+randr.xml: <xidtype name="OUTPUT" />
+randr.xml: <xidtype name="PROVIDER" />
+record.xml: <xidtype name="CONTEXT" />
+render.xml: <xidtype name="GLYPHSET" />
+render.xml: <xidtype name="PICTURE" />
+render.xml: <xidtype name="PICTFORMAT" />
+shm.xml: <xidtype name="SEG" />
+sync.xml: <xidtype name="ALARM" />
+sync.xml: <xidtype name="COUNTER" />
+sync.xml: <xidtype name="FENCE" />
+xfixes.xml: <xidtype name="REGION" />
+xfixes.xml: <xidtype name="BARRIER" />
+xprint.xml: <xidtype name="PCONTEXT" />
+xproto.xml: <xidtype name="WINDOW" />
+xproto.xml: <xidtype name="PIXMAP" />
+xproto.xml: <xidtype name="CURSOR" />
+xproto.xml: <xidtype name="FONT" />
+xproto.xml: <xidtype name="GCONTEXT" />
+xproto.xml: <xidtype name="COLORMAP" />
+xproto.xml: <xidtype name="ATOM" />
+xvmc.xml: <xidtype name="CONTEXT" />
+xvmc.xml: <xidtype name="SURFACE" />
+xvmc.xml: <xidtype name="SUBPICTURE" />
+xv.xml: <xidtype name="PORT" />
+xv.xml: <xidtype name="ENCODING" />
--- /dev/null
+from resource_classes import _resource_classes
+
+_templates = {}
+
+_templates['iter_fixed'] = \
+"""\
+xpp::generic::iterator<Connection,
+ %s,
+ SIGNATURE(%s_%s),
+ SIGNATURE(%s_%s_length)>\
+"""
+
+_templates['iter_variable'] = \
+"""\
+xpp::generic::iterator<Connection,
+ %s,
+ SIGNATURE(%s_next),
+ SIGNATURE(%s_sizeof),
+ SIGNATURE(%s_%s_iterator)>\
+"""
+
+_templates['list'] = \
+"""\
+ xpp::generic::list<Connection,
+ %s_reply_t,
+ %s
+ >
+ %s(void)
+ {
+ return xpp::generic::list<Connection,
+ %s_reply_t,
+ %s
+ >(%s);
+ }\
+"""
+
+_templates['string_accessor'] = \
+'''\
+ std::string
+ %s(void)
+ {
+ return std::string(%s_%s(this->get().get()),
+ %s_%s_length(this->get().get()));
+ }
+'''
+
+def _string_accessor(member, c_name):
+ return _templates['string_accessor'] % \
+ (member, c_name, member, c_name, member)
+
+class Accessor(object):
+ def __init__(self, is_fixed=False, is_string=False, is_variable=False, \
+ member="", c_type="", return_type="", iter_name="", c_name=""):
+
+ self.is_fixed = is_fixed
+ self.is_string = is_string
+ self.is_variable = is_variable
+
+ self.member = member
+ self.c_type = c_type
+ self.return_type = return_type
+ self.iter_name = iter_name
+ self.c_name = c_name
+
+ self.object_type = self.c_type.replace("xcb_", "").replace("_t", "").upper()
+
+ if self.c_type == "void":
+ self.return_type = "Type"
+ elif self.object_type in _resource_classes:
+ self.return_type = self.member.capitalize()
+ else:
+ self.return_type = self.c_type
+
+ def __str__(self):
+ if self.is_fixed:
+ return self.list(self.iter_fixed())
+ elif self.is_variable:
+ return self.list(self.iter_variable())
+ elif self.is_string:
+ return self.string()
+ else:
+ return ""
+
+
+ def iter_fixed(self):
+ return_type = self.return_type
+
+ return _templates['iter_fixed'] \
+ % (return_type,
+ self.c_name, self.member,
+ self.c_name, self.member)
+
+
+ def iter_variable(self):
+ return _templates['iter_variable'] \
+ % (self.c_type,
+ self.iter_name,
+ self.iter_name,
+ self.c_name, self.member)
+
+
+ def list(self, iterator):
+ template = " template<typename Type" if self.c_type == "void" else ""
+
+ # template<typename Children = xcb_window_t>
+ if self.object_type in _resource_classes:
+ template += ", " if template != "" else " template<typename "
+ template += self.member.capitalize() + " = " + self.c_type
+
+ template += ">\n" if template != "" else ""
+
+ c_tor_params = "this->m_c, this->get()"
+
+ fst_iterator = "\n ".join(iterator.split('\n'))
+ snd_iterator = "\n ".join(iterator.split('\n'))
+
+ return template + _templates['list'] \
+ % (self.c_name,
+ fst_iterator,
+ self.member,
+ self.c_name,
+ snd_iterator,
+ c_tor_params)
+
+ def string(self):
+ return _string_accessor(self.member, self.c_name)
--- /dev/null
+#!/usr/bin/env python
+# vim: set ts=4 sw=4:
+
+from xml.etree.ElementTree import *
+from os.path import basename
+from functools import reduce
+import getopt
+import os
+import sys
+import errno
+import time
+import re
+import collections
+
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext
+
+from cppevent import CppEvent
+from cpperror import CppError
+from accessor import Accessor
+from parameter import Parameter
+from cpprequest import CppRequest
+from objectclass import ObjectClass
+from interfaceclass import InterfaceClass
+from extensionclass import ExtensionClass
+from resource_classes import _resource_classes
+
+_cpp_request_names = []
+_cpp_request_objects = {}
+
+# see c_open()
+_interface_class = InterfaceClass()
+
+_cpp_events = []
+_cpp_errors = []
+
+_object_classes = {}
+
+for i, v in enumerate(_resource_classes):
+ _object_classes[i] = ObjectClass(v)
+
+ # , "render" : collections.OrderedDict( \
+ # {
+ # } )
+ # , "xinerama" : collections.OrderedDict( \
+ # {
+ # } )
+
+# Jump to the bottom of this file for the main routine
+
+# Some hacks to make the API more readable, and to keep backwards compability
+_cname_re = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
+_cname_special_cases = {'DECnet':'decnet'}
+
+_extension_special_cases = ['XPrint', 'XCMisc', 'BigRequests']
+
+_xcb_includes = \
+ { "xproto" : "xcb.h"
+ }
+
+
+_cplusplus_annoyances = {'class' : '_class',
+ 'new' : '_new',
+ 'delete': '_delete',
+ 'explicit': '_explicit'}
+_c_keywords = {'default' : '_default'}
+
+_hlines = []
+_hlevel = 0
+_clines = []
+_clevel = 0
+_ns = None
+
+# global variable to keep track of serializers and
+# switch data types due to weird dependencies
+finished_serializers = []
+finished_sizeof = []
+finished_switch = []
+
+# keeps enum objects so that we can refer to them when generating manpages.
+enums = {}
+
+manpaths = False
+
+def _get_xcb_include(ns):
+ # return _xcb_includes.get(ns, _ns.ext_name.lower()) + ".h"
+ return _xcb_includes.get(ns, _ns.file.replace(".xml", ".h"))
+
+# def get_namespace(_ns):
+# if _ns.is_ext:
+# return get_ext_name(_ns.ext_name)
+# else:
+# return "x"
+
+def _h(fmt, *args):
+ '''
+ Writes the given line to the header file.
+ '''
+ _hlines[_hlevel].append(fmt % args)
+
+def _c(fmt, *args):
+ '''
+ Writes the given line to the source file.
+ '''
+ _clines[_clevel].append(fmt % args)
+
+def _hc(fmt, *args):
+ '''
+ Writes the given line to both the header and source files.
+ '''
+ _h(fmt, *args)
+ _c(fmt, *args)
+
+# XXX See if this level thing is really necessary.
+def _h_setlevel(idx):
+ '''
+ Changes the array that header lines are written to.
+ Supports writing different sections of the header file.
+ '''
+ global _hlevel
+ while len(_hlines) <= idx:
+ _hlines.append([])
+ _hlevel = idx
+
+def _c_setlevel(idx):
+ '''
+ Changes the array that source lines are written to.
+ Supports writing to different sections of the source file.
+ '''
+ global _clevel
+ while len(_clines) <= idx:
+ _clines.append([])
+ _clevel = idx
+
+
+def _cpp(str):
+ '''
+ Checks for certain C++ reserved words and fixes them.
+ '''
+ if str in _cplusplus_annoyances:
+ return _cplusplus_annoyances[str]
+ elif str in _c_keywords:
+ return _c_keywords[str]
+ else:
+ return str
+
+
+def _n(list):
+ '''
+ Does C-name conversion on a tuple of strings.
+ Different behavior depending on length of tuple, extension/not extension, etc.
+ Basically C-name converts the individual pieces, then joins with underscores.
+ '''
+ if len(list) == 1:
+ parts = list
+ elif len(list) == 2:
+ parts = [list[0], _n_item(list[1])]
+ elif _ns.is_ext:
+ parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]]
+ else:
+ parts = [list[0]] + [_n_item(i) for i in list[1:]]
+ return '_'.join(parts).lower()
+
+def _t(list):
+ '''
+ Does C-name conversion on a tuple of strings representing a type.
+ Same as _n but adds a "_t" on the end.
+ '''
+ if len(list) == 1:
+ parts = list
+ elif len(list) == 2:
+ parts = [list[0], _n_item(list[1]), 't']
+ elif _ns.is_ext:
+ parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]] + ['t']
+ else:
+ parts = [list[0]] + [_n_item(i) for i in list[1:]] + ['t']
+ return '_'.join(parts).lower()
+
+
+def c_open(self):
+ '''
+ Exported function that handles module open.
+ Opens the files and writes out the auto-generated comment,
+ header file includes, etc.
+ '''
+ global _ns
+ _ns = self.namespace
+ # _ns.header = "test"
+ _ns.c_ext_global_name = _n(_ns.prefix + ('id',))
+
+ _interface_class.set_namespace(_ns)
+
+ # Build the type-name collision avoidance table used by c_enum
+ build_collision_table()
+
+ _h_setlevel(0)
+ _c_setlevel(0)
+
+ # _h('#ifndef EXPORT_%s_MIXINS', get_namespace(_ns).upper())
+ _h('#ifndef XPP_%s_HPP', get_namespace(_ns).upper())
+ _h('#define XPP_%s_HPP', get_namespace(_ns).upper())
+ _h('')
+ _h('#include <string>')
+ _h('#include <vector>')
+ _h('')
+
+ _h('#include <xcb/' + _get_xcb_include(_ns.header.lower()) + '>')
+
+ _h('')
+
+ _h('#include "xpp/generic.hpp"')
+
+ # if not _ns.is_ext:
+ # _h('#include "xproto-stub.hpp"')
+ # _h('#include "../core/generic/resource.hpp"')
+ _h('')
+ _h('namespace xpp { namespace %s {' % get_namespace(_ns))
+ # _h('class window;')
+ # _h('namespace %s {', get_namespace(_ns))
+ # _h('')
+
+def c_close(self):
+ '''
+ Exported function that handles module close.
+ Writes out all the stored content lines, then closes the files.
+ '''
+
+ _h('')
+ _h(ExtensionClass(_ns).make_class())
+
+
+ for cpp_event in _cpp_events:
+ _h(cpp_event.make_class())
+
+ _h('')
+
+ for cpp_error in _cpp_errors:
+ _h(cpp_error.make_class())
+
+ _h('')
+
+ for name in _cpp_request_names:
+ _h("%s", _cpp_request_objects[name].make_class())
+
+ _h('')
+
+ for key in _object_classes:
+ _h(_object_classes[key].make_inline())
+ # sys.stderr.write(_object_classes[key].make_inline())
+
+
+
+ _h('')
+ _h(_interface_class.make_proto())
+ # sys.stderr.write(_interface_class.make_proto())
+
+ _h('')
+ # _h('}; // namespace xpp')
+ _h("} } // namespace xpp::%s" % get_namespace(_ns))
+
+ _h('')
+ _h('#endif // XPP_%s_HPP', get_namespace(_ns).upper())
+
+ # Write header file
+ hfile = sys.stdout
+ for list in _hlines:
+ for line in list:
+ hfile.write(line)
+ hfile.write('\n')
+ # hfile.close()
+
+ # cfile = sys.stderr
+ # for list in _clines:
+ # for line in list:
+ # cfile.write(line)
+ # cfile.write('\n')
+
+def build_collision_table():
+ global namecount
+ namecount = {}
+
+ for v in list(module.types.values()):
+ name = _t(v[0])
+ namecount[name] = (namecount.get(name) or 0) + 1
+
+def c_enum(self, name):
+ '''
+ Exported function that handles enum declarations.
+ '''
+
+ enums[name] = self
+
+ tname = _t(name)
+ if namecount[tname] > 1:
+ tname = _t(name + ('enum',))
+
+ _h_setlevel(0)
+ _h('')
+ _h('typedef enum %s {', tname)
+
+ count = len(self.values)
+
+ for (enam, eval) in self.values:
+ count = count - 1
+ equals = ' = ' if eval != '' else ''
+ comma = ',' if count > 0 else ''
+ doc = ''
+ if hasattr(self, "doc") and self.doc and enam in self.doc.fields:
+ doc = '\n/**< %s */\n' % self.doc.fields[enam]
+ _h(' %s%s%s%s%s', _n(name + (enam,)).upper(), equals, eval, comma, doc)
+
+ _h('} %s;', tname)
+
+def _c_type_setup(self, name, postfix):
+ '''
+ Sets up all the C-related state by adding additional data fields to
+ all Field and Type objects. Here is where we figure out most of our
+ variable and function names.
+
+ Recurses into child fields and list member types.
+ '''
+ # Do all the various names in advance
+
+ self.c_type = _t(name + postfix)
+ self.c_wiretype = 'char' if self.c_type == 'void' else self.c_type
+
+ self.c_iterator_type = _t(name + ('iterator',))
+ self.c_next_name = _n(name + ('next',))
+ self.c_end_name = _n(name + ('end',))
+
+ self.c_request_name = _n(name)
+ self.c_checked_name = _n(name)
+ self.c_unchecked_name = _n(name)
+ # self.c_checked_name = _n(name + ('checked',))
+ # self.c_unchecked_name = _n(name + ('unchecked',))
+ self.c_reply_name = _n(name + ('reply',))
+ self.c_reply_type = _t(name + ('reply',))
+ self.c_cookie_type = _t(name + ('cookie',))
+ self.c_reply_fds_name = _n(name + ('reply_fds',))
+
+ self.need_aux = False
+ self.need_serialize = False
+ self.need_sizeof = False
+
+ self.c_aux_name = _n(name + ('aux',))
+ self.c_aux_checked_name = _n(name)
+ self.c_aux_unchecked_name = _n(name)
+ # self.c_aux_checked_name = _n(name + ('aux', 'checked'))
+ # self.c_aux_unchecked_name = _n(name + ('aux', 'unchecked'))
+ self.c_serialize_name = _n(name + ('serialize',))
+ self.c_unserialize_name = _n(name + ('unserialize',))
+ self.c_unpack_name = _n(name + ('unpack',))
+ self.c_sizeof_name = _n(name + ('sizeof',))
+
+ # special case: structs where variable size fields are followed by fixed size fields
+ self.var_followed_by_fixed_fields = False
+
+ if self.is_switch:
+ self.need_serialize = True
+ self.c_container = 'struct'
+ for bitcase in self.bitcases:
+ bitcase.c_field_name = _cpp(bitcase.field_name)
+ bitcase_name = bitcase.field_type if bitcase.type.has_name else name
+ _c_type_setup(bitcase.type, bitcase_name, ())
+
+ elif self.is_container:
+
+ self.c_container = 'union' if self.is_union else 'struct'
+ prev_varsized_field = None
+ prev_varsized_offset = 0
+ first_field_after_varsized = None
+
+ for field in self.fields:
+ _c_type_setup(field.type, field.field_type, ())
+ if field.type.is_list:
+ _c_type_setup(field.type.member, field.field_type, ())
+ if (field.type.nmemb is None):
+ self.need_sizeof = True
+
+ field.c_field_type = _t(field.field_type)
+ field.c_field_const_type = ('' if field.type.nmemb == 1 else 'const ') + field.c_field_type
+ field.c_field_name = _cpp(field.field_name)
+ field.c_subscript = '[%d]' % field.type.nmemb if (field.type.nmemb and field.type.nmemb > 1) else ''
+ field.c_pointer = ' ' if field.type.nmemb == 1 else '*'
+
+ # correct the c_pointer field for variable size non-list types
+ if not field.type.fixed_size() and field.c_pointer == ' ':
+ field.c_pointer = '*'
+ if field.type.is_list and not field.type.member.fixed_size():
+ field.c_pointer = '*'
+
+ if field.type.is_switch:
+ field.c_pointer = '*'
+ field.c_field_const_type = 'const ' + field.c_field_type
+ self.need_aux = True
+ elif not field.type.fixed_size() and not field.type.is_bitcase:
+ self.need_sizeof = True
+
+ field.c_iterator_type = _t(field.field_type + ('iterator',)) # xcb_fieldtype_iterator_t
+ field.c_iterator_name = _n(name + (field.field_name, 'iterator')) # xcb_container_field_iterator
+ field.c_accessor_name = _n(name + (field.field_name,)) # xcb_container_field
+ field.c_length_name = _n(name + (field.field_name, 'length')) # xcb_container_field_length
+ field.c_end_name = _n(name + (field.field_name, 'end')) # xcb_container_field_end
+
+ field.prev_varsized_field = prev_varsized_field
+ field.prev_varsized_offset = prev_varsized_offset
+
+ if prev_varsized_offset == 0:
+ first_field_after_varsized = field
+ field.first_field_after_varsized = first_field_after_varsized
+
+ if field.type.fixed_size():
+ prev_varsized_offset += field.type.size
+ # special case: intermixed fixed and variable size fields
+ if prev_varsized_field is not None and not field.type.is_pad and field.wire:
+ if not self.is_union:
+ self.need_serialize = True
+ self.var_followed_by_fixed_fields = True
+ else:
+ self.last_varsized_field = field
+ prev_varsized_field = field
+ prev_varsized_offset = 0
+
+ if self.var_followed_by_fixed_fields:
+ if field.type.fixed_size():
+ field.prev_varsized_field = None
+
+ if self.need_serialize:
+ # when _unserialize() is wanted, create _sizeof() as well for consistency reasons
+ self.need_sizeof = True
+
+ # as switch does never appear at toplevel,
+ # continue here with type construction
+ if self.is_switch:
+ if self.c_type not in finished_switch:
+ finished_switch.append(self.c_type)
+ # special: switch C structs get pointer fields for variable-sized members
+ _c_complex(self)
+ for bitcase in self.bitcases:
+ bitcase_name = bitcase.type.name if bitcase.type.has_name else name
+ _c_accessors(bitcase.type, bitcase_name, bitcase_name)
+ # no list with switch as element, so no call to
+ # _c_iterator(field.type, field_name) necessary
+
+ if not self.is_bitcase:
+ if self.need_serialize:
+ if self.c_serialize_name not in finished_serializers:
+ finished_serializers.append(self.c_serialize_name)
+ _c_serialize('serialize', self)
+
+ # _unpack() and _unserialize() are only needed for special cases:
+ # switch -> unpack
+ # special cases -> unserialize
+ if self.is_switch or self.var_followed_by_fixed_fields:
+ _c_serialize('unserialize', self)
+
+ # if self.need_sizeof:
+ # if self.c_sizeof_name not in finished_sizeof:
+ # if not module.namespace.is_ext or self.name[:2] == module.namespace.prefix:
+ # finished_sizeof.append(self.c_sizeof_name)
+ # _c_serialize('sizeof', self)
+# _c_type_setup()
+
+def _c_helper_absolute_name(prefix, field=None):
+ """
+ turn prefix, which is a list of tuples (name, separator, Type obj) into a string
+ representing a valid name in C (based on the context)
+ if field is not None, append the field name as well
+ """
+ prefix_str = ''
+ for name, sep, obj in prefix:
+ prefix_str += name
+ if '' == sep:
+ sep = '->'
+ if ((obj.is_bitcase and obj.has_name) or # named bitcase
+ (obj.is_switch and len(obj.parents)>1)):
+ sep = '.'
+ prefix_str += sep
+ if field is not None:
+ prefix_str += _cpp(field.field_name)
+ return prefix_str
+# _c_absolute_name
+
+def _c_helper_field_mapping(complex_type, prefix, flat=False):
+ """
+ generate absolute names, based on prefix, for all fields starting from complex_type
+ if flat == True, nested complex types are not taken into account
+ """
+ all_fields = {}
+ if complex_type.is_switch:
+ for b in complex_type.bitcases:
+ if b.type.has_name:
+ switch_name, switch_sep, switch_type = prefix[-1]
+ bitcase_prefix = prefix + [(b.type.name[-1], '.', b.type)]
+ else:
+ bitcase_prefix = prefix
+
+ if (True==flat and not b.type.has_name) or False==flat:
+ all_fields.update(_c_helper_field_mapping(b.type, bitcase_prefix, flat))
+ else:
+ for f in complex_type.fields:
+ fname = _c_helper_absolute_name(prefix, f)
+ if f.field_name in all_fields:
+ raise Exception("field name %s has been registered before" % f.field_name)
+
+ all_fields[f.field_name] = (fname, f)
+ if f.type.is_container and flat==False:
+ if f.type.is_bitcase and not f.type.has_name:
+ new_prefix = prefix
+ elif f.type.is_switch and len(f.type.parents)>1:
+ # nested switch gets another separator
+ new_prefix = prefix+[(f.c_field_name, '.', f.type)]
+ else:
+ new_prefix = prefix+[(f.c_field_name, '->', f.type)]
+ all_fields.update(_c_helper_field_mapping(f.type, new_prefix, flat))
+
+ return all_fields
+# _c_field_mapping()
+
+def _c_helper_resolve_field_names (prefix):
+ """
+ get field names for all objects in the prefix array
+ """
+ all_fields = {}
+ tmp_prefix = []
+ # look for fields in the remaining containers
+ for idx, p in enumerate(prefix):
+ name, sep, obj = p
+ if ''==sep:
+ # sep can be preset in prefix, if not, make a sensible guess
+ sep = '.' if (obj.is_switch or obj.is_bitcase) else '->'
+ # exception: 'toplevel' object (switch as well!) always have sep '->'
+ sep = '->' if idx<1 else sep
+ if not obj.is_bitcase or (obj.is_bitcase and obj.has_name):
+ tmp_prefix.append((name, sep, obj))
+ all_fields.update(_c_helper_field_mapping(obj, tmp_prefix, flat=True))
+
+ return all_fields
+# _c_helper_resolve_field_names
+
+def get_expr_fields(self):
+ """
+ get the Fields referenced by switch or list expression
+ """
+ def get_expr_field_names(expr):
+ if expr.op is None:
+ if expr.lenfield_name is not None:
+ return [expr.lenfield_name]
+ else:
+ # constant value expr
+ return []
+ else:
+ if expr.op == '~':
+ return get_expr_field_names(expr.rhs)
+ elif expr.op == 'popcount':
+ return get_expr_field_names(expr.rhs)
+ elif expr.op == 'sumof':
+ # sumof expr references another list,
+ # we need that list's length field here
+ field = None
+ for f in expr.lenfield_parent.fields:
+ if f.field_name == expr.lenfield_name:
+ field = f
+ break
+ if field is None:
+ raise Exception("list field '%s' referenced by sumof not found" % expr.lenfield_name)
+ # referenced list + its length field
+ return [expr.lenfield_name] + get_expr_field_names(field.type.expr)
+ elif expr.op == 'enumref':
+ return []
+ else:
+ return get_expr_field_names(expr.lhs) + get_expr_field_names(expr.rhs)
+ # get_expr_field_names()
+
+ # resolve the field names with the parent structure(s)
+ unresolved_fields_names = get_expr_field_names(self.expr)
+
+ # construct prefix from self
+ prefix = [('', '', p) for p in self.parents]
+ if self.is_container:
+ prefix.append(('', '', self))
+
+ all_fields = _c_helper_resolve_field_names (prefix)
+ resolved_fields_names = [x for x in unresolved_fields_names if x in list(all_fields.keys())]
+ if len(unresolved_fields_names) != len(resolved_fields_names):
+ raise Exception("could not resolve all fields for %s" % self.name)
+
+ resolved_fields = [all_fields[n][1] for n in resolved_fields_names]
+ return resolved_fields
+# get_expr_fields()
+
+def resolve_expr_fields(complex_obj):
+ """
+ find expr fields appearing in complex_obj and descendents that cannot be resolved within complex_obj
+ these are normally fields that need to be given as function parameters
+ """
+ all_fields = []
+ expr_fields = []
+ unresolved = []
+
+ for field in complex_obj.fields:
+ all_fields.append(field)
+ if field.type.is_switch or field.type.is_list:
+ expr_fields += get_expr_fields(field.type)
+ if field.type.is_container:
+ expr_fields += resolve_expr_fields(field.type)
+
+ # try to resolve expr fields
+ for e in expr_fields:
+ if e not in all_fields and e not in unresolved:
+ unresolved.append(e)
+ return unresolved
+# resolve_expr_fields()
+
+def get_serialize_params(context, self, buffer_var='_buffer', aux_var='_aux'):
+ """
+ functions like _serialize(), _unserialize(), and _unpack() sometimes need additional parameters:
+ E.g. in order to unpack switch, extra parameters might be needed to evaluate the switch
+ expression. This function tries to resolve all fields within a structure, and returns the
+ unresolved fields as the list of external parameters.
+ """
+ def add_param(params, param):
+ if param not in params:
+ params.append(param)
+
+ # collect all fields into param_fields
+ param_fields = []
+ wire_fields = []
+
+ for field in self.fields:
+ if field.visible:
+ # the field should appear as a parameter in the function call
+ param_fields.append(field)
+ if field.wire and not field.auto:
+ if field.type.fixed_size() and not self.is_switch:
+ # field in the xcb_out structure
+ wire_fields.append(field)
+ # fields like 'pad0' are skipped!
+
+ # in case of switch, parameters always contain any fields referenced in the switch expr
+ # we do not need any variable size fields here, as the switch data type contains both
+ # fixed and variable size fields
+ if self.is_switch:
+ param_fields = get_expr_fields(self)
+
+ # _serialize()/_unserialize()/_unpack() function parameters
+ # note: don't use set() for params, it is unsorted
+ params = []
+
+ # 1. the parameter for the void * buffer
+ if 'serialize' == context:
+ params.append(('void', '**', buffer_var))
+ elif context in ('unserialize', 'unpack', 'sizeof'):
+ params.append(('const void', '*', buffer_var))
+
+ # 2. any expr fields that cannot be resolved within self and descendants
+ unresolved_fields = resolve_expr_fields(self)
+ for f in unresolved_fields:
+ add_param(params, (f.c_field_type, '', f.c_field_name))
+
+ # 3. param_fields contain the fields necessary to evaluate the switch expr or any other fields
+ # that do not appear in the data type struct
+ for p in param_fields:
+ if self.is_switch:
+ typespec = p.c_field_const_type
+ pointerspec = p.c_pointer
+ add_param(params, (typespec, pointerspec, p.c_field_name))
+ else:
+ if p.visible and not p.wire and not p.auto:
+ typespec = p.c_field_type
+ pointerspec = ''
+ add_param(params, (typespec, pointerspec, p.c_field_name))
+
+ # 4. aux argument
+ if 'serialize' == context:
+ add_param(params, ('const %s' % self.c_type, '*', aux_var))
+ elif 'unserialize' == context:
+ add_param(params, ('%s' % self.c_type, '**', aux_var))
+ elif 'unpack' == context:
+ add_param(params, ('%s' % self.c_type, '*', aux_var))
+
+ # 5. switch contains all variable size fields as struct members
+ # for other data types though, these have to be supplied separately
+ # this is important for the special case of intermixed fixed and
+ # variable size fields
+ if not self.is_switch and 'serialize' == context:
+ for p in param_fields:
+ if not p.type.fixed_size():
+ add_param(params, (p.c_field_const_type, '*', p.c_field_name))
+
+ return (param_fields, wire_fields, params)
+# get_serialize_params()
+
+def _c_serialize_helper_insert_padding(context, code_lines, space, postpone):
+ code_lines.append('%s /* insert padding */' % space)
+ code_lines.append('%s xcb_pad = -xcb_block_len & (xcb_align_to - 1);' % space)
+# code_lines.append('%s printf("automatically inserting padding: %%%%d\\n", xcb_pad);' % space)
+ code_lines.append('%s xcb_buffer_len += xcb_block_len + xcb_pad;' % space)
+
+ if not postpone:
+ code_lines.append('%s if (0 != xcb_pad) {' % space)
+
+ if 'serialize' == context:
+ code_lines.append('%s xcb_parts[xcb_parts_idx].iov_base = xcb_pad0;' % space)
+ code_lines.append('%s xcb_parts[xcb_parts_idx].iov_len = xcb_pad;' % space)
+ code_lines.append('%s xcb_parts_idx++;' % space)
+ elif context in ('unserialize', 'unpack', 'sizeof'):
+ code_lines.append('%s xcb_tmp += xcb_pad;' % space)
+
+ code_lines.append('%s xcb_pad = 0;' % space)
+ code_lines.append('%s }' % space)
+
+ code_lines.append('%s xcb_block_len = 0;' % space)
+
+ # keep tracking of xcb_parts entries for serialize
+ return 1
+# _c_serialize_helper_insert_padding()
+
+def _c_serialize_helper_switch(context, self, complex_name,
+ code_lines, temp_vars,
+ space, prefix):
+ count = 0
+ switch_expr = _c_accessor_get_expr(self.expr, None)
+
+ for b in self.bitcases:
+ len_expr = len(b.type.expr)
+ for n, expr in enumerate(b.type.expr):
+ bitcase_expr = _c_accessor_get_expr(expr, None)
+ # only one <enumref> in the <bitcase>
+ if len_expr == 1:
+ code_lines.append(' if(%s & %s) {' % (switch_expr, bitcase_expr))
+ # multiple <enumref> in the <bitcase>
+ elif n == 0: # first
+ code_lines.append(' if((%s & %s) ||' % (switch_expr, bitcase_expr))
+ elif len_expr == (n + 1): # last
+ code_lines.append(' (%s & %s)) {' % (switch_expr, bitcase_expr))
+ else: # between first and last
+ code_lines.append(' (%s & %s) ||' % (switch_expr, bitcase_expr))
+
+ b_prefix = prefix
+ if b.type.has_name:
+ b_prefix = prefix + [(b.c_field_name, '.', b.type)]
+
+ count += _c_serialize_helper_fields(context, b.type,
+ code_lines, temp_vars,
+ "%s " % space,
+ b_prefix,
+ is_bitcase = True)
+ code_lines.append(' }')
+
+# if 'serialize' == context:
+# count += _c_serialize_helper_insert_padding(context, code_lines, space, False)
+# elif context in ('unserialize', 'unpack', 'sizeof'):
+# # padding
+# code_lines.append('%s xcb_pad = -xcb_block_len & 3;' % space)
+# code_lines.append('%s xcb_buffer_len += xcb_block_len + xcb_pad;' % space)
+
+ return count
+# _c_serialize_helper_switch
+
+def _c_serialize_helper_switch_field(context, self, field, c_switch_variable, prefix):
+ """
+ handle switch by calling _serialize() or _unpack(), depending on context
+ """
+ # switch is handled by this function as a special case
+ param_fields, wire_fields, params = get_serialize_params(context, self)
+ field_mapping = _c_helper_field_mapping(self, prefix)
+ prefix_str = _c_helper_absolute_name(prefix)
+
+ # find the parameters that need to be passed to _serialize()/_unpack():
+ # all switch expr fields must be given as parameters
+ args = get_expr_fields(field.type)
+ # length fields for variable size types in switch, normally only some of need
+ # need to be passed as parameters
+ switch_len_fields = resolve_expr_fields(field.type)
+
+ # a switch field at this point _must_ be a bitcase field
+ # we require that bitcases are "self-contiguous"
+ bitcase_unresolved = resolve_expr_fields(self)
+ if len(bitcase_unresolved) != 0:
+ raise Exception('unresolved fields within bitcase is not supported at this point')
+
+ # get the C names for the parameters
+ c_field_names = ''
+ for a in switch_len_fields:
+ c_field_names += "%s, " % field_mapping[a.c_field_name][0]
+ for a in args:
+ c_field_names += "%s, " % field_mapping[a.c_field_name][0]
+
+ # call _serialize()/_unpack() to determine the actual size
+ if 'serialize' == context:
+ length = "%s(&%s, %s&%s%s)" % (field.type.c_serialize_name, c_switch_variable,
+ c_field_names, prefix_str, field.c_field_name)
+ elif context in ('unserialize', 'unpack'):
+ length = "%s(xcb_tmp, %s&%s%s)" % (field.type.c_unpack_name,
+ c_field_names, prefix_str, field.c_field_name)
+
+ return length
+# _c_serialize_helper_switch_field()
+
+def _c_serialize_helper_list_field(context, self, field,
+ code_lines, temp_vars,
+ space, prefix):
+ """
+ helper function to cope with lists of variable length
+ """
+ expr = field.type.expr
+ prefix_str = _c_helper_absolute_name(prefix)
+ param_fields, wire_fields, params = get_serialize_params('sizeof', self)
+ param_names = [p[2] for p in params]
+
+ expr_fields_names = [f.field_name for f in get_expr_fields(field.type)]
+ resolved = [x for x in expr_fields_names if x in param_names]
+ unresolved = [x for x in expr_fields_names if x not in param_names]
+
+ field_mapping = {}
+ for r in resolved:
+ field_mapping[r] = (r, None)
+
+ if len(unresolved)>0:
+ tmp_prefix = prefix
+ if len(tmp_prefix)==0:
+ raise Exception("found an empty prefix while resolving expr field names for list %s",
+ field.c_field_name)
+
+ field_mapping.update(_c_helper_resolve_field_names(prefix))
+ resolved += [x for x in unresolved if x in field_mapping]
+ unresolved = [x for x in unresolved if x not in field_mapping]
+ if len(unresolved)>0:
+ raise Exception('could not resolve the length fields required for list %s' % field.c_field_name)
+
+ list_length = _c_accessor_get_expr(expr, field_mapping)
+
+ # default: list with fixed size elements
+ length = '%s * sizeof(%s)' % (list_length, field.type.member.c_wiretype)
+
+ # list with variable-sized elements
+ if not field.type.member.fixed_size():
+ length = ''
+ if context in ('unserialize', 'sizeof', 'unpack'):
+ int_i = ' unsigned int i;'
+ xcb_tmp_len = ' unsigned int xcb_tmp_len;'
+ if int_i not in temp_vars:
+ temp_vars.append(int_i)
+ if xcb_tmp_len not in temp_vars:
+ temp_vars.append(xcb_tmp_len)
+ # loop over all list elements and call sizeof repeatedly
+ # this should be a bit faster than using the iterators
+ code_lines.append("%s for(i=0; i<%s; i++) {" % (space, list_length))
+ code_lines.append("%s xcb_tmp_len = %s(xcb_tmp);" %
+ (space, field.type.c_sizeof_name))
+ code_lines.append("%s xcb_block_len += xcb_tmp_len;" % space)
+ code_lines.append("%s xcb_tmp += xcb_tmp_len;" % space)
+ code_lines.append("%s }" % space)
+
+ elif 'serialize' == context:
+ code_lines.append('%s xcb_parts[xcb_parts_idx].iov_len = 0;' % space)
+ code_lines.append('%s xcb_tmp = (char *) %s%s;' % (space, prefix_str, field.c_field_name))
+ code_lines.append('%s for(i=0; i<%s; i++) { ' % (space, list_length))
+ code_lines.append('%s xcb_block_len = %s(xcb_tmp);' % (space, field.type.c_sizeof_name))
+ code_lines.append('%s xcb_parts[xcb_parts_idx].iov_len += xcb_block_len;' % space)
+ code_lines.append('%s }' % space)
+ code_lines.append('%s xcb_block_len = xcb_parts[xcb_parts_idx].iov_len;' % space)
+
+ return length
+# _c_serialize_helper_list_field()
+
+def _c_serialize_helper_fields_fixed_size(context, self, field,
+ code_lines, temp_vars,
+ space, prefix):
+ # keep the C code a bit more readable by giving the field name
+ if not self.is_bitcase:
+ code_lines.append('%s /* %s.%s */' % (space, self.c_type, field.c_field_name))
+ else:
+ scoped_name = [p[2].c_type if idx==0 else p[0] for idx, p in enumerate(prefix)]
+ typename = reduce(lambda x,y: "%s.%s" % (x, y), scoped_name)
+ code_lines.append('%s /* %s.%s */' % (space, typename, field.c_field_name))
+
+ abs_field_name = _c_helper_absolute_name(prefix, field)
+ # default for simple cases: call sizeof()
+ length = "sizeof(%s)" % field.c_field_type
+
+ if context in ('unserialize', 'unpack', 'sizeof'):
+ # default: simple cast
+ value = ' %s = *(%s *)xcb_tmp;' % (abs_field_name, field.c_field_type)
+
+ # padding - we could probably just ignore it
+ if field.type.is_pad and field.type.nmemb > 1:
+ value = ''
+ for i in range(field.type.nmemb):
+ code_lines.append('%s %s[%d] = *(%s *)xcb_tmp;' %
+ (space, abs_field_name, i, field.c_field_type))
+ # total padding = sizeof(pad0) * nmemb
+ length += " * %d" % field.type.nmemb
+
+ if field.type.is_list:
+ # no such case in the protocol, cannot be tested and therefore ignored for now
+ raise Exception('list with fixed number of elemens unhandled in _unserialize()')
+
+ elif 'serialize' == context:
+ value = ' xcb_parts[xcb_parts_idx].iov_base = (char *) '
+
+ if field.type.is_expr:
+ # need to register a temporary variable for the expression in case we know its type
+ if field.type.c_type is None:
+ raise Exception("type for field '%s' (expression '%s') unkown" %
+ (field.field_name, _c_accessor_get_expr(field.type.expr, prefix)))
+
+ temp_vars.append(' %s xcb_expr_%s = %s;' % (field.type.c_type, _cpp(field.field_name),
+ _c_accessor_get_expr(field.type.expr, prefix)))
+ value += "&xcb_expr_%s;" % _cpp(field.field_name)
+
+ elif field.type.is_pad:
+ if field.type.nmemb == 1:
+ value += "&xcb_pad;"
+ else:
+ # we could also set it to 0, see definition of xcb_send_request()
+ value = ' xcb_parts[xcb_parts_idx].iov_base = xcb_pad0;'
+ length += "*%d" % field.type.nmemb
+
+ else:
+ # non-list type with fixed size
+ if field.type.nmemb == 1:
+ value += "&%s;" % (abs_field_name)
+
+ # list with nmemb (fixed size) elements
+ else:
+ value += '%s;' % (abs_field_name)
+ length = '%d' % field.type.nmemb
+
+ return (value, length)
+# _c_serialize_helper_fields_fixed_size()
+
+def _c_serialize_helper_fields_variable_size(context, self, field,
+ code_lines, temp_vars,
+ space, prefix):
+ prefix_str = _c_helper_absolute_name(prefix)
+
+ if context in ('unserialize', 'unpack', 'sizeof'):
+ value = ''
+ var_field_name = 'xcb_tmp'
+
+ # special case: intermixed fixed and variable size fields
+ if self.var_followed_by_fixed_fields and 'unserialize' == context:
+ value = ' %s = (%s *)xcb_tmp;' % (field.c_field_name, field.c_field_type)
+ temp_vars.append(' %s *%s;' % (field.type.c_type, field.c_field_name))
+ # special case: switch
+ if 'unpack' == context:
+ value = ' %s%s = (%s *)xcb_tmp;' % (prefix_str, field.c_field_name, field.c_field_type)
+
+ elif 'serialize' == context:
+ # variable size fields appear as parameters to _serialize() if the
+ # 'toplevel' container is not a switch
+ prefix_string = prefix_str if prefix[0][2].is_switch else ''
+ var_field_name = "%s%s" % (prefix_string, field.c_field_name)
+ value = ' xcb_parts[xcb_parts_idx].iov_base = (char *) %s;' % var_field_name
+
+ length = ''
+
+ code_lines.append('%s /* %s */' % (space, field.c_field_name))
+
+ if field.type.is_list:
+ if value != '':
+ # in any context, list is already a pointer, so the default assignment is ok
+ code_lines.append("%s%s" % (space, value))
+ value = ''
+ length = _c_serialize_helper_list_field(context, self, field,
+ code_lines, temp_vars,
+ space, prefix)
+
+ elif field.type.is_switch:
+ value = ''
+ if context == 'serialize':
+ # the _serialize() function allocates the correct amount memory if given a NULL pointer
+ value = ' xcb_parts[xcb_parts_idx].iov_base = (char *)0;'
+ length = _c_serialize_helper_switch_field(context, self, field,
+ 'xcb_parts[xcb_parts_idx].iov_base',
+ prefix)
+
+ else:
+ # in all remaining special cases - call _sizeof()
+ length = "%s(%s)" % (field.type.c_sizeof_name, var_field_name)
+
+ return (value, length)
+# _c_serialize_helper_fields_variable_size
+
+def _c_serialize_helper_fields(context, self,
+ code_lines, temp_vars,
+ space, prefix, is_bitcase):
+ count = 0
+ need_padding = False
+ prev_field_was_variable = False
+
+ for field in self.fields:
+ if not field.visible:
+ if not ((field.wire and not field.auto) or 'unserialize' == context):
+ continue
+
+ # switch/bitcase: fixed size fields must be considered explicitly
+ if field.type.fixed_size():
+ if self.is_bitcase or self.var_followed_by_fixed_fields:
+ if prev_field_was_variable and need_padding:
+ # insert padding
+# count += _c_serialize_helper_insert_padding(context, code_lines, space,
+# self.var_followed_by_fixed_fields)
+ prev_field_was_variable = False
+
+ # prefix for fixed size fields
+ fixed_prefix = prefix
+
+ value, length = _c_serialize_helper_fields_fixed_size(context, self, field,
+ code_lines, temp_vars,
+ space, fixed_prefix)
+ else:
+ continue
+
+ # fields with variable size
+ else:
+ # switch/bitcase: always calculate padding before and after variable sized fields
+ if need_padding or is_bitcase:
+ count += _c_serialize_helper_insert_padding(context, code_lines, space,
+ self.var_followed_by_fixed_fields)
+
+ value, length = _c_serialize_helper_fields_variable_size(context, self, field,
+ code_lines, temp_vars,
+ space, prefix)
+ prev_field_was_variable = True
+
+ # save (un)serialization C code
+ if '' != value:
+ code_lines.append('%s%s' % (space, value))
+
+ if field.type.fixed_size():
+ if is_bitcase or self.var_followed_by_fixed_fields:
+ # keep track of (un)serialized object's size
+ code_lines.append('%s xcb_block_len += %s;' % (space, length))
+ if context in ('unserialize', 'unpack', 'sizeof'):
+ code_lines.append('%s xcb_tmp += %s;' % (space, length))
+ else:
+ # variable size objects or bitcase:
+ # value & length might have been inserted earlier for special cases
+ if '' != length:
+ # special case: intermixed fixed and variable size fields
+ if (not field.type.fixed_size() and
+ self.var_followed_by_fixed_fields and 'unserialize' == context):
+ temp_vars.append(' int %s_len;' % field.c_field_name)
+ code_lines.append('%s %s_len = %s;' % (space, field.c_field_name, length))
+ code_lines.append('%s xcb_block_len += %s_len;' % (space, field.c_field_name))
+ code_lines.append('%s xcb_tmp += %s_len;' % (space, field.c_field_name))
+ else:
+ code_lines.append('%s xcb_block_len += %s;' % (space, length))
+ # increase pointer into the byte stream accordingly
+ if context in ('unserialize', 'sizeof', 'unpack'):
+ code_lines.append('%s xcb_tmp += xcb_block_len;' % space)
+
+ if 'serialize' == context:
+ if '' != length:
+ code_lines.append('%s xcb_parts[xcb_parts_idx].iov_len = %s;' % (space, length))
+ code_lines.append('%s xcb_parts_idx++;' % space)
+ count += 1
+
+ code_lines.append('%s xcb_align_to = ALIGNOF(%s);' % (space, 'char' if field.c_field_type == 'void' else field.c_field_type))
+
+ need_padding = True
+ if self.var_followed_by_fixed_fields:
+ need_padding = False
+
+ return count
+# _c_serialize_helper_fields()
+
+def _c_serialize_helper(context, complex_type,
+ code_lines, temp_vars,
+ space='', prefix=[]):
+ # count tracks the number of fields to serialize
+ count = 0
+
+ if hasattr(complex_type, 'type'):
+ self = complex_type.type
+ complex_name = complex_type.name
+ else:
+ self = complex_type
+ if self.var_followed_by_fixed_fields and 'unserialize' == context:
+ complex_name = 'xcb_out'
+ else:
+ complex_name = '_aux'
+
+ # special case: switch is serialized by evaluating each bitcase separately
+ if self.is_switch:
+ count += _c_serialize_helper_switch(context, self, complex_name,
+ code_lines, temp_vars,
+ space, prefix)
+
+ # all other data types can be evaluated one field a time
+ else:
+ # unserialize & fixed size fields: simply cast the buffer to the respective xcb_out type
+ if context in ('unserialize', 'unpack', 'sizeof') and not self.var_followed_by_fixed_fields:
+ code_lines.append('%s xcb_block_len += sizeof(%s);' % (space, self.c_type))
+ code_lines.append('%s xcb_tmp += xcb_block_len;' % space)
+ code_lines.append('%s xcb_buffer_len += xcb_block_len;' % space)
+ code_lines.append('%s xcb_block_len = 0;' % space)
+
+ count += _c_serialize_helper_fields(context, self,
+ code_lines, temp_vars,
+ space, prefix, False)
+ # "final padding"
+ count += _c_serialize_helper_insert_padding(context, code_lines, space, False)
+
+ return count
+# _c_serialize_helper()
+
+def _c_serialize(context, self):
+ """
+ depending on the context variable, generate _serialize(), _unserialize(), _unpack(), or _sizeof()
+ for the ComplexType variable self
+ """
+ _h_setlevel(1)
+ _c_setlevel(1)
+
+ # _hc('')
+ # # _serialize() returns the buffer size
+ # _hc('int')
+
+ # sys.stderr.write('int')
+
+
+ if self.is_switch and 'unserialize' == context:
+ context = 'unpack'
+
+ cases = { 'serialize' : self.c_serialize_name,
+ 'unserialize' : self.c_unserialize_name,
+ 'unpack' : self.c_unpack_name,
+ 'sizeof' : self.c_sizeof_name }
+ func_name = cases[context]
+
+ param_fields, wire_fields, params = get_serialize_params(context, self)
+ variable_size_fields = 0
+ # maximum space required for type definition of function arguments
+ maxtypelen = 0
+
+ # determine N(variable_fields)
+ for field in param_fields:
+ # if self.is_switch, treat all fields as if they are variable sized
+ if not field.type.fixed_size() or self.is_switch:
+ variable_size_fields += 1
+ # determine maxtypelen
+ for p in params:
+ maxtypelen = max(maxtypelen, len(p[0]) + len(p[1]))
+
+ # write to .c/.h
+ indent = ' '*(len(func_name)+2)
+ param_str = []
+ for p in params:
+ typespec, pointerspec, field_name = p
+ spacing = ' '*(maxtypelen-len(typespec)-len(pointerspec))
+ param_str.append("%s%s%s %s%s /**< */" % (indent, typespec, spacing, pointerspec, field_name))
+ # insert function name
+ param_str[0] = "%s (%s" % (func_name, param_str[0].strip())
+ param_str = ["%s," % x for x in param_str]
+
+ # >>> THIS! <<< #
+ # for s in param_str[:-1]:
+ # sys.stderr.write(s)
+
+ # _hc(s)
+ # _h("%s);" % param_str[-1].rstrip(','))
+
+ # >>> AND THAT! <<< #
+ # sys.stderr.write("%s);" % param_str[-1].rstrip(','))
+ # >>> AND THAT! <<< #
+ _c("%s)" % param_str[-1].rstrip(','))
+ _c('{')
+
+ code_lines = []
+ temp_vars = []
+ prefix = []
+
+ if 'serialize' == context:
+ if not self.is_switch and not self.var_followed_by_fixed_fields:
+ _c(' %s *xcb_out = *_buffer;', self.c_type)
+ _c(' unsigned int xcb_out_pad = -sizeof(%s) & 3;', self.c_type)
+ _c(' unsigned int xcb_buffer_len = sizeof(%s) + xcb_out_pad;', self.c_type)
+ _c(' unsigned int xcb_align_to = 0;')
+ else:
+ _c(' char *xcb_out = *_buffer;')
+ _c(' unsigned int xcb_buffer_len = 0;')
+ _c(' unsigned int xcb_align_to = 0;')
+ prefix = [('_aux', '->', self)]
+ aux_ptr = 'xcb_out'
+
+ elif context in ('unserialize', 'unpack'):
+ _c(' char *xcb_tmp = (char *)_buffer;')
+ if not self.is_switch:
+ if not self.var_followed_by_fixed_fields:
+ _c(' const %s *_aux = (%s *)_buffer;', self.c_type, self.c_type)
+ prefix = [('_aux', '->', self)]
+ else:
+ _c(' %s xcb_out;', self.c_type)
+ prefix = [('xcb_out', '.', self)]
+ else:
+ aux_var = '_aux' # default for unpack: single pointer
+ # note: unserialize not generated for switch
+ if 'unserialize' == context:
+ aux_var = '(*_aux)' # unserialize: double pointer (!)
+ prefix = [(aux_var, '->', self)]
+ aux_ptr = '*_aux'
+ _c(' unsigned int xcb_buffer_len = 0;')
+ _c(' unsigned int xcb_block_len = 0;')
+ _c(' unsigned int xcb_pad = 0;')
+ _c(' unsigned int xcb_align_to = 0;')
+
+ elif 'sizeof' == context:
+ param_names = [p[2] for p in params]
+ if self.is_switch:
+ # switch: call _unpack()
+ _c(' %s _aux;', self.c_type)
+ _c(' return %s(%s, &_aux);', self.c_unpack_name, reduce(lambda x,y: "%s, %s" % (x, y), param_names))
+ _c('}')
+ return
+ elif self.var_followed_by_fixed_fields:
+ # special case: call _unserialize()
+ _c(' return %s(%s, NULL);', self.c_unserialize_name, reduce(lambda x,y: "%s, %s" % (x, y), param_names))
+ _c('}')
+ return
+ else:
+ _c(' char *xcb_tmp = (char *)_buffer;')
+ prefix = [('_aux', '->', self)]
+
+ count = _c_serialize_helper(context, self, code_lines, temp_vars, prefix=prefix)
+ # update variable size fields (only important for context=='serialize'
+ variable_size_fields = count
+ if 'serialize' == context:
+ temp_vars.append(' unsigned int xcb_pad = 0;')
+ temp_vars.append(' char xcb_pad0[3] = {0, 0, 0};')
+ temp_vars.append(' struct iovec xcb_parts[%d];' % count)
+ temp_vars.append(' unsigned int xcb_parts_idx = 0;')
+ temp_vars.append(' unsigned int xcb_block_len = 0;')
+ temp_vars.append(' unsigned int i;')
+ temp_vars.append(' char *xcb_tmp;')
+ elif 'sizeof' == context:
+ # neither switch nor intermixed fixed and variable size fields:
+ # evaluate parameters directly
+ if not (self.is_switch or self.var_followed_by_fixed_fields):
+
+ # look if we have to declare an '_aux' variable at all
+ if len([x for x in code_lines if x.find('_aux')!=-1])>0:
+ if not self.var_followed_by_fixed_fields:
+ _c(' const %s *_aux = (%s *)_buffer;', self.c_type, self.c_type)
+ else:
+ _c(' %s *_aux = malloc(sizeof(%s));', self.c_type, self.c_type)
+
+ _c(' unsigned int xcb_buffer_len = 0;')
+ _c(' unsigned int xcb_block_len = 0;')
+ _c(' unsigned int xcb_pad = 0;')
+ _c(' unsigned int xcb_align_to = 0;')
+
+ _c('')
+ for t in temp_vars:
+ _c(t)
+ _c('')
+ for l in code_lines:
+ _c(l)
+
+ # variable sized fields have been collected, now
+ # allocate memory and copy everything into a continuous memory area
+ # note: this is not necessary in case of unpack
+ if context in ('serialize', 'unserialize'):
+ # unserialize: check for sizeof-only invocation
+ if 'unserialize' == context:
+ _c('')
+ _c(' if (NULL == _aux)')
+ _c(' return xcb_buffer_len;')
+
+ _c('')
+ _c(' if (NULL == %s) {', aux_ptr)
+ _c(' /* allocate memory */')
+ _c(' %s = malloc(xcb_buffer_len);', aux_ptr)
+ if 'serialize' == context:
+ _c(' *_buffer = xcb_out;')
+ _c(' }')
+ _c('')
+
+ # serialize: handle variable size fields in a loop
+ if 'serialize' == context:
+ if not self.is_switch and not self.var_followed_by_fixed_fields:
+ if len(wire_fields)>0:
+ _c(' *xcb_out = *_aux;')
+ # copy variable size fields into the buffer
+ if variable_size_fields > 0:
+ # xcb_out padding
+ if not self.is_switch and not self.var_followed_by_fixed_fields:
+ _c(' xcb_tmp = (char*)++xcb_out;')
+ _c(' xcb_tmp += xcb_out_pad;')
+ else:
+ _c(' xcb_tmp = xcb_out;')
+
+ # variable sized fields
+ _c(' for(i=0; i<xcb_parts_idx; i++) {')
+ _c(' if (0 != xcb_parts[i].iov_base && 0 != xcb_parts[i].iov_len)')
+ _c(' memcpy(xcb_tmp, xcb_parts[i].iov_base, xcb_parts[i].iov_len);')
+ _c(' if (0 != xcb_parts[i].iov_len)')
+ _c(' xcb_tmp += xcb_parts[i].iov_len;')
+ _c(' }')
+
+ # unserialize: assign variable size fields individually
+ if 'unserialize' == context:
+ _c(' xcb_tmp = ((char *)*_aux)+xcb_buffer_len;')
+ param_fields.reverse()
+ for field in param_fields:
+ if not field.type.fixed_size():
+ _c(' xcb_tmp -= %s_len;', field.c_field_name)
+ _c(' memmove(xcb_tmp, %s, %s_len);', field.c_field_name, field.c_field_name)
+ _c(' *%s = xcb_out;', aux_ptr)
+
+ _c('')
+ _c(' return xcb_buffer_len;')
+ _c('}')
+# _c_serialize()
+
+def _c_iterator_get_end(field, accum):
+ '''
+ Figures out what C code is needed to find the end of a variable-length structure field.
+ For nested structures, recurses into its last variable-sized field.
+ For lists, calls the end function
+ '''
+ if field.type.is_container:
+ accum = field.c_accessor_name + '(' + accum + ')'
+ return _c_iterator_get_end(field.type.last_varsized_field, accum)
+ if field.type.is_list:
+ # XXX we can always use the first way
+ if field.type.member.is_simple:
+ return field.c_end_name + '(' + accum + ')'
+ else:
+ return field.type.member.c_end_name + '(' + field.c_iterator_name + '(' + accum + '))'
+
+def _c_iterator(self, name):
+ '''
+ Declares the iterator structure and next/end functions for a given type.
+ '''
+ _h_setlevel(0)
+ _h('')
+ _h('/**')
+ _h(' * @brief %s', self.c_iterator_type)
+ _h(' **/')
+ _h('typedef struct %s {', self.c_iterator_type)
+ _h(' %s *data; /**< */', self.c_type)
+ _h(' int%s rem; /**< */', ' ' * (len(self.c_type) - 2))
+ _h(' int%s index; /**< */', ' ' * (len(self.c_type) - 2))
+ _h('} %s;', self.c_iterator_type)
+
+ _h_setlevel(1)
+ _c_setlevel(1)
+ _h('')
+ _h('/**')
+ _h(' * Get the next element of the iterator')
+ _h(' * @param i Pointer to a %s', self.c_iterator_type)
+ _h(' *')
+ _h(' * Get the next element in the iterator. The member rem is')
+ _h(' * decreased by one. The member data points to the next')
+ _h(' * element. The member index is increased by sizeof(%s)', self.c_type)
+ _h(' */')
+ _c('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' **')
+ _hc(' ** void %s', self.c_next_name)
+ _hc(' ** ')
+ _hc(' ** @param %s *i', self.c_iterator_type)
+ _hc(' ** @returns void')
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ _hc(' ')
+ _hc('void')
+ _h('%s (%s *i /**< */);', self.c_next_name, self.c_iterator_type)
+ _c('%s (%s *i /**< */)', self.c_next_name, self.c_iterator_type)
+ _c('{')
+
+ if not self.fixed_size():
+ _c(' %s *R = i->data;', self.c_type)
+
+ if self.is_union:
+ # FIXME - how to determine the size of a variable size union??
+ _c(' /* FIXME - determine the size of the union %s */', self.c_type)
+ else:
+ if self.need_sizeof:
+ _c(' xcb_generic_iterator_t child;')
+ _c(' child.data = (%s *)(((char *)R) + %s(R));',
+ self.c_type, self.c_sizeof_name)
+ _c(' i->index = (char *) child.data - (char *) i->data;')
+ else:
+ _c(' xcb_generic_iterator_t child = %s;', _c_iterator_get_end(self.last_varsized_field, 'R'))
+ _c(' i->index = child.index;')
+ _c(' --i->rem;')
+ _c(' i->data = (%s *) child.data;', self.c_type)
+
+ else:
+ _c(' --i->rem;')
+ _c(' ++i->data;')
+ _c(' i->index += sizeof(%s);', self.c_type)
+
+ _c('}')
+
+ _h('')
+ _h('/**')
+ _h(' * Return the iterator pointing to the last element')
+ _h(' * @param i An %s', self.c_iterator_type)
+ _h(' * @return The iterator pointing to the last element')
+ _h(' *')
+ _h(' * Set the current element in the iterator to the last element.')
+ _h(' * The member rem is set to 0. The member data points to the')
+ _h(' * last element.')
+ _h(' */')
+ _c('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' **')
+ _hc(' ** xcb_generic_iterator_t %s', self.c_end_name)
+ _hc(' ** ')
+ _hc(' ** @param %s i', self.c_iterator_type)
+ _hc(' ** @returns xcb_generic_iterator_t')
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ _hc(' ')
+ _hc('xcb_generic_iterator_t')
+ _h('%s (%s i /**< */);', self.c_end_name, self.c_iterator_type)
+ _c('%s (%s i /**< */)', self.c_end_name, self.c_iterator_type)
+ _c('{')
+ _c(' xcb_generic_iterator_t ret;')
+
+ if self.fixed_size():
+ _c(' ret.data = i.data + i.rem;')
+ _c(' ret.index = i.index + ((char *) ret.data - (char *) i.data);')
+ _c(' ret.rem = 0;')
+ else:
+ _c(' while(i.rem > 0)')
+ _c(' %s(&i);', self.c_next_name)
+ _c(' ret.data = i.data;')
+ _c(' ret.rem = i.rem;')
+ _c(' ret.index = i.index;')
+
+ _c(' return ret;')
+ _c('}')
+
+def _c_accessor_get_length(expr, field_mapping=None):
+ '''
+ Figures out what C code is needed to get a length field.
+ The field_mapping parameter can be used to change the absolute name of a length field.
+ For fields that follow a variable-length field, use the accessor.
+ Otherwise, just reference the structure field directly.
+ '''
+
+ lenfield_name = expr.lenfield_name
+ if lenfield_name is not None:
+ if field_mapping is not None and lenfield_name in field_mapping:
+ lenfield_name = field_mapping[lenfield_name][0]
+
+ if expr.lenfield is not None and expr.lenfield.prev_varsized_field is not None:
+ # special case: variable and fixed size fields are intermixed
+ # if the lenfield is among the fixed size fields, there is no need
+ # to call a special accessor function like <expr.lenfield.c_accessor_name + '(' + prefix + ')'>
+ return field_mapping(expr.lenfield_name)
+ elif expr.lenfield_name is not None:
+ return lenfield_name
+ else:
+ return str(expr.nmemb)
+
+def _c_accessor_get_expr(expr, field_mapping):
+ '''
+ Figures out what C code is needed to get the length of a list field.
+ The field_mapping parameter can be used to change the absolute name of a length field.
+ Recurses for math operations.
+ Returns bitcount for value-mask fields.
+ Otherwise, uses the value of the length field.
+ '''
+ lenexp = _c_accessor_get_length(expr, field_mapping)
+
+ if expr.op == '~':
+ return '(' + '~' + _c_accessor_get_expr(expr.rhs, field_mapping) + ')'
+ elif expr.op == 'popcount':
+ return 'xcb_popcount(' + _c_accessor_get_expr(expr.rhs, field_mapping) + ')'
+ elif expr.op == 'enumref':
+ enum_name = expr.lenfield_type.name
+ constant_name = expr.lenfield_name
+ c_name = _n(enum_name + (constant_name,)).upper()
+ return c_name
+ elif expr.op == 'sumof':
+ # locate the referenced list object
+ list_obj = expr.lenfield_type
+ field = None
+ for f in expr.lenfield_parent.fields:
+ if f.field_name == expr.lenfield_name:
+ field = f
+ break
+
+ if field is None:
+ raise Exception("list field '%s' referenced by sumof not found" % expr.lenfield_name)
+ list_name = field_mapping[field.c_field_name][0]
+ c_length_func = "%s(%s)" % (field.c_length_name, list_name)
+ # note: xcb_sumof() has only been defined for integers
+ c_length_func = _c_accessor_get_expr(field.type.expr, field_mapping)
+ return 'xcb_sumof(%s, %s)' % (list_name, c_length_func)
+ elif expr.op != None:
+ return ('(' + _c_accessor_get_expr(expr.lhs, field_mapping) +
+ ' ' + expr.op + ' ' +
+ _c_accessor_get_expr(expr.rhs, field_mapping) + ')')
+ elif expr.bitfield:
+ return 'xcb_popcount(' + lenexp + ')'
+ else:
+ return lenexp
+
+def type_pad_type(type):
+ if type == 'void':
+ return 'char'
+ return type
+
+def _c_accessors_field(self, field):
+ '''
+ Declares the accessor functions for a non-list field that follows a variable-length field.
+ '''
+ c_type = self.c_type
+
+ # special case: switch
+ switch_obj = self if self.is_switch else None
+ if self.is_bitcase:
+ switch_obj = self.parents[-1]
+ if switch_obj is not None:
+ c_type = switch_obj.c_type
+
+ if field.type.is_simple:
+ _hc('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' ** ')
+ _hc(' ** %s %s', field.c_field_type, field.c_accessor_name)
+ _hc(' ** ')
+ _hc(' ** @param const %s *R', c_type)
+ _hc(' ** @returns %s', field.c_field_type)
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ _hc(' ')
+ _hc('%s', field.c_field_type)
+ _h('%s (const %s *R /**< */);', field.c_accessor_name, c_type)
+ _c('%s (const %s *R /**< */)', field.c_accessor_name, c_type)
+ _c('{')
+ if field.prev_varsized_field is None:
+ _c(' return (%s *) (R + 1);', field.c_field_type)
+ else:
+ _c(' xcb_generic_iterator_t prev = %s;', _c_iterator_get_end(field.prev_varsized_field, 'R'))
+ _c(' return * (%s *) ((char *) prev.data + XCB_TYPE_PAD(%s, prev.index) + %d);',
+ field.c_field_type, type_pad_type(field.first_field_after_varsized.type.c_type), field.prev_varsized_offset)
+ _c('}')
+ else:
+ _hc('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' **')
+ _hc(' ** %s * %s', field.c_field_type, field.c_accessor_name)
+ _hc(' ** ')
+ _hc(' ** @param const %s *R', c_type)
+ _hc(' ** @returns %s *', field.c_field_type)
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ _hc(' ')
+ if field.type.is_switch and switch_obj is None:
+ return_type = 'void *'
+ else:
+ return_type = '%s *' % field.c_field_type
+
+ _hc(return_type)
+ _h('%s (const %s *R /**< */);', field.c_accessor_name, c_type)
+ _c('%s (const %s *R /**< */)', field.c_accessor_name, c_type)
+ _c('{')
+ if field.prev_varsized_field is None:
+ _c(' return (%s) (R + 1);', return_type)
+ # note: the special case 'variable fields followed by fixed size fields'
+ # is not of any consequence here, since the ordering gets
+ # 'corrected' in the reply function
+ else:
+ _c(' xcb_generic_iterator_t prev = %s;', _c_iterator_get_end(field.prev_varsized_field, 'R'))
+ _c(' return (%s) ((char *) prev.data + XCB_TYPE_PAD(%s, prev.index) + %d);',
+ return_type, type_pad_type(field.first_field_after_varsized.type.c_type), field.prev_varsized_offset)
+ _c('}')
+
+
+def _c_accessors_list(self, field):
+ '''
+ Declares the accessor functions for a list field.
+ Declares a direct-accessor function only if the list members are fixed size.
+ Declares length and get-iterator functions always.
+ '''
+ list = field.type
+ c_type = self.c_type
+
+ # special case: switch
+ # in case of switch, 2 params have to be supplied to certain accessor functions:
+ # 1. the anchestor object (request or reply)
+ # 2. the (anchestor) switch object
+ # the reason is that switch is either a child of a request/reply or nested in another switch,
+ # so whenever we need to access a length field, we might need to refer to some anchestor type
+ switch_obj = self if self.is_switch else None
+ if self.is_bitcase:
+ switch_obj = self.parents[-1]
+ if switch_obj is not None:
+ c_type = switch_obj.c_type
+
+ params = []
+ fields = {}
+ parents = self.parents if hasattr(self, 'parents') else [self]
+ # 'R': parents[0] is always the 'toplevel' container type
+ params.append(('const %s *R' % parents[0].c_type, parents[0]))
+ fields.update(_c_helper_field_mapping(parents[0], [('R', '->', parents[0])], flat=True))
+ # auxiliary object for 'R' parameters
+ R_obj = parents[0]
+
+ if switch_obj is not None:
+ # now look where the fields are defined that are needed to evaluate
+ # the switch expr, and store the parent objects in accessor_params and
+ # the fields in switch_fields
+
+ # 'S': name for the 'toplevel' switch
+ toplevel_switch = parents[1]
+ params.append(('const %s *S' % toplevel_switch.c_type, toplevel_switch))
+ fields.update(_c_helper_field_mapping(toplevel_switch, [('S', '->', toplevel_switch)], flat=True))
+
+ # initialize prefix for everything "below" S
+ prefix_str = '/* %s */ S' % toplevel_switch.name[-1]
+ prefix = [(prefix_str, '->', toplevel_switch)]
+
+ # look for fields in the remaining containers
+ for p in parents[2:] + [self]:
+ # the separator between parent and child is always '.' here,
+ # because of nested switch statements
+ if not p.is_bitcase or (p.is_bitcase and p.has_name):
+ prefix.append((p.name[-1], '.', p))
+ fields.update(_c_helper_field_mapping(p, prefix, flat=True))
+
+ # auxiliary object for 'S' parameter
+ S_obj = parents[1]
+
+ _h_setlevel(1)
+
+ if list.member.fixed_size():
+ idx = 1 if switch_obj is not None else 0
+
+ if switch_obj is not None:
+ spacing = ' '*(len(field.c_length_name)+2)
+ length = _c_accessor_get_expr(field.type.expr, fields)
+ else:
+ length = _c_accessor_get_expr(field.type.expr, fields)
+
+ request_name = _ext(_n_item(self.name[-1]))
+
+ if not request_name in _cpp_request_objects:
+ # print >> sys.stderr, 'WARN: Skipping undefined _cpp_request_object %s' % (request_name,)
+ return
+
+ if list.member.fixed_size() and not self.is_bitcase:
+ if field.c_field_type == "char":
+ _cpp_request_objects[request_name].accessors.append( \
+ Accessor(is_string=True,
+ member=_ext(_n_item(field.field_name)),
+ c_name=_n(self.name))
+ )
+
+ else:
+ _cpp_request_objects[request_name].accessors.append( \
+ Accessor(is_fixed=True,
+ member=_ext(_n_item(field.field_name)),
+ c_type=field.c_field_type,
+ return_type="", # 'Type' if field.c_field_type == 'void' else field.c_field_type,
+ iter_name="",
+ c_name=_n(self.name))
+ )
+
+ else:
+
+ # sys.stderr.write('request_name: %s\n' % request_name)
+ # sys.stderr.write('c_iterator_name:\n%s;\nc_end_name:\n%s\n' \
+ # % (field.c_iterator_name, field.c_end_name))
+ # sys.stderr.write('field: %s\n' % (field))
+ # sys.stderr.write('\n\n')
+
+ if not self.is_bitcase:
+ _cpp_request_objects[request_name].accessors.append( \
+ Accessor(is_variable=True,
+ member=_ext(_n_item(field.field_name)),
+ c_type=field.c_field_type,
+ return_type='Type' if field.c_field_type == 'void' else field.c_field_type,
+ iter_name=_n(field.type.name),
+ c_name=_n(self.name))
+ )
+
+ # sys.stderr.write('c_iterator_name:\n%s;\nc_end_name:\n%s\n' % (field.c_iterator_name,
+ # field.c_end_name))
+ # sys.stderr.write('field: %s\n' % (field))
+ # sys.stderr.write('\n\n')
+
+def _c_accessors(self, name, base):
+ '''
+ Declares the accessor functions for the fields of a structure.
+ '''
+ # no accessors for switch itself -
+ # switch always needs to be unpacked explicitly
+
+ for field in self.fields:
+ if field.type.is_list and not field.type.fixed_size():
+ _c_accessors_list(self, field)
+ elif field.prev_varsized_field is not None or not field.type.fixed_size():
+ pass
+ # _c_accessors_field(self, field)
+ # sys.stderr.write("c_accessors_field(%s, %s)\n" % (self, field))
+
+def c_simple(self, name):
+ '''
+ Exported function that handles cardinal type declarations.
+ These are types which are typedef'd to one of the CARDx's, char, float, etc.
+ '''
+ _c_type_setup(self, name, ())
+
+ if (self.name != name):
+ # Typedef
+ _h_setlevel(0)
+ my_name = _t(name)
+ # _h('')
+ # _h('typedef %s %s;', _t(self.name), my_name)
+ # if field.type.is_simple:
+ # if len(name) == 2:
+ # _h('NS_HEAD(type)')
+ # _h("TYPE_CLASS(%s)", _ext(_n_item(name[-1])))
+ # _h('NS_TAIL(type)')
+ # sys.stderr.write('type: %s, name: %s\n' % (field.field_type, field.field_name))
+ # sys.stderr.write('simple: %s\n' % (field))
+
+ # Iterator
+ # _c_iterator(self, name)
+
+def _c_complex(self):
+ '''
+ Helper function for handling all structure types.
+ Called for all structs, requests, replies, events, errors.
+ '''
+
+ '''
+ _h_setlevel(0)
+ _h('')
+ _h('/**')
+ _h(' * @brief %s', self.c_type)
+ _h(' **/')
+ _h('typedef %s %s {', self.c_container, self.c_type)
+ '''
+
+ struct_fields = []
+ maxtypelen = 0
+
+ varfield = None
+ for field in self.fields:
+ if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+ varfield = field.c_field_name
+ continue
+ if field.wire:
+ struct_fields.append(field)
+
+ for field in struct_fields:
+ length = len(field.c_field_type)
+ # account for '*' pointer_spec
+ if not field.type.fixed_size() and not self.is_union:
+ length += 1
+ maxtypelen = max(maxtypelen, length)
+
+ ### TODO: serialization(?)
+ def _c_complex_field(self, field, space=''):
+ if (field.type.fixed_size() or self.is_union or
+ # in case of switch with switch children, don't make the field a pointer
+ # necessary for unserialize to work
+ (self.is_switch and field.type.is_switch)):
+ spacing = ' ' * (maxtypelen - len(field.c_field_type))
+ # _h('%s %s%s %s%s; /**< */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+ # _h("%s %s" % (field.field_type, field.field_name))
+ # sys.stderr.write("serialize: %s, %s\n" % (field.field_type, field.field_name))
+ else:
+ spacing = ' ' * (maxtypelen - (len(field.c_field_type) + 1))
+ # _h('%s %s%s *%s%s; /**< */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+
+ if not self.is_switch:
+ for field in struct_fields:
+ _c_complex_field(self, field)
+ else:
+ for b in self.bitcases:
+ space = ''
+ if b.type.has_name:
+ # _h(' struct _%s {', b.c_field_name)
+ space = ' '
+ for field in b.type.fields:
+ _c_complex_field(self, field, space)
+ # if b.type.has_name:
+ # _h(' } %s;', b.c_field_name)
+
+ # _h('} %s;', self.c_type)
+
+def c_struct(self, name):
+ '''
+ Exported function that handles structure declarations.
+ '''
+ _c_type_setup(self, name, ())
+ _c_complex(self)
+ _c_accessors(self, name, name)
+ _c_iterator(self, name)
+
+def c_union(self, name):
+ '''
+ Exported function that handles union declarations.
+ '''
+ _c_type_setup(self, name, ())
+ _c_complex(self)
+ _c_iterator(self, name)
+
+def _c_request_helper(self, name, cookie_type, void, regular, aux=False, reply_fds=False):
+ '''
+ Declares a request function.
+ '''
+
+ # Four stunningly confusing possibilities here:
+ #
+ # Void Non-void
+ # ------------------------------
+ # "req" "req"
+ # 0 flag CHECKED flag Normal Mode
+ # void_cookie req_cookie
+ # ------------------------------
+ # "req_checked" "req_unchecked"
+ # CHECKED flag 0 flag Abnormal Mode
+ # void_cookie req_cookie
+ # ------------------------------
+
+
+ # Whether we are _checked or _unchecked
+ checked = void and not regular
+ unchecked = not void and not regular
+
+ # What kind of cookie we return
+ func_cookie = 'xcb_void_cookie_t' if void else self.c_cookie_type
+
+ # What flag is passed to xcb_request
+ func_flags = '0' if (void and regular) or (not void and not regular) else 'XCB_REQUEST_CHECKED'
+
+ if reply_fds:
+ if func_flags == '0':
+ func_flags = 'XCB_REQUEST_REPLY_FDS'
+ else:
+ func_flags = func_flags + '|XCB_REQUEST_REPLY_FDS'
+
+ # Global extension id variable or NULL for xproto
+ func_ext_global = '&' + _ns.c_ext_global_name if _ns.is_ext else '0'
+
+ # What our function name is
+ func_name = self.c_request_name if not aux else self.c_aux_name
+ if checked:
+ func_name = self.c_checked_name if not aux else self.c_aux_checked_name
+ if unchecked:
+ func_name = self.c_unchecked_name if not aux else self.c_aux_unchecked_name
+
+ param_fields = []
+ wire_fields = []
+ maxtypelen = len('xcb_connection_t')
+ serial_fields = []
+ # special case: list with variable size elements
+ list_with_var_size_elems = False
+
+ for field in self.fields:
+ if field.visible:
+ # The field should appear as a call parameter
+ param_fields.append(field)
+ if field.wire and not field.auto:
+ # We need to set the field up in the structure
+ wire_fields.append(field)
+ if field.type.need_serialize or field.type.need_sizeof:
+ serial_fields.append(field)
+
+ for field in param_fields:
+ c_field_const_type = field.c_field_const_type
+ if field.type.need_serialize and not aux:
+ c_field_const_type = "const void"
+ if len(c_field_const_type) > maxtypelen:
+ maxtypelen = len(c_field_const_type)
+ if field.type.is_list and not field.type.member.fixed_size():
+ list_with_var_size_elems = True
+
+ _h_setlevel(1)
+ _c_setlevel(1)
+ # _h('')
+ # _h('/**')
+ # if hasattr(self, "doc") and self.doc:
+ # if self.doc.brief:
+ # _h(' * @brief ' + self.doc.brief)
+ # else:
+ # _h(' * No brief doc yet')
+
+ # _h(' *')
+ # _h(' * @param c The connection')
+ param_names = [f.c_field_name for f in param_fields]
+ if hasattr(self, "doc") and self.doc:
+ for field in param_fields:
+ # XXX: hard-coded until we fix xproto.xml
+ base_func_name = self.c_request_name if not aux else self.c_aux_name
+ if base_func_name == 'xcb_change_gc' and field.c_field_name == 'value_mask':
+ field.enum = 'GC'
+ elif base_func_name == 'xcb_change_window_attributes' and field.c_field_name == 'value_mask':
+ field.enum = 'CW'
+ elif base_func_name == 'xcb_create_window' and field.c_field_name == 'value_mask':
+ field.enum = 'CW'
+ if field.enum:
+ # XXX: why the 'xcb' prefix?
+ key = ('xcb', field.enum)
+
+ tname = _t(key)
+ if namecount[tname] > 1:
+ tname = _t(key + ('enum',))
+ # _h(' * @param %s A bitmask of #%s values.' % (field.c_field_name, tname))
+
+ if self.doc and field.field_name in self.doc.fields:
+ desc = self.doc.fields[field.field_name]
+ for name in param_names:
+ desc = desc.replace('`%s`' % name, '\\a %s' % (name))
+ desc = desc.split("\n")
+ desc = [line if line != '' else '\\n' for line in desc]
+ # _h(' * @param %s %s' % (field.c_field_name, "\n * ".join(desc)))
+ # If there is no documentation yet, we simply don't generate an
+ # @param tag. Doxygen will then warn about missing documentation.
+
+ # _h(' * @return A cookie')
+ # _h(' *')
+
+ # if hasattr(self, "doc") and self.doc:
+ # if self.doc.description:
+ # desc = self.doc.description
+ # for name in param_names:
+ # desc = desc.replace('`%s`' % name, '\\a %s' % (name))
+ # desc = desc.split("\n")
+ # _h(' * ' + "\n * ".join(desc))
+ # else:
+ # _h(' * No description yet')
+ # else:
+ # _h(' * Delivers a request to the X server.')
+ # _h(' * ')
+ # if checked:
+ # _h(' * This form can be used only if the request will not cause')
+ # _h(' * a reply to be generated. Any returned error will be')
+ # _h(' * saved for handling by xcb_request_check().')
+ # if unchecked:
+ # _h(' * This form can be used only if the request will cause')
+ # _h(' * a reply to be generated. Any returned error will be')
+ # _h(' * placed in the event queue.')
+ # _h(' */')
+ # _c('')
+ # _hc('')
+ # _hc('/*****************************************************************************')
+ # _hc(' **')
+ # _hc(' ** %s %s', cookie_type, func_name)
+ # _hc(' ** ')
+
+ spacing = ' ' * (maxtypelen - len('xcb_connection_t'))
+ _c(' ** @param xcb_connection_t%s *c', spacing)
+
+ for field in param_fields:
+ c_field_const_type = field.c_field_const_type
+ if field.type.need_serialize and not aux:
+ c_field_const_type = "const void"
+ spacing = ' ' * (maxtypelen - len(c_field_const_type))
+ _c(' ** @param %s%s %s%s', c_field_const_type, spacing, field.c_pointer, field.c_field_name)
+
+ _c(' ** @returns %s', cookie_type)
+ _c(' **')
+ _c(' *****************************************************************************/')
+ _c(' ')
+ _c('%s', cookie_type)
+
+ spacing = ' ' * (maxtypelen - len('xcb_connection_t'))
+ comma = ',' if len(param_fields) else ');'
+ # _h('%s (xcb_connection_t%s *c /**< */%s', func_name, spacing, comma)
+ comma = ',' if len(param_fields) else ')'
+ _c('%s (xcb_connection_t%s *c /**< */%s', func_name, spacing, comma)
+
+ func_spacing = ' ' * (len(func_name) + 2)
+ count = len(param_fields)
+ for field in param_fields:
+ count = count - 1
+ c_field_const_type = field.c_field_const_type
+ c_pointer = field.c_pointer
+ if field.type.need_serialize and not aux:
+ c_field_const_type = "const void"
+ c_pointer = '*'
+ spacing = ' ' * (maxtypelen - len(c_field_const_type))
+ comma = ',' if count else ');'
+ # _h('%s%s%s %s%s /**< */%s', func_spacing, c_field_const_type,
+ # spacing, c_pointer, field.c_field_name, comma)
+ comma = ',' if count else ')'
+ _c('%s%s%s %s%s /**< */%s', func_spacing, c_field_const_type,
+ spacing, c_pointer, field.c_field_name, comma)
+
+ count = 2
+ if not self.var_followed_by_fixed_fields:
+ for field in param_fields:
+ if not field.type.fixed_size():
+ count = count + 2
+ if field.type.need_serialize:
+ # _serialize() keeps track of padding automatically
+ count -= 1
+ dimension = count + 2
+
+ _c('{')
+ _c(' static const xcb_protocol_request_t xcb_req = {')
+ _c(' /* count */ %d,', count)
+ _c(' /* ext */ %s,', func_ext_global)
+ _c(' /* opcode */ %s,', self.c_request_name.upper())
+ _c(' /* isvoid */ %d', 1 if void else 0)
+ _c(' };')
+ _c(' ')
+
+ _c(' struct iovec xcb_parts[%d];', dimension)
+ _c(' %s xcb_ret;', func_cookie)
+ _c(' %s xcb_out;', self.c_type)
+ if self.var_followed_by_fixed_fields:
+ _c(' /* in the protocol description, variable size fields are followed by fixed size fields */')
+ _c(' void *xcb_aux = 0;')
+
+
+ for idx, f in enumerate(serial_fields):
+ if aux:
+ _c(' void *xcb_aux%d = 0;' % (idx))
+ if list_with_var_size_elems:
+ _c(' unsigned int i;')
+ _c(' unsigned int xcb_tmp_len;')
+ _c(' char *xcb_tmp;')
+ _c(' ')
+ # simple request call tracing
+# _c(' printf("in function %s\\n");' % func_name)
+
+ # fixed size fields
+ for field in wire_fields:
+ if field.type.fixed_size():
+ if field.type.is_expr:
+ _c(' xcb_out.%s = %s;', field.c_field_name, _c_accessor_get_expr(field.type.expr, None))
+ elif field.type.is_pad:
+ if field.type.nmemb == 1:
+ _c(' xcb_out.%s = 0;', field.c_field_name)
+ else:
+ _c(' memset(xcb_out.%s, 0, %d);', field.c_field_name, field.type.nmemb)
+ else:
+ if field.type.nmemb == 1:
+ _c(' xcb_out.%s = %s;', field.c_field_name, field.c_field_name)
+ else:
+ _c(' memcpy(xcb_out.%s, %s, %d);', field.c_field_name, field.c_field_name, field.type.nmemb)
+
+ def get_serialize_args(type_obj, c_field_name, aux_var, context='serialize'):
+ serialize_args = get_serialize_params(context, type_obj,
+ c_field_name,
+ aux_var)[2]
+ return reduce(lambda x,y: "%s, %s" % (x,y), [a[2] for a in serialize_args])
+
+ # calls in order to free dyn. all. memory
+ free_calls = []
+
+ _c(' ')
+ if not self.var_followed_by_fixed_fields:
+ _c(' xcb_parts[2].iov_base = (char *) &xcb_out;')
+ _c(' xcb_parts[2].iov_len = sizeof(xcb_out);')
+ _c(' xcb_parts[3].iov_base = 0;')
+ _c(' xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;')
+
+ count = 4
+
+ for field in param_fields:
+ if not field.type.fixed_size():
+ _c(' /* %s %s */', field.type.c_type, field.c_field_name)
+ # default: simple cast to char *
+ if not field.type.need_serialize and not field.type.need_sizeof:
+ _c(' xcb_parts[%d].iov_base = (char *) %s;', count, field.c_field_name)
+ if field.type.is_list:
+ # _c("IS_LIST")
+ if field.type.member.fixed_size():
+ _c(' xcb_parts[%d].iov_len = %s * sizeof(%s);', count,
+ _c_accessor_get_expr(field.type.expr, None),
+ field.type.member.c_wiretype)
+ else:
+ list_length = _c_accessor_get_expr(field.type.expr, None)
+
+ length = ''
+ _c(" xcb_parts[%d].iov_len = 0;" % count)
+ _c(" xcb_tmp = (char *)%s;", field.c_field_name)
+ _c(" for(i=0; i<%s; i++) {" % list_length)
+ _c(" xcb_tmp_len = %s(xcb_tmp);" %
+ (field.type.c_sizeof_name))
+ _c(" xcb_parts[%d].iov_len += xcb_tmp_len;" % count)
+ _c(" xcb_tmp += xcb_tmp_len;")
+ _c(" }")
+ else:
+ # not supposed to happen
+ raise Exception("unhandled variable size field %s" % field.c_field_name)
+ else:
+ if not aux:
+ _c(' xcb_parts[%d].iov_base = (char *) %s;', count, field.c_field_name)
+ idx = serial_fields.index(field)
+ aux_var = '&xcb_aux%d' % idx
+ context = 'serialize' if aux else 'sizeof'
+ _c(' xcb_parts[%d].iov_len = ', count)
+ if aux:
+ serialize_args = get_serialize_args(field.type, aux_var, field.c_field_name, context)
+ _c(' %s (%s);', field.type.c_serialize_name, serialize_args)
+ _c(' xcb_parts[%d].iov_base = xcb_aux%d;' % (count, idx))
+ free_calls.append(' free(xcb_aux%d);' % idx)
+ else:
+ serialize_args = get_serialize_args(field.type, field.c_field_name, aux_var, context)
+ func_name = field.type.c_sizeof_name
+ _c(' %s (%s);', func_name, serialize_args)
+
+ count += 1
+ if not (field.type.need_serialize or field.type.need_sizeof):
+ # the _serialize() function keeps track of padding automatically
+ _c(' xcb_parts[%d].iov_base = 0;', count)
+ _c(' xcb_parts[%d].iov_len = -xcb_parts[%d].iov_len & 3;', count, count-1)
+ count += 1
+
+ # elif self.var_followed_by_fixed_fields:
+ else:
+ _c(' xcb_parts[2].iov_base = (char *) &xcb_out;')
+ # request header: opcodes + length
+ _c(' xcb_parts[2].iov_len = 2*sizeof(uint8_t) + sizeof(uint16_t);')
+ count += 1
+ # call _serialize()
+ buffer_var = '&xcb_aux'
+ serialize_args = get_serialize_args(self, buffer_var, '&xcb_out', 'serialize')
+ _c(' xcb_parts[%d].iov_len = %s (%s);', count, self.c_serialize_name, serialize_args)
+ _c(' xcb_parts[%d].iov_base = (char *) xcb_aux;', count)
+ free_calls.append(' free(xcb_aux);')
+ # no padding necessary - _serialize() keeps track of padding automatically
+
+ _c(' ')
+ for field in param_fields:
+ if field.isfd:
+ _c(' xcb_send_fd(c, %s);', field.c_field_name)
+
+ _c(' xcb_ret.sequence = xcb_send_request(c, %s, xcb_parts + 2, &xcb_req);', func_flags)
+
+ # free dyn. all. data, if any
+ for f in free_calls:
+ _c(f)
+ _c(' return xcb_ret;')
+ _c('}')
+
+def _cpp_request_helper(self, name, is_void):
+ '''
+ Declares a request function.
+ '''
+
+ request_name = _ext(_n_item(self.name[-1]))
+ c_func_name = _n(self.name)
+
+ param_fields = []
+
+ for field in self.fields:
+ if field.visible:
+ param_fields.append(field)
+
+ for field in param_fields:
+ c_field_const_type = field.c_field_const_type
+ if field.type.need_serialize:
+ c_field_const_type = "const void"
+
+ _cpp_request_names.append(request_name)
+ # self == request
+ _cpp_request_objects[request_name] = CppRequest(self, request_name, is_void, _ns, self.reply)
+
+ # is_obj_func = False
+ # if (len(param_fields) > 0 and len(param_fields[0].field_type) > 1):
+ # # e.g.: DRAWABLE in { "DRAWABLE" : [], .. }
+ # obj_name = param_fields[0].field_type[-1]
+ # is_obj_func = obj_name in _type_objects[get_namespace(_ns)]
+
+ generate_request_specialization = False
+ for field in param_fields:
+ c_field_const_type = field.c_field_const_type
+
+ if field.c_pointer == " ": c_pointer = ""
+ else: c_pointer = " " + field.c_pointer
+
+ if field.type.need_serialize:
+ c_field_const_type = "const void"
+ c_pointer = ' *'
+
+ param = Parameter(field) # , verbose=request_name=="set_screen_config")
+
+ _cpp_request_objects[request_name].add(param)
+
+ if (param.is_const and param.is_pointer
+ and param.c_type == 'void'):
+ generate_request_specialization = True
+
+ _cpp_request_objects[request_name].make_wrapped()
+
+ _interface_class.add(_cpp_request_objects[request_name])
+
+ for key in _object_classes:
+ _object_classes[key].set_namespace(_ns)
+ _object_classes[key].add(_cpp_request_objects[request_name])
+
+ ### C CODE ###
+
+ # if generate_request_specialization:
+ # if self.reply:
+ # # _c_type_setup(self.reply, name, ('reply',))
+ # # Reply structure definition
+ # # _c_complex(self.reply)
+ # # Request prototypes
+ # has_fds = _c_reply_has_fds(self.reply)
+ # _c_request_helper(self, name, self.c_cookie_type, False, True, False, has_fds)
+ # _c_request_helper(self, name, self.c_cookie_type, False, False, False, has_fds)
+ # if self.need_aux:
+ # _c_request_helper(self, name, self.c_cookie_type, False, True, True, has_fds)
+ # _c_request_helper(self, name, self.c_cookie_type, False, False, True, has_fds)
+ # # Reply accessors
+ # # _c_accessors(self.reply, name + ('reply',), name)
+ # # _c_reply(self, name)
+ # # if has_fds:
+ # # _c_reply_fds(self, name)
+ # else:
+ # # Request prototypes
+ # _c_request_helper(self, name, 'xcb_void_cookie_t', True, False)
+ # _c_request_helper(self, name, 'xcb_void_cookie_t', True, True)
+ # if self.need_aux:
+ # _c_request_helper(self, name, 'xcb_void_cookie_t', True, False, True)
+ # _c_request_helper(self, name, 'xcb_void_cookie_t', True, True, True)
+
+ ### C CODE ###
+
+def _c_reply(self, name):
+ '''
+ Declares the function that returns the reply structure.
+ '''
+ spacing1 = ' ' * (len(self.c_cookie_type) - len('xcb_connection_t'))
+ spacing2 = ' ' * (len(self.c_cookie_type) - len('xcb_generic_error_t'))
+ spacing3 = ' ' * (len(self.c_reply_name) + 2)
+
+ # check if _unserialize() has to be called for any field
+ def look_for_special_cases(complex_obj):
+ unserialize_fields = []
+ # no unserialize call in case of switch
+ if not complex_obj.is_switch:
+ for field in complex_obj.fields:
+ # three cases: 1. field with special case
+ # 2. container that contains special case field
+ # 3. list with special case elements
+ if field.type.var_followed_by_fixed_fields:
+ unserialize_fields.append(field)
+ elif field.type.is_container:
+ unserialize_fields += look_for_special_cases(field.type)
+ elif field.type.is_list:
+ if field.type.member.var_followed_by_fixed_fields:
+ unserialize_fields.append(field)
+ if field.type.member.is_container:
+ unserialize_fields += look_for_special_cases(field.type.member)
+ return unserialize_fields
+
+ unserialize_fields = look_for_special_cases(self.reply)
+
+ '''
+ _h('')
+ _h('/**')
+ _h(' * Return the reply')
+ _h(' * @param c The connection')
+ _h(' * @param cookie The cookie')
+ _h(' * @param e The xcb_generic_error_t supplied')
+ _h(' *')
+ _h(' * Returns the reply of the request asked by')
+ _h(' * ')
+ _h(' * The parameter @p e supplied to this function must be NULL if')
+ _h(' * %s(). is used.', self.c_unchecked_name)
+ _h(' * Otherwise, it stores the error if any.')
+ _h(' *')
+ _h(' * The returned value must be freed by the caller using free().')
+ _h(' */')
+ _c('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' **')
+ _hc(' ** %s * %s', self.c_reply_type, self.c_reply_name)
+ _hc(' ** ')
+ _hc(' ** @param xcb_connection_t%s *c', spacing1)
+ _hc(' ** @param %s cookie', self.c_cookie_type)
+ _hc(' ** @param xcb_generic_error_t%s **e', spacing2)
+ _hc(' ** @returns %s *', self.c_reply_type)
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ '''
+
+ _hc(' ')
+ _hc('%s *', self.c_reply_type)
+ _hc('%s (xcb_connection_t%s *c /**< */,', self.c_reply_name, spacing1)
+ _hc('%s%s cookie /**< */,', spacing3, self.c_cookie_type)
+ _h('%sxcb_generic_error_t%s **e /**< */);', spacing3, spacing2)
+ _c('%sxcb_generic_error_t%s **e /**< */)', spacing3, spacing2)
+ _c('{')
+
+ if len(unserialize_fields)>0:
+ # certain variable size fields need to be unserialized explicitly
+ _c(' %s *reply = (%s *) xcb_wait_for_reply(c, cookie.sequence, e);',
+ self.c_reply_type, self.c_reply_type)
+ _c(' int i;')
+ for field in unserialize_fields:
+ if field.type.is_list:
+ _c(' %s %s_iter = %s(reply);', field.c_iterator_type, field.c_field_name, field.c_iterator_name)
+ _c(' int %s_len = %s(reply);', field.c_field_name, field.c_length_name)
+ _c(' %s *%s_data;', field.c_field_type, field.c_field_name)
+ else:
+ raise Exception('not implemented: call _unserialize() in reply for non-list type %s', field.c_field_type)
+ # call _unserialize(), using the reply as source and target buffer
+ _c(' /* special cases: transform parts of the reply to match XCB data structures */')
+ for field in unserialize_fields:
+ if field.type.is_list:
+ _c(' for(i=0; i<%s_len; i++) {', field.c_field_name)
+ _c(' %s_data = %s_iter.data;', field.c_field_name, field.c_field_name)
+ _c(' %s((const void *)%s_data, &%s_data);', field.type.c_unserialize_name,
+ field.c_field_name, field.c_field_name)
+ _c(' %s(&%s_iter);', field.type.c_next_name, field.c_field_name)
+ _c(' }')
+ # return the transformed reply
+ _c(' return reply;')
+
+ else:
+ _c(' return (%s *) xcb_wait_for_reply(c, cookie.sequence, e);', self.c_reply_type)
+
+ _c('}')
+
+def _c_reply_has_fds(self):
+ for field in self.fields:
+ if field.isfd:
+ return True
+ return False
+
+def _c_reply_fds(self, name):
+ '''
+ Declares the function that returns fds related to the reply.
+ '''
+ spacing1 = ' ' * (len(self.c_reply_type) - len('xcb_connection_t'))
+ spacing3 = ' ' * (len(self.c_reply_fds_name) + 2)
+ _h('')
+ _h('/**')
+ _h(' * Return the reply fds')
+ _h(' * @param c The connection')
+ _h(' * @param reply The reply')
+ _h(' *')
+ _h(' * Returns the array of reply fds of the request asked by')
+ _h(' * ')
+ _h(' * The returned value must be freed by the caller using free().')
+ _h(' */')
+ _c('')
+ _hc('')
+ _hc('/*****************************************************************************')
+ _hc(' **')
+ _hc(' ** int * %s', self.c_reply_fds_name)
+ _hc(' ** ')
+ _hc(' ** @param xcb_connection_t%s *c', spacing1)
+ _hc(' ** @param %s *reply', self.c_reply_type)
+ _hc(' ** @returns int *')
+ _hc(' **')
+ _hc(' *****************************************************************************/')
+ _hc(' ')
+ _hc('int *')
+ _hc('%s (xcb_connection_t%s *c /**< */,', self.c_reply_fds_name, spacing1)
+ _h('%s%s *reply /**< */);', spacing3, self.c_reply_type)
+ _c('%s%s *reply /**< */)', spacing3, self.c_reply_type)
+ _c('{')
+
+ _c(' return xcb_get_reply_fds(c, reply, sizeof(%s) + 4 * reply->length);', self.c_reply_type)
+
+ _c('}')
+
+
+def _c_opcode(name, opcode):
+ '''
+ Declares the opcode define for requests, events, and errors.
+ '''
+ _h_setlevel(0)
+ _h('')
+ _h('/** Opcode for %s. */', _n(name))
+ _h('#define %s %s', _n(name).upper(), opcode)
+
+def _c_cookie(self, name):
+ '''
+ Declares the cookie type for a non-void request.
+ '''
+ _h_setlevel(0)
+ _h('')
+ _h('/**')
+ _h(' * @brief %s', self.c_cookie_type)
+ _h(' **/')
+ _h('typedef struct %s {', self.c_cookie_type)
+ _h(' unsigned int sequence; /**< */')
+ _h('} %s;', self.c_cookie_type)
+
+def _man_request(self, name, cookie_type, void, aux):
+ param_fields = [f for f in self.fields if f.visible]
+
+ func_name = self.c_request_name if not aux else self.c_aux_name
+
+ def create_link(linkname):
+ name = 'man/%s.3' % linkname
+ if manpaths:
+ sys.stdout.write(name)
+ f = open(name, 'w')
+ f.write('.so man3/%s.3' % func_name)
+ f.close()
+
+ if manpaths:
+ sys.stdout.write('man/%s.3 ' % func_name)
+ # Our CWD is src/, so this will end up in src/man/
+ f = open('man/%s.3' % func_name, 'w')
+ f.write('.TH %s 3 %s "XCB" "XCB Requests"\n' % (func_name, today))
+ # Left-adjust instead of adjusting to both sides
+ f.write('.ad l\n')
+ f.write('.SH NAME\n')
+ brief = self.doc.brief if hasattr(self, "doc") and self.doc else ''
+ f.write('%s \\- %s\n' % (func_name, brief))
+ f.write('.SH SYNOPSIS\n')
+ # Don't split words (hyphenate)
+ f.write('.hy 0\n')
+ f.write('.B #include <xcb/%s.h>\n' % _ns.header)
+
+ # function prototypes
+ prototype = ''
+ count = len(param_fields)
+ for field in param_fields:
+ count = count - 1
+ c_field_const_type = field.c_field_const_type
+ c_pointer = field.c_pointer
+ if c_pointer == ' ':
+ c_pointer = ''
+ if field.type.need_serialize and not aux:
+ c_field_const_type = "const void"
+ c_pointer = '*'
+ comma = ', ' if count else ');'
+ prototype += '%s\\ %s\\fI%s\\fP%s' % (c_field_const_type, c_pointer, field.c_field_name, comma)
+
+ f.write('.SS Request function\n')
+ f.write('.HP\n')
+ base_func_name = self.c_request_name if not aux else self.c_aux_name
+ f.write('%s \\fB%s\\fP(xcb_connection_t\\ *\\fIconn\\fP, %s\n' % (cookie_type, base_func_name, prototype))
+ create_link('%s_%s' % (base_func_name, ('checked' if void else 'unchecked')))
+ if not void:
+ f.write('.PP\n')
+ f.write('.SS Reply datastructure\n')
+ f.write('.nf\n')
+ f.write('.sp\n')
+ f.write('typedef %s %s {\n' % (self.reply.c_container, self.reply.c_type))
+ struct_fields = []
+ maxtypelen = 0
+
+ for field in self.reply.fields:
+ if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+ continue
+ if field.wire:
+ struct_fields.append(field)
+
+ for field in struct_fields:
+ length = len(field.c_field_type)
+ # account for '*' pointer_spec
+ if not field.type.fixed_size():
+ length += 1
+ maxtypelen = max(maxtypelen, length)
+
+ def _c_complex_field(self, field, space=''):
+ if (field.type.fixed_size() or
+ # in case of switch with switch children, don't make the field a pointer
+ # necessary for unserialize to work
+ (self.is_switch and field.type.is_switch)):
+ spacing = ' ' * (maxtypelen - len(field.c_field_type))
+ f.write('%s %s%s \\fI%s\\fP%s;\n' % (space, field.c_field_type, spacing, field.c_field_name, field.c_subscript))
+ else:
+ spacing = ' ' * (maxtypelen - (len(field.c_field_type) + 1))
+ f.write('ELSE %s = %s\n' % (field.c_field_type, field.c_field_name))
+ #_h('%s %s%s *%s%s; /**< */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+
+ if not self.is_switch:
+ for field in struct_fields:
+ _c_complex_field(self, field)
+ else:
+ for b in self.bitcases:
+ space = ''
+ if b.type.has_name:
+ space = ' '
+ for field in b.type.fields:
+ _c_complex_field(self, field, space)
+ if b.type.has_name:
+ print('ERROR: New unhandled documentation case', file=sys.stderr)
+ pass
+
+ f.write('} \\fB%s\\fP;\n' % self.reply.c_type)
+ f.write('.fi\n')
+
+ f.write('.SS Reply function\n')
+ f.write('.HP\n')
+ f.write(('%s *\\fB%s\\fP(xcb_connection_t\\ *\\fIconn\\fP, %s\\ '
+ '\\fIcookie\\fP, xcb_generic_error_t\\ **\\fIe\\fP);\n') %
+ (self.c_reply_type, self.c_reply_name, self.c_cookie_type))
+ create_link('%s' % self.c_reply_name)
+
+ has_accessors = False
+ for field in self.reply.fields:
+ if field.type.is_list and not field.type.fixed_size():
+ has_accessors = True
+ elif field.prev_varsized_field is not None or not field.type.fixed_size():
+ has_accessors = True
+
+ if has_accessors:
+ f.write('.SS Reply accessors\n')
+
+ def _c_accessors_field(self, field):
+ '''
+ Declares the accessor functions for a non-list field that follows a variable-length field.
+ '''
+ c_type = self.c_type
+
+ # special case: switch
+ switch_obj = self if self.is_switch else None
+ if self.is_bitcase:
+ switch_obj = self.parents[-1]
+ if switch_obj is not None:
+ c_type = switch_obj.c_type
+
+ if field.type.is_simple:
+ f.write('%s %s (const %s *reply)\n' % (field.c_field_type, field.c_accessor_name, c_type))
+ create_link('%s' % field.c_accessor_name)
+ else:
+ f.write('%s *%s (const %s *reply)\n' % (field.c_field_type, field.c_accessor_name, c_type))
+ create_link('%s' % field.c_accessor_name)
+
+ def _c_accessors_list(self, field):
+ '''
+ Declares the accessor functions for a list field.
+ Declares a direct-accessor function only if the list members are fixed size.
+ Declares length and get-iterator functions always.
+ '''
+ list = field.type
+ c_type = self.reply.c_type
+
+ # special case: switch
+ # in case of switch, 2 params have to be supplied to certain accessor functions:
+ # 1. the anchestor object (request or reply)
+ # 2. the (anchestor) switch object
+ # the reason is that switch is either a child of a request/reply or nested in another switch,
+ # so whenever we need to access a length field, we might need to refer to some anchestor type
+ switch_obj = self if self.is_switch else None
+ if self.is_bitcase:
+ switch_obj = self.parents[-1]
+ if switch_obj is not None:
+ c_type = switch_obj.c_type
+
+ params = []
+ fields = {}
+ parents = self.parents if hasattr(self, 'parents') else [self]
+ # 'R': parents[0] is always the 'toplevel' container type
+ params.append(('const %s *\\fIreply\\fP' % parents[0].c_type, parents[0]))
+ fields.update(_c_helper_field_mapping(parents[0], [('R', '->', parents[0])], flat=True))
+ # auxiliary object for 'R' parameters
+ R_obj = parents[0]
+
+ if switch_obj is not None:
+ # now look where the fields are defined that are needed to evaluate
+ # the switch expr, and store the parent objects in accessor_params and
+ # the fields in switch_fields
+
+ # 'S': name for the 'toplevel' switch
+ toplevel_switch = parents[1]
+ params.append(('const %s *S' % toplevel_switch.c_type, toplevel_switch))
+ fields.update(_c_helper_field_mapping(toplevel_switch, [('S', '->', toplevel_switch)], flat=True))
+
+ # initialize prefix for everything "below" S
+ prefix_str = '/* %s */ S' % toplevel_switch.name[-1]
+ prefix = [(prefix_str, '->', toplevel_switch)]
+
+ # look for fields in the remaining containers
+ for p in parents[2:] + [self]:
+ # the separator between parent and child is always '.' here,
+ # because of nested switch statements
+ if not p.is_bitcase or (p.is_bitcase and p.has_name):
+ prefix.append((p.name[-1], '.', p))
+ fields.update(_c_helper_field_mapping(p, prefix, flat=True))
+
+ # auxiliary object for 'S' parameter
+ S_obj = parents[1]
+
+ if list.member.fixed_size():
+ idx = 1 if switch_obj is not None else 0
+ f.write('.HP\n')
+ f.write('%s *\\fB%s\\fP(%s);\n' %
+ (field.c_field_type, field.c_accessor_name, params[idx][0]))
+ create_link('%s' % field.c_accessor_name)
+
+ f.write('.HP\n')
+ f.write('int \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+ (field.c_length_name, c_type))
+ create_link('%s' % field.c_length_name)
+
+ if field.type.member.is_simple:
+ f.write('.HP\n')
+ f.write('xcb_generic_iterator_t \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+ (field.c_end_name, c_type))
+ create_link('%s' % field.c_end_name)
+ else:
+ f.write('.HP\n')
+ f.write('%s \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+ (field.c_iterator_type, field.c_iterator_name,
+ c_type))
+ create_link('%s' % field.c_iterator_name)
+
+ for field in self.reply.fields:
+ if field.type.is_list and not field.type.fixed_size():
+ _c_accessors_list(self, field)
+ elif field.prev_varsized_field is not None or not field.type.fixed_size():
+ _c_accessors_field(self, field)
+
+
+ f.write('.br\n')
+ # Re-enable hyphenation and adjusting to both sides
+ f.write('.hy 1\n')
+
+ # argument reference
+ f.write('.SH REQUEST ARGUMENTS\n')
+ f.write('.IP \\fI%s\\fP 1i\n' % 'conn')
+ f.write('The XCB connection to X11.\n')
+ for field in param_fields:
+ f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+ printed_enum = False
+ # XXX: hard-coded until we fix xproto.xml
+ if base_func_name == 'xcb_change_gc' and field.c_field_name == 'value_mask':
+ field.enum = 'GC'
+ elif base_func_name == 'xcb_change_window_attributes' and field.c_field_name == 'value_mask':
+ field.enum = 'CW'
+ elif base_func_name == 'xcb_create_window' and field.c_field_name == 'value_mask':
+ field.enum = 'CW'
+ if hasattr(field, "enum") and field.enum:
+ # XXX: why the 'xcb' prefix?
+ key = ('xcb', field.enum)
+ if key in enums:
+ f.write('One of the following values:\n')
+ f.write('.RS 1i\n')
+ enum = enums[key]
+ count = len(enum.values)
+ for (enam, eval) in enum.values:
+ count = count - 1
+ f.write('.IP \\fI%s\\fP 1i\n' % (_n(key + (enam,)).upper()))
+ if hasattr(enum, "doc") and enum.doc and enam in enum.doc.fields:
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', enum.doc.fields[enam])
+ f.write('%s\n' % desc)
+ else:
+ f.write('TODO: NOT YET DOCUMENTED.\n')
+ f.write('.RE\n')
+ f.write('.RS 1i\n')
+ printed_enum = True
+
+ if hasattr(self, "doc") and self.doc and field.field_name in self.doc.fields:
+ desc = self.doc.fields[field.field_name]
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+ if printed_enum:
+ f.write('\n')
+ f.write('%s\n' % desc)
+ else:
+ f.write('TODO: NOT YET DOCUMENTED.\n')
+ if printed_enum:
+ f.write('.RE\n')
+
+ # Reply reference
+ if not void:
+ f.write('.SH REPLY FIELDS\n')
+ # These fields are present in every reply:
+ f.write('.IP \\fI%s\\fP 1i\n' % 'response_type')
+ f.write(('The type of this reply, in this case \\fI%s\\fP. This field '
+ 'is also present in the \\fIxcb_generic_reply_t\\fP and can '
+ 'be used to tell replies apart from each other.\n') %
+ _n(self.reply.name).upper())
+ f.write('.IP \\fI%s\\fP 1i\n' % 'sequence')
+ f.write('The sequence number of the last request processed by the X11 server.\n')
+ f.write('.IP \\fI%s\\fP 1i\n' % 'length')
+ f.write('The length of the reply, in words (a word is 4 bytes).\n')
+ for field in self.reply.fields:
+ if (field.c_field_name in frozenset(['response_type', 'sequence', 'length']) or
+ field.c_field_name.startswith('pad')):
+ continue
+
+ if field.type.is_list and not field.type.fixed_size():
+ continue
+ elif field.prev_varsized_field is not None or not field.type.fixed_size():
+ continue
+ f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+ printed_enum = False
+ if hasattr(field, "enum") and field.enum:
+ # XXX: why the 'xcb' prefix?
+ key = ('xcb', field.enum)
+ if key in enums:
+ f.write('One of the following values:\n')
+ f.write('.RS 1i\n')
+ enum = enums[key]
+ count = len(enum.values)
+ for (enam, eval) in enum.values:
+ count = count - 1
+ f.write('.IP \\fI%s\\fP 1i\n' % (_n(key + (enam,)).upper()))
+ if enum.doc and enam in enum.doc.fields:
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', enum.doc.fields[enam])
+ f.write('%s\n' % desc)
+ else:
+ f.write('TODO: NOT YET DOCUMENTED.\n')
+ f.write('.RE\n')
+ f.write('.RS 1i\n')
+ printed_enum = True
+
+ if hasattr(self.reply, "doc") and self.reply.doc and field.field_name in self.reply.doc.fields:
+ desc = self.reply.doc.fields[field.field_name]
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+ if printed_enum:
+ f.write('\n')
+ f.write('%s\n' % desc)
+ else:
+ f.write('TODO: NOT YET DOCUMENTED.\n')
+ if printed_enum:
+ f.write('.RE\n')
+
+
+
+ # text description
+ f.write('.SH DESCRIPTION\n')
+ if hasattr(self, "doc") and self.doc and self.doc.description:
+ desc = self.doc.description
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+ lines = desc.split('\n')
+ f.write('\n'.join(lines) + '\n')
+
+ f.write('.SH RETURN VALUE\n')
+ if void:
+ f.write(('Returns an \\fIxcb_void_cookie_t\\fP. Errors (if any) '
+ 'have to be handled in the event loop.\n\nIf you want to '
+ 'handle errors directly with \\fIxcb_request_check\\fP '
+ 'instead, use \\fI%s_checked\\fP. See '
+ '\\fBxcb-requests(3)\\fP for details.\n') % (base_func_name))
+ else:
+ f.write(('Returns an \\fI%s\\fP. Errors have to be handled when '
+ 'calling the reply function \\fI%s\\fP.\n\nIf you want to '
+ 'handle errors in the event loop instead, use '
+ '\\fI%s_unchecked\\fP. See \\fBxcb-requests(3)\\fP for '
+ 'details.\n') %
+ (cookie_type, self.c_reply_name, base_func_name))
+ f.write('.SH ERRORS\n')
+ if hasattr(self, "doc") and self.doc:
+ for errtype, errtext in list(self.doc.errors.items()):
+ f.write('.IP \\fI%s\\fP 1i\n' % (_t(('xcb', errtype, 'error'))))
+ errtext = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', errtext)
+ f.write('%s\n' % (errtext))
+ if not hasattr(self, "doc") or not self.doc or len(self.doc.errors) == 0:
+ f.write('This request does never generate any errors.\n')
+ if hasattr(self, "doc") and self.doc and self.doc.example:
+ f.write('.SH EXAMPLE\n')
+ f.write('.nf\n')
+ f.write('.sp\n')
+ lines = self.doc.example.split('\n')
+ f.write('\n'.join(lines) + '\n')
+ f.write('.fi\n')
+ f.write('.SH SEE ALSO\n')
+ if hasattr(self, "doc") and self.doc:
+ see = ['.BR %s (3)' % 'xcb-requests']
+ if self.doc.example:
+ see.append('.BR %s (3)' % 'xcb-examples')
+ for seename, seetype in list(self.doc.see.items()):
+ if seetype == 'program':
+ see.append('.BR %s (1)' % seename)
+ elif seetype == 'event':
+ see.append('.BR %s (3)' % _t(('xcb', seename, 'event')))
+ elif seetype == 'request':
+ see.append('.BR %s (3)' % _n(('xcb', seename)))
+ elif seetype == 'function':
+ see.append('.BR %s (3)' % seename)
+ else:
+ see.append('TODO: %s (type %s)' % (seename, seetype))
+ f.write(',\n'.join(see) + '\n')
+ f.write('.SH AUTHOR\n')
+ f.write('Generated from %s.xml. Contact xcb@lists.freedesktop.org for corrections and improvements.\n' % _ns.header)
+ f.close()
+
+def _man_event(self, name):
+ if manpaths:
+ sys.stdout.write('man/%s.3 ' % self.c_type)
+ # Our CWD is src/, so this will end up in src/man/
+ f = open('man/%s.3' % self.c_type, 'w')
+ f.write('.TH %s 3 %s "XCB" "XCB Events"\n' % (self.c_type, today))
+ # Left-adjust instead of adjusting to both sides
+ f.write('.ad l\n')
+ f.write('.SH NAME\n')
+ brief = self.doc.brief if hasattr(self, "doc") and self.doc else ''
+ f.write('%s \\- %s\n' % (self.c_type, brief))
+ f.write('.SH SYNOPSIS\n')
+ # Don't split words (hyphenate)
+ f.write('.hy 0\n')
+ f.write('.B #include <xcb/%s.h>\n' % _ns.header)
+
+ f.write('.PP\n')
+ f.write('.SS Event datastructure\n')
+ f.write('.nf\n')
+ f.write('.sp\n')
+ f.write('typedef %s %s {\n' % (self.c_container, self.c_type))
+ struct_fields = []
+ maxtypelen = 0
+
+ for field in self.fields:
+ if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+ continue
+ if field.wire:
+ struct_fields.append(field)
+
+ for field in struct_fields:
+ length = len(field.c_field_type)
+ # account for '*' pointer_spec
+ if not field.type.fixed_size():
+ length += 1
+ maxtypelen = max(maxtypelen, length)
+
+ def _c_complex_field(self, field, space=''):
+ if (field.type.fixed_size() or
+ # in case of switch with switch children, don't make the field a pointer
+ # necessary for unserialize to work
+ (self.is_switch and field.type.is_switch)):
+ spacing = ' ' * (maxtypelen - len(field.c_field_type))
+ f.write('%s %s%s \\fI%s\\fP%s;\n' % (space, field.c_field_type, spacing, field.c_field_name, field.c_subscript))
+ else:
+ print('ERROR: New unhandled documentation case', file=sys.stderr)
+
+ if not self.is_switch:
+ for field in struct_fields:
+ _c_complex_field(self, field)
+ else:
+ for b in self.bitcases:
+ space = ''
+ if b.type.has_name:
+ space = ' '
+ for field in b.type.fields:
+ _c_complex_field(self, field, space)
+ if b.type.has_name:
+ print('ERROR: New unhandled documentation case', file=sys.stderr)
+ pass
+
+ f.write('} \\fB%s\\fP;\n' % self.c_type)
+ f.write('.fi\n')
+
+
+ f.write('.br\n')
+ # Re-enable hyphenation and adjusting to both sides
+ f.write('.hy 1\n')
+
+ # argument reference
+ f.write('.SH EVENT FIELDS\n')
+ f.write('.IP \\fI%s\\fP 1i\n' % 'response_type')
+ f.write(('The type of this event, in this case \\fI%s\\fP. This field is '
+ 'also present in the \\fIxcb_generic_event_t\\fP and can be used '
+ 'to tell events apart from each other.\n') % _n(name).upper())
+ f.write('.IP \\fI%s\\fP 1i\n' % 'sequence')
+ f.write('The sequence number of the last request processed by the X11 server.\n')
+
+ if not self.is_switch:
+ for field in struct_fields:
+ # Skip the fields which every event has, we already documented
+ # them (see above).
+ if field.c_field_name in ('response_type', 'sequence'):
+ continue
+ if isinstance(field.type, PadType):
+ continue
+ f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+ if hasattr(self, "doc") and self.doc and field.field_name in self.doc.fields:
+ desc = self.doc.fields[field.field_name]
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+ f.write('%s\n' % desc)
+ else:
+ f.write('NOT YET DOCUMENTED.\n')
+
+ # text description
+ f.write('.SH DESCRIPTION\n')
+ if hasattr(self, "doc") and self.doc and self.doc.description:
+ desc = self.doc.description
+ desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+ lines = desc.split('\n')
+ f.write('\n'.join(lines) + '\n')
+
+ if hasattr(self, "doc") and self.doc and self.doc.example:
+ f.write('.SH EXAMPLE\n')
+ f.write('.nf\n')
+ f.write('.sp\n')
+ lines = self.doc.example.split('\n')
+ f.write('\n'.join(lines) + '\n')
+ f.write('.fi\n')
+ f.write('.SH SEE ALSO\n')
+ if hasattr(self, "doc") and self.doc:
+ see = ['.BR %s (3)' % 'xcb_generic_event_t']
+ if self.doc.example:
+ see.append('.BR %s (3)' % 'xcb-examples')
+ for seename, seetype in list(self.doc.see.items()):
+ if seetype == 'program':
+ see.append('.BR %s (1)' % seename)
+ elif seetype == 'event':
+ see.append('.BR %s (3)' % _t(('xcb', seename, 'event')))
+ elif seetype == 'request':
+ see.append('.BR %s (3)' % _n(('xcb', seename)))
+ elif seetype == 'function':
+ see.append('.BR %s (3)' % seename)
+ else:
+ see.append('TODO: %s (type %s)' % (seename, seetype))
+ f.write(',\n'.join(see) + '\n')
+ f.write('.SH AUTHOR\n')
+ f.write('Generated from %s.xml. Contact xcb@lists.freedesktop.org for corrections and improvements.\n' % _ns.header)
+ f.close()
+
+
+def c_request(self, name):
+ '''
+ Exported function that handles request declarations.
+ '''
+
+ _c_type_setup(self, name, ('request',))
+
+ # Request structure declaration
+ # _c_complex(self)
+
+ request_name = _ext(_n_item(self.name[-1]))
+ c_func_name = _n(self.name)
+
+ if self.reply:
+
+ _c_type_setup(self.reply, name, ('reply',))
+ # Reply structure definition
+ # _c_complex(self.reply)
+
+ # Request prototypes
+ _cpp_request_helper(self, name, False)
+
+ # Reply accessors
+ _c_accessors(self.reply, name + ('reply',), name)
+
+ else:
+ # Request prototypes
+ _cpp_request_helper(self, name, True)
+
+ # We generate the manpage afterwards because _c_type_setup has been called.
+ # TODO: what about aux helpers?
+ # cookie_type = self.c_cookie_type if self.reply else 'xcb_void_cookie_t'
+ # _man_request(self, name, cookie_type, not self.reply, False)
+
+def c_event(self, name):
+ '''
+ Exported function that handles event declarations.
+ '''
+
+ # The generic event structure xcb_ge_event_t has the full_sequence field
+ # at the 32byte boundary. That's why we've to inject this field into GE
+ # events while generating the structure for them. Otherwise we would read
+ # garbage (the internal full_sequence) when accessing normal event fields
+ # there.
+ if hasattr(self, 'is_ge_event') and self.is_ge_event and self.name == name:
+ event_size = 0
+ for field in self.fields:
+ if field.type.size != None and field.type.nmemb != None:
+ event_size += field.type.size * field.type.nmemb
+ if event_size == 32:
+ full_sequence = Field(tcard32, tcard32.name, 'full_sequence', False, True, True)
+ idx = self.fields.index(field)
+ self.fields.insert(idx + 1, full_sequence)
+ break
+
+ _c_type_setup(self, name, ('event',))
+
+ # Opcode define
+ # _c_opcode(name, self.opcodes[name])
+
+ _h('typedef %s %s;', _t(self.name + ('event',)), _t(name + ('event',)))
+ if self.name == name:
+ pass
+ # Structure definition
+ # _c_complex(self)
+ else:
+ pass
+ # Typedef
+ # _h('')
+ # _h('typedef %s %s;', _t(self.name + ('event',)), _t(name + ('event',)))
+
+ # _man_event(self, name)
+
+def cpp_event(self, name):
+ '''
+ Exported function that handles event declarations.
+ '''
+
+ _c_type_setup(self, name, ('event',))
+
+ opcode = _n(name).upper()
+ c_name = _t(self.name + ('event',))
+
+ cpp_event = CppEvent(self.opcodes[name], opcode, c_name, _ns, name, self.fields)
+ _cpp_events.append(cpp_event)
+ _interface_class.add_event(cpp_event)
+
+def c_error(self, name):
+ '''
+ Exported function that handles error declarations.
+ '''
+ _c_type_setup(self, name, ('error',))
+
+ # Opcode define
+ _c_opcode(name, self.opcodes[name])
+
+ if self.name == name:
+ # Structure definition
+ _c_complex(self)
+ else:
+ # Typedef
+ _h('')
+ _h('typedef %s %s;', _t(self.name + ('error',)), _t(name + ('error',)))
+
+def cpp_error(self, name):
+ '''
+ Exported function that handles error declarations.
+ '''
+ _c_type_setup(self, name, ('error',))
+
+ # Opcode define
+ # _c_opcode(name, self.opcodes[name])
+
+ # sys.stderr.write("error declaration: %s\n" % str(self))
+ # sys.stderr.write("opcode: %s\n" % self.opcodes[name])
+ # sys.stderr.write('typedef %s %s;\n\n' % ( _t(self.name + ('error',)), _t(name + ('error',))))
+
+ opcode_name = _n(name).upper()
+ c_name = _t(self.name + ('error',))
+ cpp_error = CppError(self, _ns, name, c_name, self.opcodes[name], opcode_name)
+ _cpp_errors.append(cpp_error)
+ _interface_class.add_error(cpp_error)
+
+ # if self.name == name:
+ # # Structure definition
+ # _c_complex(self)
+ # else:
+ # # Typedef
+ # _h('')
+ # _h('typedef %s %s;', _t(self.name + ('error',)), _t(name + ('error',)))
+
+# Main routine starts here
+
+# # Must create an "output" dictionary before any xcbgen imports.
+# output = {'open' : c_open,
+# 'close' : c_close,
+# 'simple' : c_simple,
+# 'enum' : c_enum,
+# 'struct' : c_struct,
+# 'union' : c_union,
+# 'request' : c_request,
+# 'event' : c_event,
+# 'error' : c_error,
+# }
+
+# Must create an "output" dictionary before any xcbgen imports.
+output = {'open' : c_open,
+ 'close' : c_close,
+ 'simple' : c_simple, # lambda x, y: None,
+ 'enum' : lambda x, y: None,
+ 'struct' : lambda x, y: None,
+ 'union' : lambda x, y: None,
+ 'request' : c_request,
+ 'event' : cpp_event,
+ 'error' : cpp_error,
+ 'eventstruct' : lambda x, y: None,
+ }
+
+# Boilerplate below this point
+
+# Check for the argument that specifies path to the xcbgen python package.
+try:
+ opts, args = getopt.getopt(sys.argv[1:], 'p:m')
+except getopt.GetoptError as err:
+ print(err)
+ print('Usage: c_client.py [-p path] file.xml')
+ sys.exit(1)
+
+for (opt, arg) in opts:
+ if opt == '-p':
+ sys.path.insert(1, arg)
+ elif opt == '-m':
+ manpaths = True
+ sys.stdout.write('man_MANS = ')
+
+# Import the module class
+try:
+ from xcbgen.state import Module
+ from xcbgen.xtypes import *
+except ImportError:
+ print('''
+Failed to load the xcbgen Python package!
+Make sure that xcb/proto installed it on your Python path.
+If not, you will need to create a .pth file or define $PYTHONPATH
+to extend the path.
+Refer to the README file in xcb/proto for more info.
+''')
+ raise
+
+# Ensure the man subdirectory exists
+try:
+ os.mkdir('man')
+except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+today = time.strftime('%Y-%m-%d', time.gmtime(os.path.getmtime(args[0])))
+
+# Parse the xml header
+module = Module(args[0], output)
+
+# Build type-registry and resolve type dependencies
+module.register()
+module.resolve()
+
+# Output the code
+module.generate()
--- /dev/null
+from utils import _n, _ext, _n_item, get_namespace
+
+_templates = {}
+
+_templates['void_cookie_function'] = \
+'''\
+%s\
+void
+%s_checked(Connection && c%s)
+{%s\
+ xpp::generic::check<Connection, xpp::%s::error::dispatcher>(
+ std::forward<Connection>(c),
+ %s_checked(std::forward<Connection>(c)%s));
+}
+
+%s\
+void
+%s(Connection && c%s)
+{%s\
+ %s(std::forward<Connection>(c)%s);
+}
+'''
+
+def _void_cookie_function(ns, name, c_name, template, return_value, protos, calls, initializer):
+ if len(template) == 0: template = "template<typename Connection>\n"
+ return _templates['void_cookie_function'] % \
+ ( template
+ , name
+ , protos
+ , initializer
+ , ns
+ , c_name
+ , calls
+ , template
+ , name
+ , protos
+ , initializer
+ , c_name
+ , calls
+ )
+
+_templates['cookie_static_getter'] = \
+'''\
+%s\
+ static
+ %s
+ cookie(xcb_connection_t * const c%s)
+ {%s\
+ return base::cookie(c%s);
+ }
+'''
+
+def _cookie_static_getter(template, return_value, protos, calls, initializer):
+ return _templates['cookie_static_getter'] % \
+ ( template
+ , return_value
+ , protos
+ , initializer
+ , calls
+ )
+
+class CppCookie(object):
+ def __init__(self, namespace, is_void, name, reply, parameter_list):
+ self.namespace = namespace
+ self.is_void = is_void
+ self.name = name
+ self.reply = reply
+ self.parameter_list = parameter_list
+ self.request_name = _ext(_n_item(self.name[-1]))
+ self.c_name = "xcb" \
+ + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+ + "_" + self.request_name
+
+ def comma(self):
+ return self.parameter_list.comma()
+
+ def calls(self, sort):
+ return self.parameter_list.calls(sort)
+
+ def protos(self, sort, defaults):
+ return self.parameter_list.protos(sort, defaults)
+
+ def iterator_template(self, indent=" ", tail="\n"):
+ prefix = "template<typename " + ("Connection, typename " if self.is_void else "")
+ return indent + prefix \
+ + ", typename ".join(self.parameter_list.iterator_templates \
+ + self.parameter_list.templates) \
+ + ">" + tail \
+ if len(self.parameter_list.iterator_templates) > 0 \
+ else ""
+
+ def iterator_calls(self, sort):
+ return self.parameter_list.iterator_calls(sort)
+
+ def iterator_protos(self, sort, defaults):
+ return self.parameter_list.iterator_protos(sort, defaults)
+
+ def iterator_initializers(self):
+ return self.parameter_list.iterator_initializers()
+
+ def void_functions(self, protos, calls, template="", initializer=[]):
+ inits = "" if len(initializer) > 0 else "\n"
+ for i in initializer:
+ inits += "\n"
+ for line in i.split('\n'):
+ inits += " " + line + "\n"
+
+ return_value = "xcb_void_cookie_t"
+
+ return _void_cookie_function(get_namespace(self.namespace),
+ self.request_name,
+ self.c_name,
+ template,
+ return_value,
+ self.comma() + protos,
+ self.comma() + calls,
+ inits)
+
+
+ def static_reply_methods(self, protos, calls, template="", initializer=[]):
+ inits = "" if len(initializer) > 0 else "\n"
+ for i in initializer:
+ inits += "\n"
+ for line in i.split('\n'):
+ inits += " " + line + "\n"
+
+ if self.is_void: return_value = "xcb_void_cookie_t"
+ else: return_value = self.c_name + "_cookie_t"
+
+ return _cookie_static_getter(template,
+ return_value,
+ self.comma() + protos,
+ self.comma() + calls,
+ inits)
+
+
+ def make_static_getter(self):
+ default = self.static_reply_methods(self.protos(False, False), self.calls(False))
+
+ if self.parameter_list.has_defaults:
+ default = self.static_reply_methods(self.protos(True, True), self.calls(False))
+
+ wrapped = ""
+ if self.parameter_list.want_wrap:
+ wrapped = \
+ self.static_reply_methods(self.iterator_protos(True, True),
+ self.iterator_calls(False), self.iterator_template(),
+ self.iterator_initializers())
+
+ default_args = ""
+ if self.parameter_list.is_reordered():
+ default_args = \
+ self.static_reply_methods(self.protos(True, True), self.calls(False))
+
+ result = ""
+
+ if (self.parameter_list.has_defaults
+ or self.parameter_list.is_reordered()
+ or self.parameter_list.want_wrap):
+ result += default
+
+ if self.parameter_list.is_reordered():
+ result += "\n" + default_args
+
+ if self.parameter_list.want_wrap:
+ result += "\n" + wrapped
+
+ return result
+
+ def make_void_functions(self):
+ default = self.void_functions(self.protos(False, False), self.calls(False))
+
+ if self.parameter_list.has_defaults:
+ default = self.void_functions(self.protos(True, True), self.calls(False))
+
+ wrapped = ""
+ if self.parameter_list.want_wrap:
+ wrapped = \
+ self.void_functions(self.iterator_protos(True, True),
+ self.iterator_calls(False),
+ self.iterator_template(indent=""),
+ self.iterator_initializers())
+
+ default_args = ""
+ if self.parameter_list.is_reordered():
+ default_args = \
+ self.void_functions(self.protos(True, True), self.calls(False))
+
+ result = ""
+
+ if (self.parameter_list.has_defaults
+ or self.parameter_list.is_reordered()
+ or self.parameter_list.want_wrap):
+ result += default
+
+ if self.parameter_list.is_reordered():
+ result += "\n" + default_args
+
+ if self.parameter_list.want_wrap:
+ result += "\n" + wrapped
+
+ return result
--- /dev/null
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext, \
+ _reserved_keywords
+
+_templates = {}
+
+_templates['error_dispatcher_class'] = \
+'''\
+namespace error {
+
+class dispatcher
+{
+ public:
+%s\
+%s\
+
+ void
+ operator()(const std::shared_ptr<xcb_generic_error_t> &%s) const
+ {
+%s\
+ }
+
+%s\
+}; // class dispatcher
+
+} // namespace error
+'''
+
+def _error_dispatcher_class(typedef, ctors, switch, members, has_errors):
+ return _templates['error_dispatcher_class'] % \
+ ( typedef
+ , ctors
+ , " error" if has_errors else ""
+ , switch if has_errors else ""
+ , members
+ )
+
+
+def error_dispatcher_class(namespace, cpperrors):
+ ns = get_namespace(namespace)
+
+ ctor_name = "dispatcher"
+
+ typedef = []
+ ctors = []
+ members = []
+ opcode_switch = "error->error_code"
+
+ typedef = [ "typedef xpp::%s::extension extension;\n" % ns ]
+
+ # >>> if begin <<<
+ if namespace.is_ext:
+ opcode_switch = "error->error_code - m_first_error"
+
+ members += \
+ [ "protected:"
+ , " uint8_t m_first_error;"
+ ]
+
+ ctors = \
+ [ "%s(uint8_t first_error)" % (ctor_name)
+ , " : m_first_error(first_error)"
+ , "{}"
+ , ""
+ , "%s(const xpp::%s::extension & extension)" % (ctor_name, ns)
+ , " : %s(extension->first_error)" % ctor_name
+ , "{}"
+ ]
+
+ # >>> if end <<<
+
+ if len(typedef) > 0:
+ typedef = "\n".join([" " + s for s in typedef]) + "\n"
+ else:
+ typedef = ""
+
+ if len(ctors) > 0:
+ ctors = "\n".join([(" " if len(s) > 0 else "") + s for s in ctors]) + "\n"
+ else:
+ ctors = ""
+
+ if len(members) > 0:
+ members = "\n".join([" " + s for s in members]) + "\n"
+ else:
+ members = ""
+
+ switch = error_switch_cases(cpperrors, opcode_switch, "error")
+ return _error_dispatcher_class(typedef,
+ ctors,
+ switch,
+ members,
+ len(cpperrors) > 0)
+
+def error_switch_cases(cpperrors, arg_switch, arg_error):
+ cases = ""
+ errors = cpperrors
+ templ = [ " case %s: // %s"
+ , " throw %s" + "(%s);" % arg_error
+ , ""
+ , ""
+ ]
+
+ cases += " switch (%s) {\n\n" % arg_switch
+ for e in errors:
+ cases += "\n".join(templ) % (e.opcode_name, e.opcode, e.scoped_name())
+ cases += " };\n"
+
+ return cases
+
+
+class CppError(object):
+ def __init__(self, error, namespace, name, c_name, opcode, opcode_name):
+ self.error = error
+ self.namespace = namespace
+ self.c_name = c_name
+ self.opcode = opcode
+ self.opcode_name = opcode_name
+
+ self.names = list(map(str.lower, _n_item(name[-1], True)))
+ self.name = "_".join(self.names)
+
+ self.nssopen = ""
+ self.nssclose = ""
+ self.scope = []
+ for name in self.names[0:-1]:
+ if name in _reserved_keywords: name += "_"
+ self.nssopen += " namespace %s {" % name
+ self.nssclose += " }"
+ self.scope.append(name)
+
+ def get_name(self):
+ return _reserved_keywords.get(self.name, self.name)
+
+
+ def scoped_name(self):
+ ns = get_namespace(self.namespace)
+ return "xpp::" + ns + "::error::" + self.get_name()
+
+ def make_class(self):
+ ns = get_namespace(self.namespace)
+ typedef = []
+ members = []
+
+ opcode_accessor = \
+ [ "static uint8_t opcode(void)"
+ , "{"
+ , " return %s;" % self.opcode_name
+ , "}"
+ ]
+
+ if self.namespace.is_ext:
+ opcode_accessor += \
+ [ ""
+ , "static uint8_t opcode(uint8_t first_error)"
+ , "{"
+ , " return first_error + opcode();"
+ , "}"
+ , ""
+ , "static uint8_t opcode(const xpp::%s::extension & extension)" % ns
+ , "{"
+ , " return opcode(extension->first_error);"
+ , "}"
+ ]
+
+ members = \
+ [ "protected:"
+ , " uint8_t m_first_error;"
+ ]
+
+ if len(opcode_accessor) > 0:
+ opcode_accessor = "\n".join([" " + s for s in opcode_accessor]) + "\n"
+ else:
+ opcode_accessor = ""
+
+ if len(members) > 0:
+ members = "\n" + "\n".join([" " + s for s in members]) + "\n"
+ else:
+ members = ""
+
+ if len(typedef) > 0:
+ typedef = "\n".join([" " + s for s in typedef]) + "\n\n"
+ else:
+ typedef = ""
+
+ name = self.name
+ if self.name in _reserved_keywords: name = self.name + "_"
+
+ return \
+'''
+namespace error {
+class %s
+ : public xpp::generic::error<%s,
+ %s>
+{
+ public:
+%s\
+ using xpp::generic::error<%s, %s>::error;
+
+ virtual ~%s(void) {}
+
+%s
+ static std::string description(void)
+ {
+ return std::string("%s");
+ }
+%s\
+}; // class %s
+} // namespace error
+''' % (self.get_name(), # class %s
+ self.get_name(), # : public xpp::generic::error<%s,
+ self.c_name, # %s>
+ typedef,
+ self.get_name(), self.c_name, # using xpp::generic::error<%s, %s>::error;
+ self.get_name(), # virtual ~%s(void) {}
+ opcode_accessor,
+ self.opcode_name, # static constexpr const char * opcode_literal
+ members,
+ self.get_name()) # // class %s
--- /dev/null
+import sys # stderr
+
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext, \
+ _reserved_keywords
+
+from resource_classes import _resource_classes
+
+_field_accessor_template_specialization = \
+'''\
+template<typename Connection>
+template<>
+%s
+%s<Connection>::%s<%s>(void) const
+{
+ return %s;
+}\
+'''
+
+_templates = {}
+
+_templates['field_accessor_template'] = \
+'''\
+ template<typename ReturnType = %s, typename ... Parameter>
+ ReturnType
+ %s(Parameter && ... parameter) const
+ {
+ using make = xpp::generic::factory::make<Connection,
+ decltype((*this)->%s),
+ ReturnType,
+ Parameter ...>;
+ return make()(this->m_c,
+ (*this)->%s,
+ std::forward<Parameter>(parameter) ...);
+ }\
+'''
+
+def _field_accessor_template(c_type, method_name, member):
+ return _templates['field_accessor_template'] % \
+ ( c_type
+ , method_name
+ , member
+ , member
+ )
+
+_templates['event_dispatcher_class'] = \
+'''\
+namespace event {
+
+template<typename Connection>
+class dispatcher
+{
+ public:
+%s\
+%s\
+
+ template<typename Handler>
+ bool
+ operator()(Handler%s,
+ const std::shared_ptr<xcb_generic_event_t> &%s) const
+ {\
+%s
+ return false;
+ }
+
+%s\
+}; // class dispatcher
+
+} // namespace event
+'''
+
+def _event_dispatcher_class(typedef, ctors, switch, members, has_events):
+ return _templates['event_dispatcher_class'] % \
+ ( typedef
+ , ctors
+ , " handler" if has_events else ""
+ , " event" if has_events else ""
+ , switch if has_events else ""
+ , members
+ )
+
+def event_dispatcher_class(namespace, cppevents):
+ ns = get_namespace(namespace)
+
+ ctor_name = "dispatcher"
+
+ typedef = []
+ ctors = []
+ members = []
+
+ opcode_switch = "event->response_type & ~0x80"
+ typedef = [ "typedef xpp::%s::extension extension;\n" % ns ]
+
+ members = \
+ [ "protected:"
+ , " Connection m_c;"
+ ]
+
+ ctors = \
+ [ "template<typename C>"
+ , "%s(C && c)" % ctor_name
+ , " : m_c(std::forward<C>(c))"
+ , "{}"
+ ]
+
+ # >>> if begin <<<
+ if namespace.is_ext:
+ # XXX: The xkb extension contains the sub-event in the member pad0
+ if ns == "xkb":
+ opcode_switch = "event->pad0"
+ else:
+ opcode_switch = "(event->response_type & ~0x80) - m_first_event"
+
+ members += [ " uint8_t m_first_event;" ]
+
+ ctors = \
+ [ "template<typename C>"
+ , "%s(C && c, uint8_t first_event)" % (ctor_name)
+ , " : m_c(std::forward<C>(c))"
+ , " , m_first_event(first_event)"
+ , "{}"
+ , ""
+ , "template<typename C>"
+ , "%s(C && c, const xpp::%s::extension & extension)" % (ctor_name, ns)
+ , " : %s(std::forward<C>(c), extension->first_event)" % ctor_name
+ , "{}"
+ ]
+
+ # >>> if end <<<
+
+ if len(typedef) > 0:
+ typedef = "\n".join([" " + s for s in typedef]) + "\n"
+ else:
+ typedef = ""
+
+ if len(ctors) > 0:
+ ctors = "\n".join([(" " if len(s) > 0 else "") + s for s in ctors]) + "\n"
+ else:
+ ctors = ""
+
+ if len(members) > 0:
+ members = "\n".join([" " + s for s in members]) + "\n"
+ else:
+ members = ""
+
+ switch = event_switch_cases(cppevents, opcode_switch, "handler", "event", namespace)
+
+ return _event_dispatcher_class(typedef,
+ ctors,
+ switch,
+ members,
+ len(cppevents) > 0)
+
+def event_switch_cases(cppevents, arg_switch, arg_handler, arg_event, ns):
+ cases = ""
+ first_event_arg = ", m_first_event" if ns.is_ext else ""
+ templ = [ " case %s:"
+ , " %s(" % arg_handler + "%s<Connection>" + "(m_c%s, %s));" % (first_event_arg, arg_event)
+ , " return true;"
+ , ""
+ , ""
+ ]
+
+ distinct_events = [[]]
+ for e in cppevents:
+ done = False
+ for l in distinct_events:
+ if e in l:
+ continue
+ else:
+ l.append(e)
+ done = True
+ break
+
+ if not done:
+ distinct_events.append([e])
+ else:
+ continue
+
+ for l in distinct_events:
+ cases += "\n switch (%s) {\n\n" % arg_switch
+ for e in l:
+ cases += "\n".join(templ) % (e.opcode_name, e.scoped_name())
+ cases += " };\n"
+
+ return cases if len(cppevents) > 0 else ""
+
+########## EVENT ##########
+
+class CppEvent(object):
+ def __init__(self, opcode, opcode_name, c_name, namespace, name, fields):
+ self.opcode = opcode
+ self.opcode_name = opcode_name
+ self.c_name = c_name
+ self.namespace = namespace
+ self.fields = fields
+
+ self.names = list(map(str.lower, _n_item(name[-1], True)))
+ self.name = "_".join(self.names)
+
+ self.nssopen = ""
+ self.nssclose = ""
+ self.scope = []
+ for name in self.names[0:-1]:
+ if name in _reserved_keywords: name += "_"
+ self.nssopen += " namespace %s {" % name
+ self.nssclose += " };"
+ self.scope.append(name)
+
+ def __cmp__(self, other):
+ if self.opcode == other.opcode:
+ return 0
+ elif self.opcode < other.opcode:
+ return -1
+ else:
+ return 1
+
+ def get_name(self):
+ return _reserved_keywords.get(self.name, self.name)
+
+
+ def scoped_name(self):
+ ns = get_namespace(self.namespace)
+ return "xpp::" + ns + "::event::" + self.get_name()
+
+ def make_class(self):
+ member_accessors = []
+ member_accessors_special = []
+ for field in self.fields:
+ if field.field_type[-1] in _resource_classes:
+ template_name = field.field_name.capitalize()
+ c_type = field.c_field_type
+ method_name = field.field_name.lower()
+ if (method_name == self.get_name()
+ or method_name in _reserved_keywords):
+ method_name += "_"
+ member = field.c_field_name
+
+ member_accessors.append(_field_accessor_template(c_type, method_name, member))
+
+ ns = get_namespace(self.namespace)
+
+ extension = "xpp::%s::extension" % ns
+
+ ctor = \
+ [ "template<typename C>"
+ , "%s(C && c," % self.get_name()
+ , (" " * len(self.get_name())) + " const std::shared_ptr<xcb_generic_event_t> & event)"
+ , " : base(event)"
+ , " , m_c(std::forward<C>(c))"
+ , "{}"
+ ]
+
+ m_first_event = ""
+
+ typedef = [ "typedef xpp::%s::extension extension;" % ns ]
+
+ description = \
+ [ "static std::string description(void)"
+ , "{"
+ , " return std::string(\"%s\");" % self.opcode_name
+ , "}"
+ ]
+
+ opcode_accessor = \
+ [ "static uint8_t opcode(void)"
+ , "{"
+ , " return %s;" % self.opcode_name
+ , "}"
+ ]
+
+ first_event = []
+
+ if self.namespace.is_ext:
+ opcode_accessor += \
+ [ ""
+ , "static uint8_t opcode(uint8_t first_event)"
+ , "{"
+ , " return first_event + opcode();"
+ , "}"
+ , ""
+ , "static uint8_t opcode(const xpp::%s::extension & extension)" % ns
+ , "{"
+ , " return opcode(extension->first_event);"
+ , "}"
+ ]
+
+ first_event = \
+ [ "uint8_t first_event(void)"
+ , "{"
+ , " return m_first_event;"
+ , "}"
+ ]
+
+ ctor = \
+ [ "template<typename C>"
+ , "%s(C && c," % self.get_name()
+ , (" " * len(self.get_name())) + " uint8_t first_event,"
+ , (" " * len(self.get_name())) + " const std::shared_ptr<xcb_generic_event_t> & event)"
+ , " : base(event)"
+ , " , m_c(std::forward<C>(c))"
+ , " , m_first_event(first_event)"
+ , "{}"
+ ]
+
+ m_first_event = " const uint8_t m_first_event;\n"
+
+ if len(opcode_accessor) > 0:
+ opcode_accessor = "\n".join([" " + s for s in opcode_accessor]) + "\n"
+ else:
+ opcode_accessor = ""
+
+ if len(ctor) > 0:
+ ctor = "\n".join([" " + s for s in ctor]) + "\n"
+ else:
+ ctor = ""
+
+ if len(typedef) > 0:
+ typedef = "\n".join([" " + s for s in typedef]) + "\n\n"
+ else:
+ typedef = ""
+
+ if len(member_accessors) > 0:
+ member_accessors = "\n" + "\n\n".join(member_accessors) + "\n\n"
+ member_accessors_special = "\n" + "\n\n".join(member_accessors_special) + "\n\n"
+ else:
+ member_accessors = ""
+ member_accessors_special = ""
+
+ if len(description) > 0:
+ description = "\n" + "\n".join([" " + s for s in description]) + "\n"
+ else:
+ description = ""
+
+ if len(first_event) > 0:
+ first_event = "\n" + "\n".join([" " + s for s in first_event]) + "\n"
+ else:
+ first_event = ""
+
+ return \
+'''
+namespace event {
+template<typename Connection>
+class %s
+ : public xpp::generic::event<%s>
+{
+ public:
+%s\
+ typedef xpp::generic::event<%s> base;
+
+%s\
+
+ virtual ~%s(void) {}
+
+%s\
+%s\
+%s\
+%s\
+ protected:
+ Connection m_c;
+%s\
+}; // class %s
+%s\
+} // namespace event
+''' % (self.get_name(), # class %s
+ self.c_name, # %s>
+ typedef,
+ self.c_name, # typedef xpp::generic::event<%s>::base;
+ ctor,
+ self.get_name(), # virtual ~%s(void) {}
+ opcode_accessor,
+ description,
+ first_event,
+ member_accessors,
+ m_first_event,
+ self.get_name(), # // class %s
+ member_accessors_special)
--- /dev/null
+from utils import _n, _ext, _n_item, get_namespace
+from resource_classes import _resource_classes
+
+_templates = {}
+
+_templates['reply_class'] = \
+'''\
+namespace reply {
+
+namespace detail {
+
+template<typename Connection,
+ typename Check,
+ typename CookieFunction>
+class %s
+ : public xpp::generic::reply<%s<Connection, Check, CookieFunction>,
+ Connection,
+ Check,
+ SIGNATURE(%s_reply),
+ CookieFunction>
+{
+ public:
+ typedef xpp::generic::reply<%s<Connection, Check, CookieFunction>,
+ Connection,
+ Check,
+ SIGNATURE(%s_reply),
+ CookieFunction>
+ base;
+
+ template<typename C, typename ... Parameter>
+ %s(C && c, Parameter && ... parameter)
+ : base(std::forward<C>(c), std::forward<Parameter>(parameter) ...)
+ {}
+
+%s\
+%s\
+}; // class %s
+
+} // namespace detail
+
+namespace checked {
+template<typename Connection>
+using %s = detail::%s<
+ Connection, xpp::generic::checked_tag,
+ SIGNATURE(%s)>;
+} // namespace checked
+
+namespace unchecked {
+template<typename Connection>
+using %s = detail::%s<
+ Connection, xpp::generic::unchecked_tag,
+ SIGNATURE(%s_unchecked)>;
+} // namespace unchecked
+
+} // namespace reply
+'''
+
+def _reply_class(name, c_name, ns, cookie, accessors):
+ return _templates['reply_class'] % \
+ ( name
+ , name # base class
+ , c_name # %s_reply
+ , name # typedef
+ , c_name # %s_reply
+ , name # c'tor
+ , cookie.make_static_getter()
+ , accessors
+ , name # // class %s
+ , name # checked { using %s =
+ , name # checked { detail::%s
+ , c_name # checked { SIGNATURE
+ , name # unchecked { using %s =
+ , name # unchecked { detail::%s
+ , c_name # unchecked { SIGNATURE
+ )
+
+_templates['reply_member_accessor'] = \
+'''\
+ template<typename ReturnType = %s, typename ... Parameter>
+ ReturnType
+ %s(Parameter && ... parameter)
+ {
+ using make = xpp::generic::factory::make<Connection,
+ decltype(this->get()->%s),
+ ReturnType,
+ Parameter ...>;
+ return make()(this->m_c,
+ this->get()->%s,
+ std::forward<Parameter>(parameter) ...);
+ }
+'''
+
+def _reply_member_accessor(request_name, name, c_type, template_type):
+ return _templates['reply_member_accessor'] % \
+ ( c_type
+ , name
+ , name
+ , name
+ )
+
+class CppReply(object):
+ def __init__(self, namespace, name, cookie, reply, accessors, parameter_list):
+ self.namespace = namespace
+ self.name = name
+ self.reply = reply
+ self.cookie = cookie
+ self.accessors = accessors
+ self.parameter_list = parameter_list
+ self.request_name = _ext(_n_item(self.name[-1]))
+ self.c_name = "xcb" \
+ + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+ + "_" + self.request_name
+
+ def make_accessors(self):
+ return "\n".join(["\n%s\n" % a for a in self.accessors])
+
+ def make(self):
+ accessors = [self.make_accessors()]
+ naccessors = len(self.accessors)
+
+ for field in self.reply.fields:
+ if (field.field_type[-1] in _resource_classes
+ and not field.type.is_list
+ and not field.type.is_container):
+
+ naccessors = naccessors + 1
+
+ name = field.field_name.lower()
+ c_type = field.c_field_type
+ template_type = field.field_name.capitalize()
+
+ accessors.append(_reply_member_accessor(self.request_name, name, c_type, template_type))
+
+ result = ""
+ result += _reply_class(
+ self.request_name, self.c_name, get_namespace(self.namespace),
+ self.cookie, "\n".join(accessors))
+ return result
--- /dev/null
+# vim: set ts=4 sws=4 sw=4:
+
+# from utils import *
+from utils import _n, _ext, _n_item, get_namespace
+from parameter import *
+from resource_classes import _resource_classes
+from cppreply import CppReply
+from cppcookie import CppCookie
+
+_templates = {}
+
+_templates['void_request_function'] = \
+'''\
+template<typename Connection, typename ... Parameter>
+void
+%s_checked(Connection && c, Parameter && ... parameter)
+{
+ xpp::generic::check<Connection, xpp::%s::error::dispatcher>(
+ std::forward<Connection>(c),
+ %s_checked(
+ std::forward<Connection>(c),
+ std::forward<Parameter>(parameter) ...));
+}
+
+template<typename ... Parameter>
+void
+%s(Parameter && ... parameter)
+{
+ %s(std::forward<Parameter>(parameter) ...);
+}
+'''
+
+def _void_request_function(ns, name, c_name):
+ return _templates['void_request_function'] % \
+ ( name
+ , ns
+ , c_name
+ , name
+ , c_name
+ )
+
+_templates['reply_request_function'] = \
+'''\
+template<typename Connection, typename ... Parameter>
+reply::checked::%s<Connection>
+%s(Connection && c, Parameter && ... parameter)
+{
+ return reply::checked::%s<Connection>(
+ std::forward<Connection>(c), std::forward<Parameter>(parameter) ...);
+}
+
+template<typename Connection, typename ... Parameter>
+reply::unchecked::%s<Connection>
+%s_unchecked(Connection && c, Parameter && ... parameter)
+{
+ return reply::unchecked::%s<Connection>(
+ std::forward<Connection>(c), std::forward<Parameter>(parameter) ...);
+}
+'''
+
+def _reply_request_function(name):
+ return _templates['reply_request_function'] % \
+ ( name
+ , name
+ , name
+ , name
+ , name
+ , name)
+
+_templates['inline_reply_class'] = \
+'''\
+ template<typename ... Parameter>
+ auto
+ %s(Parameter && ... parameter) const
+ -> reply::checked::%s<Connection>
+ {
+ return xpp::%s::%s(
+ connection(),
+ %s\
+ std::forward<Parameter>(parameter) ...);
+ }
+
+ template<typename ... Parameter>
+ auto
+ %s_unchecked(Parameter && ... parameter) const
+ -> reply::unchecked::%s<Connection>
+ {
+ return xpp::%s::%s_unchecked(
+ connection(),
+ %s\
+ std::forward<Parameter>(parameter) ...);
+ }
+'''
+
+def _inline_reply_class(request_name, method_name, member, ns):
+ return _templates['inline_reply_class'] % \
+ ( method_name
+ , request_name
+ , ns
+ , request_name
+ , member
+ , method_name
+ , request_name
+ , ns
+ , request_name
+ , member
+ )
+
+_templates['inline_void_class'] = \
+'''\
+ template<typename ... Parameter>
+ void
+ %s_checked(Parameter && ... parameter) const
+ {
+ xpp::%s::%s_checked(connection(),
+ %s\
+ std::forward<Parameter>(parameter) ...);
+ }
+
+ template<typename ... Parameter>
+ void
+ %s(Parameter && ... parameter) const
+ {
+ xpp::%s::%s(connection(),
+ %s\
+ std::forward<Parameter>(parameter) ...);
+ }
+'''
+
+def _inline_void_class(request_name, method_name, member, ns):
+ return _templates['inline_void_class'] % \
+ ( method_name
+ , ns
+ , request_name
+ , member
+ , method_name
+ , ns
+ , request_name
+ , member
+ )
+
+_replace_special_classes = \
+ { "gcontext" : "gc" }
+
+def replace_class(method, class_name):
+ cn = _replace_special_classes.get(class_name, class_name)
+ return method.replace("_" + cn, "")
+
+class CppRequest(object):
+ def __init__(self, request, name, is_void, namespace, reply):
+ self.request = request
+ self.name = name
+ self.request_name = _ext(_n_item(self.request.name[-1]))
+ self.is_void = is_void
+ self.namespace = namespace
+ self.reply = reply
+ self.c_namespace = \
+ "" if namespace.header.lower() == "xproto" \
+ else get_namespace(namespace)
+ self.accessors = []
+ self.parameter_list = ParameterList()
+
+ self.c_name = "xcb" \
+ + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+ + "_" + self.request_name
+
+ def add(self, param):
+ self.parameter_list.add(param)
+
+ def make_wrapped(self):
+ self.parameter_list.make_wrapped()
+
+ def make_class(self):
+ cppcookie = CppCookie(self.namespace, self.is_void, self.request.name, self.reply, self.parameter_list)
+
+ if self.is_void:
+ void_functions = cppcookie.make_void_functions()
+ if len(void_functions) > 0:
+ return void_functions
+ else:
+ return _void_request_function(get_namespace(self.namespace), self.request_name, self.c_name)
+
+ else:
+ cppreply = CppReply(self.namespace, self.request.name, cppcookie, self.reply, self.accessors, self.parameter_list)
+ return cppreply.make() + "\n\n" + _reply_request_function(self.request_name)
+
+ def make_object_class_inline(self, is_connection, class_name=""):
+ member = ""
+ method_name = self.name
+ if not is_connection:
+ member = "resource(),\n"
+ method_name = replace_class(method_name, class_name)
+
+ if self.is_void:
+ return _inline_void_class(self.request_name, method_name, member, get_namespace(self.namespace))
+ else:
+ return _inline_reply_class(self.request_name, method_name, member, get_namespace(self.namespace))
--- /dev/null
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext
+
+class ExtensionClass(object):
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+ def make_class(self):
+ # if not self.namespace.is_ext:
+ # return ""
+ # else:
+ ns = get_namespace(self.namespace)
+ if self.namespace.is_ext:
+ base = "\n : public xpp::generic::extension<extension, &xcb_%s_id>\n" % ns
+ ctor = " using base = xpp::generic::extension<extension, &xcb_%s_id>;\n" % ns + \
+ " using base::base;\n"
+ else:
+ base = " "
+ ctor = ""
+
+ return \
+'''\
+template<typename Derived, typename Connection>
+class interface;
+
+namespace event { template<typename Connection> class dispatcher; }
+namespace error { class dispatcher; }
+
+class extension%s{
+ public:
+%s\
+ template<typename Derived, typename Connection>
+ using interface = xpp::%s::interface<Derived, Connection>;
+ template<typename Connection>
+ using event_dispatcher = xpp::%s::event::dispatcher<Connection>;
+ using error_dispatcher = xpp::%s::error::dispatcher;
+};\
+''' % (base,
+ ctor,
+ ns, # typedef xpp::interface::%s interface;
+ ns, # typedef xpp::event::dispatcher::%s dispatcher;
+ ns) # typedef xpp::error::dispatcher::%s dispatcher;
--- /dev/null
+# vim: set ts=4 sws=4 sw=4:
+
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext
+
+from cppevent import event_dispatcher_class
+from cpperror import error_dispatcher_class
+
+_templates = {}
+
+_templates['interface_class'] = \
+"""\
+template<typename Derived, typename Connection>
+class interface
+{
+ protected:
+ Connection
+ connection(void) const
+ {
+ return static_cast<const Derived *>(this)->connection();
+ }
+
+ public:
+%s\
+
+ virtual ~interface(void) {}
+
+ const interface<Derived, Connection> &
+ %s(void)
+ {
+ return *this;
+ }
+
+%s\
+}; // class interface
+"""
+
+_ignore_events = \
+ { "XCB_PRESENT_GENERIC" }
+
+########## INTERFACECLASS ##########
+
+class InterfaceClass(object):
+ def __init__(self):
+ self.requests = []
+ self.events = []
+ self.errors = []
+
+ def add(self, request):
+ self.requests.append(request)
+
+ def add_event(self, event):
+ if event.opcode_name not in _ignore_events:
+ self.events.append(event)
+
+ def add_error(self, error):
+ self.errors.append(error)
+
+ def set_namespace(self, namespace):
+ self.namespace = namespace
+
+ def make_proto(self):
+ ns = get_namespace(self.namespace)
+ methods = ""
+ for request in self.requests:
+ methods += request.make_object_class_inline(True) + "\n\n"
+
+ typedef = []
+ if self.namespace.is_ext:
+ typedef = [ "typedef xpp::%s::extension extension;" % ns ]
+
+ if len(typedef) > 0:
+ typedef = "".join([" " + s for s in typedef]) + "\n\n"
+ else:
+ typedef = ""
+
+
+ return (_templates['interface_class'] \
+ % (typedef, ns, methods)) + \
+ '\n' + event_dispatcher_class(self.namespace, self.events) + \
+ '\n' + error_dispatcher_class(self.namespace, self.errors)
+
+########## INTERFACECLASS ##########
--- /dev/null
+# vim: set ts=4 sws=4 sw=4:
+
+import sys # stderr
+import copy # deepcopy
+
+from utils import \
+ get_namespace, \
+ get_ext_name, \
+ _n_item, \
+ _ext
+
+class ObjectClass(object):
+ def __init__(self, name):
+ self.name = name
+ self.requests = []
+
+ def add(self, request):
+ if (len(request.parameter_list.parameter) > 0
+ and request.parameter_list.parameter[0].c_type == self.c_name):
+ request_copy = copy.deepcopy(request)
+ request_copy.parameter_list.parameter.pop(0)
+ request_copy.make_wrapped()
+ self.requests.append(request_copy)
+
+ def set_namespace(self, namespace):
+ self.namespace = namespace
+ name = (get_namespace(namespace) + "_") if namespace.is_ext else ""
+ self.c_name = "xcb_%s_t" % (name + self.name.lower())
+
+ def make_inline(self):
+ ns = get_namespace(self.namespace)
+ name = self.name.lower()
+ c_name = self.c_name
+ methods = ""
+
+ for request in self.requests:
+ methods += request.make_object_class_inline(False, self.name.lower()) + "\n\n"
+
+ if methods == "":
+ return ""
+ else:
+ return \
+"""\
+template<typename Derived, typename Connection>
+class %s
+{
+ protected:
+ Connection
+ connection(void) const
+ {
+ return static_cast<const Derived *>(this)->connection();
+ }
+
+ const %s &
+ resource(void) const
+ {
+ return static_cast<const Derived *>(this)->resource();
+ }
+
+ public:
+ virtual ~%s(void) {}
+
+%s
+}; // class %s
+""" % (name, # class %s
+ c_name, # %s resource(void) { ... }
+ name, # virtual ~%s(void)
+ methods,
+ name) # }; // class %s
--- /dev/null
+# vim: set ts=4 sws=4 sw=4:
+
+import sys # stderr
+
+_templates = {}
+
+_templates['initializer'] = \
+'''\
+typedef typename value_type<%s, ! std::is_pointer<%s>::value>::type
+ vector_type;
+std::vector<vector_type> %s =
+ { value_iterator<%s>(%s), value_iterator<%s>(%s) };
+'''
+
+def _initializer(iter_type, c_name, iter_begin, iter_end):
+ return _templates['initializer'] % \
+ ( iter_type
+ , iter_type
+ , c_name
+ , iter_type
+ , iter_begin
+ , iter_type
+ , iter_end
+ )
+
+class ParameterList(object):
+ def __init__(self):
+ self.want_wrap = False
+ self.has_defaults = False
+ self.parameter = []
+ self.wrap_calls = []
+ self.wrap_protos = []
+ self.iter_calls = []
+ self.iter_2nd_lvl_calls = []
+ self.iter_protos = []
+ self.templates = []
+ self.iterator_templates = []
+ self.initializer = []
+
+ def add(self, param):
+ self.has_defaults = param.default != None
+ self.parameter.append(param)
+
+ def comma(self):
+ return "" if len(self.parameter) == 0 else ", "
+
+ def is_reordered(self):
+ tmp = sorted(self.parameter, key=lambda p: p.default or '')
+ return tmp != self.parameter
+
+ def calls(self, sort, params=None):
+ ps = self.parameter if params == None else params
+ if sort:
+ tmp = sorted(ps, key=lambda p: p.default or '')
+ ps = tmp
+ calls = [p.call() for p in ps]
+ return ", ".join(calls)
+
+ def protos(self, sort, defaults, params=None):
+ if defaults: sort = True
+ ps = self.parameter if params == None else params
+ if sort:
+ tmp = sorted(ps, key=lambda p: p.default or '')
+ ps = tmp
+ protos = [p.proto(defaults) for p in ps]
+ return ", ".join(protos)
+
+ def iterator_initializers(self):
+ return self.initializer
+
+ def make_wrapped(self):
+ self.wrap_calls = []
+ self.wrap_protos = []
+ self.iter_calls = []
+ self.iter_2nd_lvl_calls = []
+ self.iter_protos = []
+ self.initializer = []
+ self.templates = []
+ self.iterator_templates = []
+
+ lenfields = {}
+ # if a parameter is removed, take reduced parameter size into account
+ adjust = 0
+ for index, param in enumerate(self.parameter):
+ prev = index - adjust - 1
+
+ if param.field.type.is_list:
+ name = param.field.type.expr.lenfield_name
+ if name in lenfields:
+ lenfields[name].append(param.c_name)
+ else:
+ lenfields[name] = [ param.c_name ]
+
+ # sys.stderr.write("list: %s %s\n\n"
+ # % ( param.field.type.expr.lenfield_type
+ # , param.field.type.expr.lenfield_name
+ # ))
+
+ # SetGamma: takes 1 size, but 3 value lists
+ # if param.field.type.is_list and prev >= 0:
+ if (param.is_const and param.is_pointer
+ and prev >= 0
+ and self.parameter[prev].c_name == param.c_name + "_len"):
+
+ adjust = adjust + 1
+ self.want_wrap = True
+ self.wrap_calls.pop(prev)
+ self.wrap_protos.pop(prev)
+ self.iter_calls.pop(prev)
+ self.iter_2nd_lvl_calls.pop(prev)
+ self.iter_protos.pop(prev)
+
+ prev_type = self.parameter[prev].c_type
+ if param.c_type == 'char':
+
+ def append_proto_string(list):
+ list.append(Parameter(None, \
+ c_type='const std::string &',
+ c_name=param.c_name))
+
+ def append_call_string(list):
+ list.append(Parameter(None, \
+ c_name="static_cast<" + prev_type + ">(" \
+ + param.c_name + '.length())'))
+
+ list.append(Parameter(None, \
+ c_name=param.c_name + '.c_str()'))
+
+ append_proto_string(self.wrap_protos)
+ append_proto_string(self.iter_protos)
+ append_call_string(self.wrap_calls)
+ append_call_string(self.iter_calls)
+ append_call_string(self.iter_2nd_lvl_calls)
+
+ else:
+ param_type = param.c_type
+ if param_type == "void":
+ param_type = "Type_" + str(index)
+ self.templates.append(param_type)
+
+ prev_type = self.parameter[prev].c_type
+
+ ### std::vector
+ self.wrap_protos.append(Parameter(None, \
+ c_type='const std::vector<' + param_type + '> &',
+ c_name=param.c_name))
+
+ self.wrap_calls.append(Parameter(None, \
+ c_name="static_cast<" + prev_type + ">(" \
+ + param.c_name + '.size())'))
+
+ self.wrap_calls.append(Parameter(None, \
+ c_name=param.c_name + '.data()'))
+
+ ### Iterator
+ iter_type = param.c_name.capitalize() + "_Iterator"
+ iter_begin = param.c_name + "_begin"
+ iter_end = param.c_name + "_end"
+
+ if len(self.templates) > 0:
+ self.templates[-1] += " = typename " + iter_type + "::value_type"
+ self.iterator_templates.append(iter_type)
+
+ self.iter_protos.append(Parameter(None, \
+ c_type=iter_type,
+ c_name=iter_begin))
+
+ self.iter_protos.append(Parameter(None, \
+ c_type=iter_type,
+ c_name=iter_end))
+
+ self.iter_calls.append(Parameter(None, \
+ c_name="static_cast<" + prev_type + ">(" \
+ + param.c_name + '.size())'))
+
+ self.iter_calls.append(Parameter(None, \
+ c_name='const_cast<const vector_type *>(' \
+ + param.c_name + '.data())'))
+
+ self.iter_2nd_lvl_calls.append(Parameter(None, \
+ c_name=iter_begin))
+
+ self.iter_2nd_lvl_calls.append(Parameter(None, \
+ c_name=iter_end))
+
+# vector_type = \
+# '''\
+# typename value_type<%s,
+# ! std::is_pointer<%s>::value
+# >::type\
+# ''' % (iter_type, iter_type)
+
+ # self.initializer.append( \
+ # "std::vector<%s> %s = { value_iterator<%s>(%s), \
+ # value_iterator<%s>(%s) };" \
+ # % (vector_type, param.c_name,
+ # iter_type, iter_begin,
+ # iter_type, iter_end))
+
+ self.initializer.append(
+ _initializer(iter_type, param.c_name, iter_begin, iter_end))
+
+ else:
+ self.wrap_calls.append(param)
+ self.wrap_protos.append(param)
+ self.iter_calls.append(param)
+ self.iter_2nd_lvl_calls.append(param)
+ self.iter_protos.append(param)
+
+ # end: for index, param in enumerate(self.parameter):
+
+ for k, v in list(lenfields.items()):
+ if len(v) > 1:
+ sys.stderr.write("list: %s, %s\n" % (k, v))
+
+
+ def wrapped_calls(self, sort):
+ return self.calls(sort, params=self.wrap_calls)
+
+ def wrapped_protos(self, sort, defaults):
+ return self.protos(sort, defaults, params=self.wrap_protos)
+
+ def iterator_calls(self, sort):
+ return self.calls(sort, params=self.iter_calls)
+
+ def iterator_2nd_lvl_calls(self, sort):
+ return self.calls(sort, params=self.iter_2nd_lvl_calls)
+
+ def iterator_protos(self, sort, defaults):
+ return self.protos(sort, defaults, params=self.iter_protos)
+
+
+
+_default_parameter_values = \
+ { "xcb_timestamp_t" : "XCB_TIME_CURRENT_TIME" }
+
+class Parameter(object):
+ def __init__(self, field, c_type="", c_name="", verbose=False):
+ self.field = field
+ if field != None:
+ self.c_type = field.c_field_type
+ self.c_name = field.c_field_name
+ self.is_const = field.c_field_const_type == "const " + field.c_field_type
+ self.is_pointer = field.c_pointer != " "
+ # self.serialize = field.type.need_serialize
+ self.default = _default_parameter_values.get(self.c_type)
+ self.with_default = True
+ if verbose:
+ sys.stderr.write("c_type: %s; c_name: %s; default: %s\n" \
+ % (self.c_type, self.c_name, self.default))
+
+ else:
+ self.c_type = c_type
+ self.c_name = c_name
+ self.is_const = False
+ self.is_pointer = False
+ # self.serialize = field.type.need_serialize
+ self.default = _default_parameter_values.get(self.c_type)
+ self.with_default = True
+
+ def call(self):
+ return self.c_name
+
+ def proto(self, with_default):
+ c_type = ("const " if self.is_const else "") \
+ + self.c_type \
+ + (" *" if self.is_pointer else "")
+ param = " = " + self.default if with_default and self.default != None else ""
+ return c_type + " " + self.c_name + param
--- /dev/null
+_resource_classes = \
+ {
+ ### XPROTO ###
+ "WINDOW"
+ , "PIXMAP"
+ , "CURSOR"
+ , "FONT"
+ , "GCONTEXT"
+ , "COLORMAP"
+ , "ATOM"
+ , "DRAWABLE"
+ , "FONTABLE"
+ ### XPROTO ###
+
+ ### DAMAGE ###
+ , "DAMAGE"
+ ### DAMAGE ###
+
+ ### GLX, RECORD, XVMC ###
+ , "CONTEXT"
+ ### GLX, RECORD, XVMC ###
+
+ ### GLX ###
+ # , "PIXMAP" # already in XPROTO
+ # , "CONTEXT"
+ , "PBUFFER"
+ # , "WINDOW" # already in XPROTO
+ , "FBCONFIG"
+ ### GLX ###
+
+ ### PRESENT ###
+ , "EVENT"
+ ### PRESENT ###
+
+ ### RANDR ###
+ , "MODE"
+ , "CRTC"
+ , "OUTPUT"
+ , "PROVIDER"
+ ### RANDR ###
+
+ ### RECORD ###
+ # , "CONTEXT"
+ ### RECORD ###
+
+ ### RENDER ###
+ , "GLYPHSET"
+ , "PICTURE"
+ , "PICTFORMAT"
+ ### RENDER ###
+
+ ### SHM ###
+ , "SEG"
+ ### SHM ###
+
+ ### SYNC ###
+ , "ALARM"
+ , "COUNTER"
+ , "FENCE"
+ ### SYNC ###
+
+ ### XFIXES ###
+ , "REGION"
+ , "BARRIER"
+ ### XFIXES ###
+
+ ### XPRINT ###
+ , "PCONTEXT"
+ ### XPRINT ###
+
+ ### XVMC ###
+ # , "CONTEXT"
+ , "SURFACE"
+ , "SUBPICTURE"
+ ### XVMC ###
+
+ ### XV ###
+ , "PORT"
+ , "ENCODING"
+ ### XV ###
+ }
--- /dev/null
+import re # compile
+
+_reserved_keywords = {'class' : '_class',
+ 'new' : '_new',
+ 'delete': '_delete',
+ 'default' : '_default',
+ 'private' : '_private',
+ 'explicit': '_explicit'}
+
+def get_namespace(namespace):
+ if namespace.is_ext:
+ return get_ext_name(namespace.ext_name)
+ else:
+ return "x"
+
+def get_ext_name(string):
+ return _ext(string)
+
+_cname_re = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
+_cname_special_cases = {'DECnet':'decnet'}
+
+def _n_item(string, parts=False):
+ '''
+ Does C-name conversion on a single string fragment.
+ The resulting string is a valid C-name
+ Uses a regexp with some hard-coded special cases.
+ '''
+ if string in _cname_special_cases:
+ return _cname_special_cases[string]
+ else:
+ split = _cname_re.finditer(string)
+ name_parts = [match.group(0) for match in split]
+ if parts:
+ return name_parts
+ else:
+ return '_'.join(name_parts)
+
+_extension_special_cases = ['XPrint', 'XCMisc', 'BigRequests']
+
+def _ext(string):
+ '''
+ Does C-name conversion on an extension name.
+ Has some additional special cases on top of _n_item.
+ '''
+ if string in _extension_special_cases:
+ return _n_item(string).lower()
+ else:
+ return string.lower()
+
+def _n(list, namespace):
+ '''
+ Does C-name conversion on a tuple of strings.
+ Different behavior depending on length of tuple, extension/not extension, etc.
+ Basically C-name converts the individual pieces, then joins with underscores.
+ '''
+ if len(list) == 1:
+ parts = list
+ elif len(list) == 2:
+ parts = [list[0], _n_item(list[1])]
+ elif namespace.is_ext:
+ parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]]
+ else:
+ parts = [list[0]] + [_n_item(i) for i in list[1:]]
+ return '_'.join(parts).lower()
--- /dev/null
+#ifndef XPP_ATOM_HPP
+#define XPP_ATOM_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class atom
+ : public xpp::generic::resource<Connection, xcb_atom_t,
+ xpp::x::atom, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_atom_t, Interfaces ...>;
+
+ public:
+ using base::base;
+ using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::atom<Connection, Interfaces ...>>
+{
+ typedef xcb_atom_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_ATOM_HPP
--- /dev/null
+#ifndef XPP_COLORMAP_HPP
+#define XPP_COLORMAP_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class colormap
+ : public xpp::generic::resource<Connection, xcb_colormap_t,
+ xpp::x::colormap, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_colormap_t,
+ xpp::x::colormap, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ colormap(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ colormap<Connection, Interfaces ...>
+ create(C && c, uint8_t alloc, xcb_window_t window, xcb_visualid_t visual)
+ {
+ return colormap(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_colormap_t & colormap)
+ {
+ xpp::x::create_colormap(c, alloc, colormap, window, visual);
+ },
+ [&](const Connection & c, const xcb_colormap_t & colormap)
+ {
+ xpp::x::free_colormap(c, colormap);
+ });
+ }
+
+ template<typename C>
+ static
+ colormap<Connection, Interfaces ...>
+ create_checked(C && c, uint8_t alloc,
+ xcb_window_t window, xcb_visualid_t visual)
+ {
+ return colormap(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_colormap_t & colormap)
+ {
+ xpp::x::create_colormap_checked(c, alloc, colormap, window, visual);
+ },
+ [&](const Connection & c, const xcb_colormap_t & colormap)
+ {
+ xpp::x::free_colormap_checked(c, colormap);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::colormap<Connection, Interfaces ...>>
+{
+ typedef xcb_colormap_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_COLORMAP_HPP
--- /dev/null
+#ifndef XPP_CONNECTION_HPP
+#define XPP_CONNECTION_HPP
+
+#include "core.hpp"
+#include "generic/factory.hpp"
+
+#include "xpp/proto/x.hpp"
+
+namespace xpp {
+
+namespace detail {
+
+template<typename Connection, typename ... Extensions>
+class interfaces
+ : public xpp::x::extension::interface<interfaces<Connection, Extensions ...>, Connection>
+ , public Extensions::template interface<interfaces<Connection, Extensions ...>, Connection> ...
+{
+ public:
+ Connection
+ connection(void) const
+ {
+ return static_cast<const Connection &>(*this);
+ }
+}; // class interfaces
+
+} // namespace detail
+
+template<typename ... Extensions>
+class connection
+ : public xpp::core
+ , public xpp::generic::error_dispatcher
+ , public detail::interfaces<connection<Extensions ...>, Extensions ...>
+ // private interfaces: extensions and error_dispatcher
+ , private xpp::x::extension
+ , private xpp::x::extension::error_dispatcher
+ , private Extensions ...
+ , private Extensions::error_dispatcher ...
+{
+ protected:
+ typedef connection<Extensions ...> self;
+
+
+ public:
+ template<typename ... Parameters>
+ explicit
+ connection(Parameters && ... parameters)
+ : xpp::core::core(std::forward<Parameters>(parameters) ...)
+ , detail::interfaces<connection<Extensions ...>, Extensions ...>(*this)
+ , Extensions(static_cast<xcb_connection_t *>(*this)) ...
+ , Extensions::error_dispatcher(static_cast<Extensions &>(*this).get()) ...
+ {
+ m_root_window = screen_of_display(default_screen())->root;
+ }
+
+ virtual
+ ~connection(void)
+ {}
+
+ virtual
+ operator xcb_connection_t *(void) const
+ {
+ return *(static_cast<const core &>(*this));
+ }
+
+ void
+ operator()(const std::shared_ptr<xcb_generic_error_t> & error) const
+ {
+ check<xpp::x::extension, Extensions ...>(error);
+ }
+
+ template<typename Extension>
+ const Extension &
+ extension(void) const
+ {
+ return static_cast<const Extension &>(*this);
+ }
+
+ // TODO
+ // virtual operator Display * const(void) const
+ // {
+ // }
+
+ template<typename Window = xcb_window_t>
+ Window
+ root(void)
+ {
+ using make = xpp::generic::factory::make<self, xcb_window_t, Window>;
+ return make()(*this, m_root_window);
+ }
+
+ template<typename Window = xcb_window_t>
+ Window
+ root(void) const
+ {
+ using make = xpp::generic::factory::make<self, xcb_window_t, Window>;
+ return make()(*this, m_root_window);
+ }
+
+ virtual
+ shared_generic_event_ptr
+ wait_for_event(void) const
+ {
+ try {
+ return core::wait_for_event();
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ check<xpp::x::extension, Extensions ...>(error);
+ }
+ // re-throw any exception caused by wait_for_event
+ throw;
+ }
+
+ virtual
+ shared_generic_event_ptr
+ wait_for_special_event(xcb_special_event_t * se) const
+ {
+ try {
+ return core::wait_for_special_event(se);
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ check<xpp::x::extension, Extensions ...>(error);
+ }
+ // re-throw any exception caused by wait_for_special_event
+ throw;
+ }
+
+ private:
+ xcb_window_t m_root_window;
+
+ template<typename Extension, typename Next, typename ... Rest>
+ void
+ check(const std::shared_ptr<xcb_generic_error_t> & error) const
+ {
+ check<Extension>(error);
+ check<Next, Rest ...>(error);
+ }
+
+ template<typename Extension>
+ void
+ check(const std::shared_ptr<xcb_generic_error_t> & error) const
+ {
+ using error_dispatcher = typename Extension::error_dispatcher;
+ auto & dispatcher = static_cast<const error_dispatcher &>(*this);
+ dispatcher(error);
+ }
+}; // class connection
+
+template<>
+template<typename ... Parameters>
+connection<>::connection(Parameters && ... parameters)
+ : xpp::core::core(std::forward<Parameters>(parameters) ...)
+ , detail::interfaces<connection<>>(*this)
+{
+ m_root_window = screen_of_display(static_cast<core &>(*this).default_screen())->root;
+}
+
+} // namespace xpp
+
+#endif // XPP_CONNECTION_HPP
--- /dev/null
+#ifndef XPP_CORE_HPP
+#define XPP_CORE_HPP
+
+#include <string>
+#include <memory>
+#include <stdexcept>
+#include <xcb/xcb.h>
+
+namespace xpp {
+
+class connection_error
+ : public std::runtime_error
+{
+ public:
+ connection_error(uint8_t code, const std::string & description)
+ : std::runtime_error(description + "(" + std::to_string(code) + ")")
+ , m_code(code)
+ , m_description(description)
+ {}
+
+ uint8_t
+ code(void)
+ {
+ return m_code;
+ }
+
+ std::string
+ description(void)
+ {
+ return m_description;
+ }
+
+ protected:
+ uint8_t m_code;
+ std::string m_description;
+};
+
+class core
+{
+ protected:
+ using shared_generic_event_ptr = std::shared_ptr<xcb_generic_event_t>;
+
+ int m_screen = 0;
+ // reference counting for xcb_connection_t
+ std::shared_ptr<xcb_connection_t> m_c;
+
+ shared_generic_event_ptr
+ dispatch(const std::string & producer, xcb_generic_event_t * event) const
+ {
+ if (event) {
+ if (event->response_type == 0) {
+ throw std::shared_ptr<xcb_generic_error_t>(
+ reinterpret_cast<xcb_generic_error_t *>(event));
+ }
+
+ return shared_generic_event_ptr(event, std::free);
+ }
+
+ check_connection();
+ throw std::runtime_error(producer + " failed");
+ }
+
+ public:
+ explicit
+ core(xcb_connection_t * c)
+ : m_c(std::shared_ptr<xcb_connection_t>(c, [](...) {}))
+ {}
+
+ template<typename ... ConnectionParameter>
+ explicit
+ core(xcb_connection_t * (*Connect)(ConnectionParameter ...),
+ ConnectionParameter ... connection_parameter)
+ : m_c(std::shared_ptr<xcb_connection_t>(
+ Connect(connection_parameter ...),
+ [&](void *) { disconnect(); }))
+ {}
+
+ // xcb_connect (const char *displayname, int *screenp)
+ explicit
+ core(const std::string & displayname = "")
+ : core(xcb_connect, displayname.c_str(), &m_screen)
+ {}
+
+ // xcb_connect_to_fd (int fd, xcb_auth_info_t *auth_info)
+ explicit
+ core(int fd, xcb_auth_info_t * auth_info)
+ : core(xcb_connect_to_fd, fd, auth_info)
+ {}
+
+ // xcb_connect_to_display_with_auth_info (
+ // const char *display, xcb_auth_info_t *auth, int *screen)
+ explicit
+ core(const std::string & display, xcb_auth_info_t * auth)
+ : core(xcb_connect_to_display_with_auth_info,
+ display.c_str(), auth, &m_screen)
+ {}
+
+ virtual
+ ~core(void)
+ {}
+
+ virtual
+ xcb_connection_t *
+ operator*(void) const
+ {
+ return m_c.get();
+ }
+
+ virtual
+ operator xcb_connection_t *(void) const
+ {
+ return m_c.get();
+ }
+
+ virtual
+ int
+ default_screen(void) const
+ {
+ return m_screen;
+ }
+
+ virtual
+ int
+ flush(void) const
+ {
+ return xcb_flush(m_c.get());
+ }
+
+ virtual
+ uint32_t
+ get_maximum_request_length(void) const
+ {
+ return xcb_get_maximum_request_length(m_c.get());
+ }
+
+ virtual
+ void
+ prefetch_maximum_request_length(void) const
+ {
+ xcb_prefetch_maximum_request_length(m_c.get());
+ }
+
+ virtual
+ shared_generic_event_ptr
+ wait_for_event(void) const
+ {
+ return dispatch("wait_for_event", xcb_wait_for_event(m_c.get()));
+ }
+
+ virtual
+ shared_generic_event_ptr
+ poll_for_event(void) const
+ {
+ return shared_generic_event_ptr(xcb_poll_for_event(m_c.get()), std::free);
+ }
+
+ virtual
+ shared_generic_event_ptr
+ poll_for_queued_event(void) const
+ {
+ return shared_generic_event_ptr(xcb_poll_for_queued_event(m_c.get()), std::free);
+ }
+
+ virtual
+ shared_generic_event_ptr
+ poll_for_special_event(xcb_special_event_t * se) const
+ {
+ return shared_generic_event_ptr(xcb_poll_for_special_event(m_c.get(), se), std::free);
+ }
+
+ virtual
+ shared_generic_event_ptr
+ wait_for_special_event(xcb_special_event_t * se) const
+ {
+ return dispatch("wait_for_special_event",
+ xcb_wait_for_special_event(m_c.get(), se));
+ }
+
+ virtual
+ xcb_special_event_t *
+ register_for_special_xge(xcb_extension_t * ext,
+ uint32_t eid,
+ uint32_t * stamp) const
+ {
+ return xcb_register_for_special_xge(m_c.get(), ext, eid, stamp);
+ }
+
+ virtual
+ void
+ unregister_for_special_event(xcb_special_event_t * se) const
+ {
+ xcb_unregister_for_special_event(m_c.get(), se);
+ }
+
+ virtual
+ std::shared_ptr<xcb_generic_error_t>
+ request_check(xcb_void_cookie_t cookie) const
+ {
+ return std::shared_ptr<xcb_generic_error_t>(
+ xcb_request_check(m_c.get(), cookie));
+ }
+
+ virtual
+ void
+ discard_reply(unsigned int sequence) const
+ {
+ xcb_discard_reply(m_c.get(), sequence);
+ }
+
+ // The result must not be freed.
+ // This storage is managed by the cache itself.
+ virtual
+ const xcb_query_extension_reply_t *
+ get_extension_data(xcb_extension_t * ext) const
+ {
+ return xcb_get_extension_data(m_c.get(), ext);
+ }
+
+ virtual
+ void
+ prefetch_extension_data(xcb_extension_t * ext) const
+ {
+ xcb_prefetch_extension_data(m_c.get(), ext);
+ }
+
+ virtual
+ const xcb_setup_t *
+ get_setup(void) const
+ {
+ return xcb_get_setup(m_c.get());
+ }
+
+ virtual
+ int
+ get_file_descriptor(void) const
+ {
+ return xcb_get_file_descriptor(m_c.get());
+ }
+
+ virtual
+ int
+ connection_has_error(void) const
+ {
+ return xcb_connection_has_error(m_c.get());
+ }
+
+ virtual
+ void
+ disconnect(void) const
+ {
+ xcb_disconnect(m_c.get());
+ }
+
+ // hostname, display, screen
+ virtual
+ std::tuple<std::string, int, int>
+ parse_display(const std::string & name) const
+ {
+ int screen = 0;
+ int display = 0;
+ char * host = NULL;
+ std::string hostname;
+
+ xcb_parse_display(name.c_str(), &host, &display, &screen);
+ if (host != NULL) {
+ hostname = std::string(host);
+ }
+
+ return std::make_tuple(hostname, display, screen);
+ }
+
+ virtual
+ uint32_t
+ generate_id(void) const
+ {
+ return xcb_generate_id(m_c.get());
+ }
+
+ xcb_screen_t *
+ screen_of_display(int screen)
+ {
+ xcb_screen_iterator_t iter;
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(m_c.get()));
+ for (; iter.rem; --screen, xcb_screen_next(&iter))
+ if (screen == 0)
+ return iter.data;
+
+ return NULL;
+ }
+
+ void
+ check_connection(void) const
+ {
+ switch (xcb_connection_has_error(m_c.get())) {
+ case XCB_CONN_ERROR:
+ throw(connection_error(
+ XCB_CONN_ERROR, "XCB_CONN_ERROR"));
+
+ case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
+ throw(connection_error(XCB_CONN_CLOSED_EXT_NOTSUPPORTED,
+ "XCB_CONN_CLOSED_EXT_NOTSUPPORTED"));
+
+ case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
+ throw(connection_error(XCB_CONN_CLOSED_MEM_INSUFFICIENT,
+ "XCB_CONN_CLOSED_MEM_INSUFFICIENT"));
+
+ case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
+ throw(connection_error(XCB_CONN_CLOSED_REQ_LEN_EXCEED,
+ "XCB_CONN_CLOSED_REQ_LEN_EXCEED"));
+
+ case XCB_CONN_CLOSED_PARSE_ERR:
+ throw(connection_error(XCB_CONN_CLOSED_PARSE_ERR,
+ "XCB_CONN_CLOSED_PARSE_ERR"));
+
+ case XCB_CONN_CLOSED_INVALID_SCREEN:
+ throw(connection_error(XCB_CONN_CLOSED_INVALID_SCREEN,
+ "XCB_CONN_CLOSED_INVALID_SCREEN"));
+ };
+ }
+}; // class core
+
+} // namespace xpp
+
+#endif // XPP_CORE_HPP
--- /dev/null
+#ifndef XPP_CURSOR_HPP
+#define XPP_CURSOR_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class cursor
+ : public xpp::generic::resource<Connection, xcb_cursor_t,
+ xpp::x::cursor, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_cursor_t,
+ xpp::x::cursor, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ cursor(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ cursor<Connection, Interfaces ...>
+ create(C && c,
+ xcb_pixmap_t source, xcb_pixmap_t mask,
+ uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+ uint16_t back_red, uint16_t back_green, uint16_t back_blue,
+ uint16_t x, uint16_t y)
+ {
+ return cursor(
+ std::forward<C>(c),
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::create_cursor(c, cursor,
+ source, mask,
+ fore_red, fore_green, fore_blue,
+ back_red, back_green, back_blue,
+ x, y);
+ },
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::free_cursor(c, cursor);
+ });
+ }
+
+ template<typename C>
+ static
+ cursor<Connection, Interfaces ...>
+ create_checked(C && c,
+ xcb_pixmap_t source, xcb_pixmap_t mask,
+ uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+ uint16_t back_red, uint16_t back_green, uint16_t back_blue,
+ uint16_t x, uint16_t y)
+ {
+ return cursor(
+ std::forward<C>(c),
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::create_cursor_checked(c, cursor,
+ source, mask,
+ fore_red, fore_green, fore_blue,
+ back_red, back_green, back_blue,
+ x, y);
+ },
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::free_cursor_checked(c, cursor);
+ });
+ }
+
+ template<typename C>
+ static
+ cursor<Connection, Interfaces ...>
+ create_glyph(C && c,
+ xcb_font_t source_font, xcb_font_t mask_font,
+ uint16_t source_char, uint16_t mask_char,
+ uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+ uint16_t back_red, uint16_t back_green, uint16_t back_blue)
+ {
+ return cursor(
+ std::forward<C>(c),
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::create_glyph_cursor(c, cursor,
+ source_font, mask_font,
+ source_char, mask_char,
+ fore_red, fore_green, fore_blue,
+ back_red, back_green, back_blue);
+ },
+ [](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::free_cursor(c, cursor);
+ });
+ }
+
+ template<typename C>
+ static
+ cursor<Connection, Interfaces ...>
+ create_glyph_checked(C && c,
+ xcb_font_t source_font, xcb_font_t mask_font,
+ uint16_t source_char, uint16_t mask_char,
+ uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+ uint16_t back_red, uint16_t back_green, uint16_t back_blue)
+ {
+ return cursor(
+ std::forward<C>(c),
+ [&](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::create_glyph_cursor_checked(c, cursor,
+ source_font, mask_font,
+ source_char, mask_char,
+ fore_red, fore_green, fore_blue,
+ back_red, back_green, back_blue);
+ },
+ [](Connection & c, const xcb_cursor_t & cursor)
+ {
+ xpp::x::free_cursor_checked(c, cursor);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::cursor<Connection, Interfaces ...>>
+{
+ typedef xcb_cursor_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_CURSOR_HPP
--- /dev/null
+#ifndef XPP_DRAWABLE_HPP
+#define XPP_DRAWABLE_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class drawable
+ : public xpp::generic::resource<Connection, xcb_drawable_t,
+ xpp::x::drawable, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_drawable_t,
+ xpp::x::drawable, Interfaces ...>;
+
+ public:
+ using base::base;
+ using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::drawable<Connection, Interfaces ...>>
+{
+ typedef xcb_drawable_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_DRAWABLE_HPP
--- /dev/null
+#ifndef XPP_EVENT_HPP
+#define XPP_EVENT_HPP
+
+#include <climits>
+#include <map>
+#include <vector>
+#include <unordered_map>
+
+#include "xpp/proto/x.hpp"
+
+#define MAX_PRIORITY UINT32_MAX
+
+namespace xpp {
+
+namespace event {
+
+namespace detail {
+
+class dispatcher {
+ public:
+ virtual ~dispatcher(void) {}
+ template<typename Event> void dispatch(const Event & e);
+}; // class dispatcher
+
+template<typename Event>
+class sink : virtual public dispatcher
+{
+ public:
+ virtual ~sink(void) {}
+ virtual void handle(const Event &) = 0;
+};
+
+} // namespace detail
+
+template<typename Event, typename ... Events>
+class sink
+ : public detail::sink<Event>
+ , public detail::sink<Events> ...
+{};
+
+template<typename Connection, typename ... Extensions>
+class registry
+ : public xpp::x::event::dispatcher<Connection>
+ , public Extensions::template event_dispatcher<Connection> ...
+{
+ public:
+ typedef unsigned int priority;
+
+ template<typename C>
+ explicit
+ registry(C && c)
+ : xpp::x::event::dispatcher<Connection>(std::forward<C>(c))
+ , Extensions::template event_dispatcher<Connection>(
+ std::forward<C>(c), c.template extension<Extensions>()) ...
+ , m_c(std::forward<C>(c))
+ {}
+
+ bool
+ dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+ {
+ return dispatch<xpp::x::extension, Extensions ...>(event);
+ }
+
+ template<typename Event, typename ... Rest>
+ void
+ attach(priority p, sink<Event, Rest ...> * s)
+ {
+ attach<sink<Event, Rest ...>, Event, Rest ...>(p, s);
+ }
+
+ template<typename Event, typename ... Rest>
+ void
+ detach(priority p, sink<Event, Rest ...> * s)
+ {
+ detach<sink<Event, Rest ...>, Event, Rest ...>(p, s);
+ }
+
+ private:
+ typedef std::multimap<priority, detail::dispatcher *> priority_map;
+
+ Connection m_c;
+ std::unordered_map<uint8_t, priority_map> m_dispatchers;
+
+ template<typename Event>
+ uint8_t opcode(const xpp::x::extension &) const
+ {
+ return Event::opcode();
+ }
+
+ template<typename Event, typename Extension>
+ uint8_t opcode(const Extension & extension) const
+ {
+ return Event::opcode(extension);
+ }
+
+ template<typename Event>
+ uint8_t opcode(void) const
+ {
+ return opcode<Event>(m_c.template extension<typename Event::extension>());
+ }
+
+ template<typename Event>
+ void
+ handle(const Event & event) const
+ {
+ auto it = m_dispatchers.find(opcode<Event>());
+ if (it != m_dispatchers.end()) {
+ for (auto & item : it->second) {
+ item.second->dispatch(event);
+ }
+ }
+ }
+
+ struct handler {
+ handler(const registry<Connection, Extensions ...> & registry)
+ : m_registry(registry)
+ {}
+
+ const registry<Connection, Extensions ...> & m_registry;
+
+ template<typename Event>
+ void
+ operator()(const Event & event) const
+ {
+ m_registry.handle(event);
+ }
+ };
+
+ template<typename Extension>
+ bool
+ dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+ {
+ typedef const typename Extension::template event_dispatcher<Connection> & dispatcher;
+ return static_cast<dispatcher>(*this)(handler(*this), event);
+ }
+
+ template<typename Extension, typename Next, typename ... Rest>
+ bool
+ dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+ {
+ dispatch<Extension>(event);
+ return dispatch<Next, Rest ...>(event);
+ }
+
+ template<typename Sink, typename Event>
+ void
+ attach(priority p, Sink * s)
+ {
+ attach(p, s, opcode<Event>());
+ }
+
+ template<typename Sink, typename Event, typename Next, typename ... Rest>
+ void
+ attach(priority p, Sink * s)
+ {
+ attach(p, s, opcode<Event>());
+ attach<Sink, Next, Rest ...>(p, s);
+ }
+
+ void attach(priority p, detail::dispatcher * d, uint8_t opcode)
+ {
+ m_dispatchers[opcode].emplace(p, d);
+ }
+
+ template<typename Sink, typename Event>
+ void
+ detach(priority p, Sink * s)
+ {
+ detach(p, s, opcode<Event>());
+ }
+
+ template<typename Sink, typename Event, typename Next, typename ... Rest>
+ void
+ detach(priority p, Sink * s)
+ {
+ detach(p, s, opcode<Event>());
+ detach<Sink, Next, Rest ...>(p, s);
+ }
+
+ void
+ detach(priority p, detail::dispatcher * d, uint8_t opcode)
+ {
+ auto it = m_dispatchers.find(opcode);
+
+ if (it == m_dispatchers.end()) {
+ return;
+ }
+
+ auto & prio_map = it->second;
+ const auto & prio_sink_pair = prio_map.equal_range(p);
+ for (auto it = prio_sink_pair.first; it != prio_sink_pair.second; ) {
+ if (d == it->second) {
+ it = prio_map.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+}; // xpp::event::source
+
+} // namespace event
+
+} // namespace xpp
+
+template<typename Event>
+void xpp::event::detail::dispatcher::dispatch(const Event & e)
+{
+ auto event_sink = dynamic_cast<xpp::event::detail::sink<Event> *>(this);
+ if (event_sink != nullptr) {
+ event_sink->handle(e);
+ }
+}
+
+#endif // XPP_EVENT_HPP
--- /dev/null
+LIBS=x11 \
+ xcb \
+ xcb-icccm \
+ xcb-sync \
+ xcb-xf86dri \
+ xcb-xprint \
+ xcb-xinput \
+ xcb-shape \
+ xcb-shm \
+ xcb-render \
+ xcb-proto \
+ xcb-event \
+ xcb-xfixes \
+ xcb-xkb \
+ xcb-dri3 \
+ xcb-ewmh \
+ xcb-util \
+ xcb-renderutil \
+ xcb-xtest \
+ xcb-xevie \
+ xcb-keysyms \
+ xcb-image \
+ xcb-composite \
+ xcb-randr \
+ xcb-present \
+ xcb-xv \
+ xcb-aux \
+ xcb-record \
+ xcb-dpms \
+ xcb-glx \
+ xcb-atom \
+ xcb-damage \
+ xcb-screensaver \
+ xcb-xvmc \
+ xcb-res \
+ xcb-xinerama \
+ xcb-dri2
+
+CXXFLAGS=-std=c++11 -Wall -O0 $(shell pkg-config --cflags ${LIBS})
+LDFLAGS=$(shell pkg-config --libs ${LIBS})
--- /dev/null
+#ifndef XPP_FONT_HPP
+#define XPP_FONT_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class font
+ : public xpp::generic::resource<Connection, xcb_font_t,
+ xpp::x::font, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_font_t,
+ xpp::x::font, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ font(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ font<Connection, Interfaces ...>
+ open(C && c, const std::string & name) noexcept
+ {
+ return font(std::forward<C>(c),
+ [&](const Connection & c, const xcb_font_t & font)
+ {
+ xpp::x::open_font(c, font, name);
+ },
+ [&](const Connection & c, const xcb_font_t & font)
+ {
+ xpp::x::close_font(c, font);
+ });
+ }
+
+ template<typename C>
+ static
+ font<Connection, Interfaces ...>
+ open_checked(C && c, const std::string & name)
+ {
+ return font(std::forward<C>(c),
+ [&](const Connection & c, const xcb_font_t & font)
+ {
+ xpp::x::open_font_checked(c, font, name);
+ },
+ [&](const Connection & c, const xcb_font_t & font)
+ {
+ xpp::x::close_font_checked(c, font);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::font<Connection, Interfaces ...>>
+{
+ typedef xcb_font_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_FONT_HPP
--- /dev/null
+#ifndef XPP_FONTABLE_HPP
+#define XPP_FONTABLE_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class fontable
+ : public xpp::generic::resource<Connection, xcb_fontable_t,
+ xpp::x::fontable, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_fontable_t,
+ xpp::x::fontable, Interfaces ...>;
+
+ public:
+ using base::base;
+ using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::fontable<Connection, Interfaces ...>>
+{
+ typedef xcb_fontable_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_FONTABLE_HPP
--- /dev/null
+#ifndef XPP_GCONTEXT_HPP
+#define XPP_GCONTEXT_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class gcontext
+ : public xpp::generic::resource<Connection, xcb_gcontext_t,
+ xpp::x::gcontext, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_gcontext_t,
+ xpp::x::gcontext, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ gcontext(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ gcontext<Connection, Interfaces ...>
+ create(C && c, xcb_drawable_t drawable,
+ uint32_t value_mask, const uint32_t * value_list)
+ {
+ return gcontext(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::create_gc(c, gcontext, drawable, value_mask, value_list);
+ },
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::free_gc(c, gcontext);
+ });
+ }
+
+ template<typename C>
+ static
+ gcontext<Connection, Interfaces ...>
+ create_checked(C && c, xcb_drawable_t drawable,
+ uint32_t value_mask, const uint32_t * value_list)
+ {
+ return gcontext(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::create_gc_checked(c, gcontext, drawable,
+ value_mask, value_list);
+ },
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::free_gc_checked(c, gcontext);
+ });
+ }
+
+ template<typename C>
+ static
+ gcontext<Connection, Interfaces ...>
+ copy(C && c, xcb_gcontext_t src_gc, uint32_t value_mask)
+ {
+ return gcontext(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::copy_gc(c, src_gc, gcontext, value_mask);
+ },
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::free_gc(c, gcontext);
+ });
+ }
+
+
+ template<typename C>
+ static
+ gcontext<Connection, Interfaces ...>
+ copy_checked(C && c, xcb_gcontext_t src_gc, uint32_t value_mask)
+ {
+ return gcontext(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::copy_gc_checked(c, src_gc, gcontext, value_mask);
+ },
+ [&](const Connection & c, const xcb_gcontext_t & gcontext)
+ {
+ xpp::x::free_gc_checked(c, gcontext);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::gcontext<Connection, Interfaces ...>>
+{
+ typedef xcb_gcontext_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GCONTEXT_HPP
--- /dev/null
+#ifndef XPP_GENERIC_HPP
+#define XPP_GENERIC_HPP
+
+#include "generic/error.hpp"
+#include "generic/event.hpp"
+#include "generic/factory.hpp"
+#include "generic/request.hpp"
+#include "generic/resource.hpp"
+#include "generic/extension.hpp"
+#include "generic/signature.hpp"
+#include "generic/reply_iterator.hpp"
+#include "generic/iterator_traits.hpp"
+#include "generic/input_iterator_adapter.hpp"
+
+#endif // XPP_GENERIC_HPP
--- /dev/null
+#ifndef XPP_GENERIC_ERROR_HPP
+#define XPP_GENERIC_ERROR_HPP
+
+#include <iostream> // shared_ptr
+#include <memory> // shared_ptr
+#include <xcb/xcb.h> // xcb_generic_error_t
+
+namespace xpp { namespace generic {
+
+class error_dispatcher {
+ public:
+ virtual
+ void operator()(const std::shared_ptr<xcb_generic_error_t> &) const = 0;
+};
+
+namespace detail {
+
+template<typename Object>
+void
+dispatch(const Object & object,
+ const std::shared_ptr<xcb_generic_error_t> & error,
+ std::true_type)
+{
+ static_cast<const xpp::generic::error_dispatcher &>(object)(error);
+}
+
+template<typename Object>
+void
+dispatch(const Object &,
+ const std::shared_ptr<xcb_generic_error_t> & error,
+ std::false_type)
+{
+ throw error;
+}
+
+} // namespace detail
+
+template<typename Object>
+void
+dispatch(const Object & object,
+ const std::shared_ptr<xcb_generic_error_t> & error)
+{
+ detail::dispatch(object,
+ error,
+ std::is_base_of<xpp::generic::error_dispatcher, Object>());
+}
+
+template<typename Derived, typename Error>
+class error
+ : public std::runtime_error
+{
+ public:
+ error(const std::shared_ptr<xcb_generic_error_t> & error)
+ : runtime_error(get_error_description(error.get()))
+ , m_error(error)
+ {}
+
+ virtual
+ ~error(void)
+ {}
+
+ virtual
+ operator const Error &(void) const
+ {
+ return reinterpret_cast<const Error &>(*m_error);
+ }
+
+ virtual
+ const Error &
+ operator*(void) const
+ {
+ return reinterpret_cast<const Error &>(*m_error);
+ }
+
+ virtual
+ Error *
+ operator->(void) const
+ {
+ return reinterpret_cast<Error * const>(m_error.get());
+ }
+
+ protected:
+ virtual
+ std::string
+ get_error_description(xcb_generic_error_t * error) const
+ {
+ return std::string(Derived::description())
+ + " (" + std::to_string(error->error_code) + ")";
+ }
+
+ std::shared_ptr<xcb_generic_error_t> m_error;
+}; // class error
+
+} } // xpp::generic
+
+#endif // XPP_GENERIC_ERROR_HPP
--- /dev/null
+#ifndef XPP_GENERIC_EVENT_HPP
+#define XPP_GENERIC_EVENT_HPP
+
+#include <memory> // shared_ptr
+#include <xcb/xcb.h> // xcb_generic_event_t
+
+namespace xpp { namespace generic {
+
+template<typename Event>
+class event {
+ public:
+ event(const std::shared_ptr<xcb_generic_event_t> & event)
+ : m_event(event)
+ {}
+
+ virtual
+ ~event(void) {}
+
+ virtual
+ operator const Event &(void) const
+ {
+ return reinterpret_cast<const Event &>(*m_event);
+ }
+
+ virtual
+ const Event &
+ operator*(void) const
+ {
+ return reinterpret_cast<const Event &>(*m_event);
+ }
+
+ virtual
+ Event *
+ operator->(void) const
+ {
+ return reinterpret_cast<Event * const>(m_event.get());
+ }
+
+ protected:
+ std::shared_ptr<xcb_generic_event_t> m_event;
+}; // class event
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_EVENT_HPP
--- /dev/null
+#ifndef XPP_GENERIC_EXTENSION_HPP
+#define XPP_GENERIC_EXTENSION_HPP
+
+// #include <iostream>
+#include <xcb/xcb.h>
+
+namespace xpp { namespace generic {
+
+template<typename Derived, xcb_extension_t * Id>
+class extension
+{
+ public:
+ extension(xcb_connection_t * const c)
+ : m_c(c)
+ {
+ prefetch();
+ }
+
+ const xcb_query_extension_reply_t &
+ operator*(void) const
+ {
+ return *m_extension;
+ }
+
+ const xcb_query_extension_reply_t *
+ operator->(void) const
+ {
+ return m_extension;
+ }
+
+ operator const xcb_query_extension_reply_t *(void) const
+ {
+ return m_extension;
+ }
+
+ Derived &
+ get(void)
+ {
+ m_extension = xcb_get_extension_data(m_c, Id);
+ return static_cast<Derived &>(*this);
+ }
+
+ Derived &
+ prefetch(void)
+ {
+ xcb_prefetch_extension_data(m_c, Id);
+ return static_cast<Derived &>(*this);
+ }
+
+ private:
+ xcb_connection_t * m_c = nullptr;
+ // The result must not be freed.
+ // This storage is managed by the cache itself.
+ const xcb_query_extension_reply_t * m_extension = nullptr;
+}; // class extension
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_EXTENSION_HPP
--- /dev/null
+#ifndef XPP_GENERIC_FACTORY_HPP
+#define XPP_GENERIC_FACTORY_HPP
+
+#include <utility> // std::forward
+
+namespace xpp { namespace generic {
+
+namespace factory {
+
+template<typename ReturnType>
+class make_object
+{
+ public:
+ template<typename Connection, typename ... Parameter>
+ ReturnType
+ operator()(Connection &&, Parameter && ... parameter) const
+ {
+ return ReturnType { std::forward<Parameter>(parameter) ... };
+ }
+};
+
+template<typename ReturnType>
+class make_object_with_member
+{
+ public:
+ template<typename Member, typename Connection, typename ... Parameter>
+ ReturnType
+ operator()(Connection && c, Member && member, Parameter && ... parameter) const
+ {
+ return ReturnType { std::forward<Member>(member)
+ , std::forward<Connection>(c)
+ , std::forward<Parameter>(parameter) ...
+ };
+ }
+};
+
+template<typename ReturnType>
+class make_object_with_connection
+{
+ public:
+ template<typename Connection, typename ... Parameter>
+ ReturnType
+ operator()(Connection && c, Parameter && ... parameter) const
+ {
+ return ReturnType { std::forward<Connection>(c)
+ , std::forward<Parameter>(parameter) ...
+ };
+ }
+};
+
+template<typename ReturnType>
+class make_fundamental {
+ public:
+ template<typename Connection, typename Member, typename ... Parameter>
+ ReturnType
+ operator()(Connection &&, Member && member) const
+ {
+ return std::forward<Member>(member);
+ }
+};
+
+template<typename Connection,
+ typename MemberType,
+ typename ReturnType,
+ typename ... Parameter>
+class make
+ : public std::conditional<
+ std::is_constructible<ReturnType, MemberType>::value,
+ make_fundamental<ReturnType>,
+ typename std::conditional<
+ std::is_constructible<ReturnType,
+ MemberType,
+ Connection,
+ Parameter ...>::value,
+ make_object_with_member<ReturnType>,
+ typename std::conditional<
+ std::is_constructible<ReturnType,
+ Connection,
+ MemberType,
+ Parameter ...>::value,
+ make_object_with_connection<ReturnType>,
+ make_object<ReturnType>
+ >::type
+ >::type
+ >::type
+{};
+
+} // namespace factory
+
+} } // xpp::generic
+
+#endif // XPP_GENERIC_FACTORY_HPP
--- /dev/null
+#ifndef XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
+#define XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
+
+#include <utility>
+#include <iterator>
+#include <type_traits>
+
+#define GENERATE_HAS_MEMBER(member) \
+ \
+template<typename T, bool B> \
+class HasMember_##member { \
+ private: \
+ using Yes = char[2]; \
+ using No = char[1]; \
+ \
+ struct Fallback { int member; }; \
+ struct Derived : T, Fallback {}; \
+ \
+ template<typename U> \
+ static No& test(decltype(U::member)*); \
+ template<typename U> \
+ static Yes& test(U*); \
+ \
+ public: \
+ static constexpr bool RESULT = sizeof(test<Derived>(nullptr)) == sizeof(Yes); \
+}; \
+ \
+template<typename T> \
+class HasMember_##member<T, false> { \
+ public: \
+ static constexpr bool RESULT = false; \
+}; \
+ \
+template<typename T> \
+struct has_member_##member \
+ : public std::integral_constant< \
+ bool, \
+ HasMember_##member<T, std::is_class<T>::value>::RESULT \
+ > \
+{};
+
+GENERATE_HAS_MEMBER(first)
+GENERATE_HAS_MEMBER(second)
+
+// namespace iterator {
+
+template<typename Iterator>
+struct value_iterator_base {
+ value_iterator_base(const Iterator & iterator)
+ : m_iterator(iterator)
+ {}
+
+ bool
+ operator==(const value_iterator_base & other)
+ {
+ return m_iterator == other.m_iterator;
+ }
+
+ bool
+ operator!=(const value_iterator_base & other)
+ {
+ return m_iterator != other.m_iterator;
+ }
+
+ void
+ operator++(void)
+ {
+ ++m_iterator;
+ }
+
+ template<typename Key, typename Value>
+ const Value &
+ get_value(const std::pair<Key, Value> & pair)
+ {
+ return pair.second;
+ }
+
+ template<typename Value>
+ const Value &
+ get_value(const Value & v)
+ {
+ return v;
+ }
+
+ Iterator m_iterator;
+};
+
+template<typename Iterator>
+struct value_iterator_pair
+ : public value_iterator_base<Iterator>
+ , public std::iterator<typename std::input_iterator_tag,
+ // value_type
+ typename Iterator::value_type::second_type,
+ typename std::iterator_traits<Iterator>::difference_type,
+ // pointer
+ typename Iterator::value_type::second_type *,
+ // reference
+ const typename Iterator::value_type::second_type &>
+{
+ typedef value_iterator_base<Iterator> base;
+ using base::base;
+
+ const typename Iterator::value_type::second_type &
+ operator*(void)
+ {
+ return base::get_value(*base::m_iterator);
+ }
+};
+
+template<typename Iterator>
+struct value_iterator_integral
+ : public value_iterator_base<Iterator>
+ , public std::iterator<typename std::input_iterator_tag,
+ typename std::iterator_traits<Iterator>::value_type,
+ typename std::iterator_traits<Iterator>::difference_type,
+ typename std::iterator_traits<Iterator>::pointer,
+ typename std::iterator_traits<Iterator>::reference>
+{
+ typedef value_iterator_base<Iterator> base;
+ using base::base;
+
+ const typename Iterator::value_type &
+ operator*(void)
+ {
+ return base::get_value(*base::m_iterator);
+ }
+};
+
+template<typename Iterator>
+struct value_iterator
+ : public std::conditional<
+ has_member_first<typename Iterator::value_type>::value
+ && has_member_second<typename Iterator::value_type>::value,
+ value_iterator_pair<Iterator>,
+ value_iterator_integral<Iterator>
+ >::type
+{
+ typedef typename std::conditional<
+ has_member_first<typename Iterator::value_type>::value
+ && has_member_second<typename Iterator::value_type>::value,
+ value_iterator_pair<Iterator>,
+ value_iterator_integral<Iterator>
+ >::type base;
+ using base::base;
+};
+
+template<typename T, bool B = true>
+struct value_type {
+ typedef typename std::conditional<
+ has_member_second<typename T::value_type>::value,
+ typename T::value_type::second_type,
+ typename T::value_type>::type
+ type;
+};
+
+template<typename T>
+struct value_type<T, false> {
+ typedef typename std::remove_const<
+ typename std::remove_pointer<T>::type
+ >::type type;
+};
+
+#endif // XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
--- /dev/null
+#ifndef XPP_GENERIC_ITERATOR_TRAITS_HPP
+#define XPP_GENERIC_ITERATOR_TRAITS_HPP
+
+namespace xpp {
+
+namespace generic {
+
+template<typename T>
+struct traits
+{
+ typedef T type;
+};
+
+template<typename Object>
+struct conversion_type
+{
+ using type = typename traits<Object>::type;
+};
+
+} // namespace generic
+
+}
+
+#endif // XPP_GENERIC_ITERATOR_TRAITS_HPP
--- /dev/null
+#ifndef XPP_GENERIC_REPLY_ITERATOR_HPP
+#define XPP_GENERIC_REPLY_ITERATOR_HPP
+
+#include <cstdlib> // size_t
+#include <memory>
+#include <stack>
+#include <xcb/xcb.h> // xcb_str_*
+#include "factory.hpp"
+#include "signature.hpp"
+#include "iterator_traits.hpp"
+
+#define NEXT_TEMPLATE \
+ void (&Next)(XcbIterator *)
+
+#define NEXT_SIGNATURE \
+ xpp::generic::signature<void (XcbIterator *), Next>
+
+#define SIZEOF_TEMPLATE \
+ int (&SizeOf)(const void *)
+
+#define SIZEOF_SIGNATURE \
+ xpp::generic::signature<int (const void *), SizeOf>
+
+#define GETITERATOR_TEMPLATE \
+ XcbIterator (&GetIterator)(const Reply *)
+
+#define GETITERATOR_SIGNATURE \
+ xpp::generic::signature<XcbIterator (const Reply *), GetIterator>
+
+#define ACCESSOR_TEMPLATE \
+ Data * (&Accessor)(const Reply *)
+
+#define ACCESSOR_SIGNATURE \
+ xpp::generic::signature<Data * (const Reply *), Accessor>
+
+#define LENGTH_TEMPLATE \
+ int (&Length)(const Reply *)
+
+#define LENGTH_SIGNATURE \
+ xpp::generic::signature<int (const Reply *), Length>
+
+namespace xpp {
+
+namespace generic {
+
+template<typename Data>
+class get
+{
+ public:
+ Data
+ operator()(Data * const data)
+ {
+ return *data;
+ }
+};
+
+template<>
+class get<xcb_str_t>
+{
+ public:
+ std::string
+ operator()(xcb_str_t * const data)
+ {
+ return std::string(xcb_str_name(data),
+ xcb_str_name_length(data));
+ }
+};
+
+namespace detail
+{
+
+template<typename F>
+struct function_traits;
+
+template<typename Signature, Signature& S>
+struct function_traits<signature<Signature, S>> : function_traits<Signature> {};
+
+template<typename R, typename... Args>
+struct function_traits<R(*)(Args...)> : function_traits<R(Args...)> {};
+
+template<typename R, typename... Args>
+struct function_traits<R(Args...)>
+{
+ using result_type = R;
+ const static std::size_t arity = sizeof...(Args);
+
+ template <std::size_t I>
+ struct argument
+ {
+ static_assert(I < arity, "invalid argument index");
+ using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
+ };
+};
+}
+
+// iterator for variable size data fields
+
+template<typename ... Types>
+class iterator;
+
+template<typename Connection,
+ typename Object,
+ typename NextTemplate,
+ NextTemplate& Next,
+ typename SizeOfTemplate,
+ SizeOfTemplate& SizeOf,
+ typename GetIteratorTemplate,
+ GetIteratorTemplate& GetIterator>
+class iterator<Connection,
+ Object,
+ xpp::generic::signature<NextTemplate, Next>,
+ xpp::generic::signature<SizeOfTemplate, SizeOf>,
+ xpp::generic::signature<GetIteratorTemplate, GetIterator>>
+ : public std::iterator<typename std::input_iterator_tag,
+ Object,
+ typename std::size_t,
+ Object *,
+ const Object &>
+{
+ protected:
+ using self = iterator<Connection,
+ Object,
+ xpp::generic::signature<NextTemplate, Next>,
+ xpp::generic::signature<SizeOfTemplate, SizeOf>,
+ xpp::generic::signature<GetIteratorTemplate, GetIterator>>;
+
+ using get_iterator_traits = detail::function_traits<GetIteratorTemplate>;
+ using const_reply_ptr = typename get_iterator_traits::template argument<0>::type;
+ using Reply = typename std::remove_pointer<typename std::remove_const<const_reply_ptr>::type>::type;
+ using XcbIterator = typename get_iterator_traits::result_type;
+
+ Connection m_c;
+ std::shared_ptr<Reply> m_reply;
+ std::stack<std::size_t> m_lengths;
+ XcbIterator m_iterator;
+
+ public:
+ iterator(void) {}
+
+ template<typename C>
+ iterator(C && c, const std::shared_ptr<Reply> & reply)
+ : m_c(std::forward<C>(c))
+ , m_reply(reply)
+ , m_iterator(GetIterator(reply.get()))
+ {}
+
+ bool
+ operator==(const iterator & other)
+ {
+ return m_iterator.rem == other.m_iterator.rem;
+ }
+
+ bool
+ operator!=(const iterator & other)
+ {
+ return ! (*this == other);
+ }
+
+ auto
+ operator*(void) -> decltype(get<Object>()(this->m_iterator.data))
+ {
+ return get<Object>()(m_iterator.data);
+ }
+
+ // prefix
+ self &
+ operator++(void)
+ {
+ m_lengths.push(SizeOf(m_iterator.data));
+ Next(&m_iterator);
+ return *this;
+ }
+
+ // postfix
+ self
+ operator++(int)
+ {
+ auto copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+ // prefix
+ self &
+ operator--(void)
+ {
+ typedef typename std::remove_pointer<decltype(m_iterator.data)>::type data_t;
+ if (m_lengths.empty()) {
+ data_t * data = m_iterator.data;
+ data_t * prev = data - m_lengths.top();
+ m_lengths.pop();
+ m_iterator.index = (char *)m_iterator.data - (char *)prev;
+ m_iterator.data = prev;
+ ++m_iterator.rem;
+ }
+ return *this;
+ }
+
+ // postfix
+ self
+ operator--(int)
+ {
+ auto copy = *this;
+ --(*this);
+ return copy;
+ }
+
+ template<typename C>
+ static
+ self
+ begin(C && c, const std::shared_ptr<Reply> & reply)
+ {
+ return self { std::forward<C>(c), reply };
+ }
+
+ template<typename C>
+ static
+ self
+ end(C && c, const std::shared_ptr<Reply> & reply)
+ {
+ auto it = self { std::forward<C>(c), reply };
+ it.m_iterator.rem = 0;
+ return it;
+ }
+}; // class iterator
+
+// iterator for fixed size data fields
+
+template<typename Connection,
+ typename Object,
+ typename AccessorTemplate,
+ AccessorTemplate& Accessor,
+ typename LengthTemplate,
+ LengthTemplate& Length>
+class iterator<Connection,
+ Object,
+ signature<AccessorTemplate, Accessor>,
+ signature<LengthTemplate, Length>>
+ : public std::iterator<typename std::input_iterator_tag,
+ Object,
+ typename std::size_t,
+ Object *,
+ const Object &>
+{
+ protected:
+
+ using accessor_traits = detail::function_traits<AccessorTemplate>;
+ using Data = typename std::remove_pointer<typename accessor_traits::result_type>::type;
+ using const_reply_ptr = typename accessor_traits::template argument<0>::type;
+ using Reply = typename std::remove_pointer<typename std::remove_const<const_reply_ptr>::type>::type;
+
+ using data_t = typename std::conditional<std::is_void<Data>::value,
+ typename xpp::generic::conversion_type<Object>::type, Data>::type;
+ using make = xpp::generic::factory::make<Connection, data_t, Object>;
+
+ Connection m_c;
+ std::size_t m_index = 0;
+ std::shared_ptr<Reply> m_reply;
+
+ public:
+ typedef iterator<Connection,
+ Object,
+ signature<AccessorTemplate, Accessor>,
+ signature<LengthTemplate, Length>>
+ self;
+
+ iterator(void) {}
+
+ template<typename C>
+ iterator(C && c,
+ const std::shared_ptr<Reply> & reply,
+ std::size_t index)
+ : m_c(c)
+ , m_index(index)
+ , m_reply(reply)
+ {
+ if (std::is_void<Data>::value) {
+ m_index /= sizeof(data_t);
+ }
+ }
+
+ bool operator==(const iterator & other)
+ {
+ return m_index == other.m_index;
+ }
+
+ bool operator!=(const iterator & other)
+ {
+ return ! (*this == other);
+ }
+
+ Object operator*(void)
+ {
+ return make()(m_c, static_cast<data_t *>(Accessor(m_reply.get()))[m_index]);
+ }
+
+ // prefix
+ self & operator++(void)
+ {
+ ++m_index;
+ return *this;
+ }
+
+ // postfix
+ self operator++(int)
+ {
+ auto copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+ // prefix
+ self & operator--(void)
+ {
+ --m_index;
+ return *this;
+ }
+
+ // postfix
+ self operator--(int)
+ {
+ auto copy = *this;
+ --(*this);
+ return copy;
+ }
+
+ template<typename C>
+ static
+ self
+ begin(C && c, const std::shared_ptr<Reply> & reply)
+ {
+ return self { std::forward<C>(c), reply, 0 };
+ }
+
+ template<typename C>
+ static
+ self
+ end(C && c, const std::shared_ptr<Reply> & reply)
+ {
+ return self { std::forward<C>(c),
+ reply,
+ static_cast<std::size_t>(Length(reply.get())) };
+ }
+}; // class iterator
+
+template<typename Connection, typename Reply, typename Iterator>
+class list {
+ private:
+ // before public part, to make decltype in begin() & end() work!
+ Connection m_c;
+ std::shared_ptr<Reply> m_reply;
+
+ public:
+ template<typename C>
+ list(C && c, const std::shared_ptr<Reply> & reply)
+ : m_c(std::forward<C>(c)), m_reply(reply)
+ {}
+
+ auto
+ begin(void) -> decltype(Iterator::begin(this->m_c, this->m_reply))
+ {
+ return Iterator::begin(m_c, m_reply);
+ }
+
+ auto
+ end(void) -> decltype(Iterator::end(this->m_c, this->m_reply))
+ {
+ return Iterator::end(m_c, m_reply);
+ }
+}; // class list
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GENERIC_REPLY_ITERATOR_HPP
--- /dev/null
+#ifndef XPP_GENERIC_REQUEST_HPP
+#define XPP_GENERIC_REQUEST_HPP
+
+#include <array>
+#include <memory>
+#include <cstdlib>
+#include <xcb/xcb.h>
+#include "error.hpp"
+#include "signature.hpp"
+
+#define REPLY_TEMPLATE \
+ typename Reply, \
+ typename Cookie, \
+ Reply *(&ReplyFunction)(xcb_connection_t *, Cookie, xcb_generic_error_t **)
+
+#define REPLY_SIGNATURE \
+ xpp::generic::signature<Reply *(xcb_connection_t *, \
+ Cookie, \
+ xcb_generic_error_t **), \
+ ReplyFunction>
+
+#define REPLY_COOKIE_TEMPLATE \
+ typename ... CookieParameter, \
+ Cookie(&CookieFunction)(CookieParameter ...)
+
+#define REPLY_COOKIE_SIGNATURE \
+ xpp::generic::signature<Cookie(CookieParameter ...), CookieFunction>
+
+namespace xpp { namespace generic {
+
+template<typename Connection, typename Dispatcher>
+void
+check(Connection && c, const xcb_void_cookie_t & cookie)
+{
+ xcb_generic_error_t * error =
+ xcb_request_check(std::forward<Connection>(c), cookie);
+ if (error) {
+ dispatch(std::forward<Connection>(c),
+ std::shared_ptr<xcb_generic_error_t>(error, std::free));
+ }
+}
+
+struct checked_tag {};
+struct unchecked_tag {};
+
+template<typename ... Types>
+class reply;
+
+template<typename Derived,
+ typename Connection,
+ typename Check,
+ REPLY_TEMPLATE,
+ REPLY_COOKIE_TEMPLATE>
+class reply<Derived,
+ Connection,
+ Check,
+ REPLY_SIGNATURE,
+ REPLY_COOKIE_SIGNATURE>
+{
+ public:
+ template<typename C, typename ... Parameter>
+ reply(C && c, Parameter && ... parameter)
+ : m_c(std::forward<C>(c))
+ , m_cookie(Derived::cookie(std::forward<C>(c),
+ std::forward<Parameter>(parameter) ...))
+ {}
+
+ operator bool(void)
+ {
+ return m_reply.operator bool();
+ }
+
+ const Reply &
+ operator*(void)
+ {
+ return *get();
+ }
+
+ Reply *
+ operator->(void)
+ {
+ return get().get();
+ }
+
+ const std::shared_ptr<Reply> &
+ get(void)
+ {
+ if (! m_reply) {
+ m_reply = get(Check());
+ }
+ return m_reply;
+ }
+
+ template<typename ... Parameter>
+ static
+ Cookie
+ cookie(Parameter && ... parameter)
+ {
+ return CookieFunction(std::forward<Parameter>(parameter) ...);
+ }
+
+ protected:
+ Connection m_c;
+ Cookie m_cookie;
+ std::shared_ptr<Reply> m_reply;
+
+ std::shared_ptr<Reply>
+ get(checked_tag)
+ {
+ xcb_generic_error_t * error = nullptr;
+ auto reply = std::shared_ptr<Reply>(ReplyFunction(m_c, m_cookie, &error),
+ std::free);
+ if (error) {
+ dispatch(m_c, std::shared_ptr<xcb_generic_error_t>(error, std::free));
+ }
+ return reply;
+ }
+
+ std::shared_ptr<Reply>
+ get(unchecked_tag)
+ {
+ return std::shared_ptr<Reply>(ReplyFunction(m_c, m_cookie, nullptr),
+ std::free);
+ }
+};
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_REQUEST_HPP
--- /dev/null
+#ifndef XPP_GENERIC_RESOURCE_HPP
+#define XPP_GENERIC_RESOURCE_HPP
+
+#include <iostream> // std::{hex,dec}
+#include <memory> // std::shared_ptr
+#include "iterator_traits.hpp"
+
+#include <xcb/xcb.h> // xcb_generate_id
+
+namespace xpp {
+
+namespace generic {
+
+namespace detail {
+
+template<typename Connection, typename Resource, typename ResourceId,
+ template<typename, typename> class ... Interfaces>
+class interfaces
+ : public Interfaces<interfaces<Connection, Resource, ResourceId, Interfaces ...>,
+ Connection> ...
+{
+ public:
+ const ResourceId &
+ resource(void) const
+ {
+ return *static_cast<const Resource &>(*this);
+ }
+
+ Connection
+ connection(void) const
+ {
+ return static_cast<const Resource &>(*this).connection();
+ }
+}; // class interfaces
+
+}
+
+template<typename Connection, typename ResourceId,
+ template<typename, typename> class ... Interfaces>
+class resource
+ : public detail::interfaces<Connection,
+ resource<Connection, ResourceId, Interfaces ...>,
+ ResourceId, Interfaces ...>
+{
+ protected:
+ using self = resource<Connection, ResourceId, Interfaces ...>;
+
+ Connection m_c;
+ // reference counting for Resource object
+ std::shared_ptr<ResourceId> m_resource;
+
+ resource(Connection c)
+ : m_c(c)
+ {}
+
+ template<typename C, typename Create, typename Destroy>
+ static
+ self
+ make(C && c, Create create, Destroy destroy)
+ {
+ self resource(std::forward<C>(c));
+
+ auto xid = xcb_generate_id(std::forward<C>(c));
+
+ // class create before instatiating the shared_ptr
+ // create might fail and throw an error, hence shared_ptr would hold an
+ // invalid xid, causing possibly another exception in destroy()
+ // when create() throws, then the shared_ptr will not be created
+ create(std::forward<C>(c), xid);
+
+ resource.m_resource =
+ std::shared_ptr<ResourceId>(new ResourceId(xid),
+ [&](ResourceId * r)
+ {
+ destroy(resource.m_c, *r);
+ delete r;
+ });
+
+ return resource;
+ }
+
+ public:
+ template<typename C>
+ resource(C && c, const ResourceId & resource_id)
+ : m_c(std::forward<C>(c))
+ , m_resource(std::make_shared<ResourceId>(resource_id))
+ {}
+
+ resource(const resource<Connection, ResourceId, Interfaces ...> & other)
+ : m_c(other.m_c)
+ , m_resource(other.m_resource)
+ {}
+
+ virtual
+ void
+ operator=(const resource<Connection, ResourceId, Interfaces ...> & other)
+ {
+ m_c = other.m_c;
+ m_resource = other.m_resource;
+ }
+
+ virtual
+ void
+ operator=(const ResourceId & resource)
+ {
+ m_resource = std::make_shared<ResourceId>(resource);
+ }
+
+ virtual
+ const ResourceId &
+ operator*(void) const
+ {
+ return *m_resource;
+ }
+
+ virtual
+ operator const ResourceId &(void) const
+ {
+ return *m_resource;
+ }
+
+ Connection
+ connection(void) const
+ {
+ return m_c;
+ }
+}; // class resource
+
+template<typename Connection, typename ResourceId,
+ template<typename, typename> class ... Interfaces>
+std::ostream &
+operator<<(std::ostream & os,
+ const resource<Connection, ResourceId, Interfaces ...> & resource)
+{
+ return os << std::hex << "0x" << *resource << std::dec;
+}
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GENERIC_RESOURCE_HPP
--- /dev/null
+#ifndef XPP_GENERIC_SIGNATURE_HPP
+#define XPP_GENERIC_SIGNATURE_HPP
+
+#define SIGNATURE(NAME) \
+ xpp::generic::signature<decltype(NAME), NAME>
+
+namespace xpp
+{
+ namespace generic
+ {
+ template<typename Signature, Signature & S>
+ class signature;
+ }
+}
+
+#endif // XPP_GENERIC_SIGNATURE_HPP
--- /dev/null
+#ifndef XPP_PIXMAP_HPP
+#define XPP_PIXMAP_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class pixmap
+ : public xpp::generic::resource<Connection, xcb_pixmap_t,
+ xpp::x::pixmap, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_pixmap_t,
+ xpp::x::pixmap, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ pixmap(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ pixmap<Connection, Interfaces ...>
+ create(C && c, uint8_t depth, xcb_drawable_t drawable,
+ uint16_t width, uint16_t height)
+ {
+ return pixmap(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_pixmap_t & pixmap)
+ {
+ xpp::x::create_pixmap(c, depth, pixmap, drawable, width, height);
+ },
+ [&](const Connection & c, const xcb_pixmap_t & pixmap)
+ {
+ xpp::x::free_pixmap(c, pixmap);
+ });
+ }
+
+ template<typename C>
+ static
+ pixmap<Connection, Interfaces ...>
+ create_checked(C && c, uint8_t depth, xcb_drawable_t drawable,
+ uint16_t width, uint16_t height)
+ {
+ return pixmap(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_pixmap_t & pixmap)
+ {
+ xpp::x::create_pixmap_checked(c, depth, pixmap, drawable, width, height);
+ },
+ [&](const Connection & c, const xcb_pixmap_t & pixmap)
+ {
+ xpp::x::free_pixmap_checked(c, pixmap);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::pixmap<Connection, Interfaces ...>>
+{
+ typedef xcb_pixmap_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_PIXMAP_HPP
--- /dev/null
+#ifndef X_VALUEPARAM_HPP
+#define X_VALUEPARAM_HPP
+
+#include <map>
+#include <vector>
+
+namespace xpp {
+
+class valueparam {
+ public:
+ valueparam &
+ set(const uint32_t & bit, const uint32_t & value)
+ {
+ m_has_changed = true;
+ m_values_map[bit] = value;
+ return *this;
+ }
+
+ uint32_t
+ mask(void)
+ {
+ return m_mask;
+ }
+
+ uint32_t * const
+ values(void)
+ {
+ if (m_has_changed) {
+ m_values.clear();
+ }
+
+ for (auto & item : m_values_map) {
+ m_values.push_back(item.second);
+ }
+
+ m_has_changed = false;
+
+ return m_values.data();
+ }
+
+ private:
+ bool m_has_changed = true;
+ uint32_t m_mask = 0;
+ std::vector<uint32_t> m_values;
+ std::map<uint32_t, uint32_t> m_values_map;
+};
+
+} // namespace xpp
+
+#endif // X_VALUEPARAM_HPP
--- /dev/null
+#ifndef XPP_WINDOW_HPP
+#define XPP_WINDOW_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class window
+ : public xpp::generic::resource<Connection, xcb_window_t,
+ xpp::x::window, Interfaces ...>
+{
+ protected:
+ using base = xpp::generic::resource<Connection, xcb_window_t,
+ xpp::x::window, Interfaces ...>;
+
+ template<typename C, typename Create, typename Destroy>
+ window(C && c, Create && create, Destroy && destroy)
+ : base(base::make(std::forward<C>(c),
+ std::forward<Create>(create),
+ std::forward<Destroy>(destroy)))
+ {}
+
+ public:
+ window(const window&) = default;
+ using base::base;
+ using base::operator=;
+
+ template<typename C>
+ static
+ window<Connection, Interfaces ...>
+ create(C && c, uint8_t depth, xcb_window_t parent,
+ int16_t x, int16_t y, uint16_t width, uint16_t height,
+ uint16_t border_width,
+ uint16_t _class, xcb_visualid_t visual,
+ uint32_t value_mask, const uint32_t * value_list)
+ {
+ return window(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_window_t & window)
+ {
+ xpp::x::create_window(c, depth, window, parent,
+ x, y, width, height, border_width,
+ _class, visual,
+ value_mask, value_list);
+ },
+ [&](const Connection & c, const xcb_window_t & window)
+ {
+ xpp::x::destroy_window(c, window);
+ });
+ }
+
+ template<typename C>
+ static
+ window<Connection, Interfaces ...>
+ create_checked(C && c, uint8_t depth, xcb_window_t parent,
+ int16_t x, int16_t y, uint16_t width, uint16_t height,
+ uint16_t border_width,
+ uint16_t _class, xcb_visualid_t visual,
+ uint32_t value_mask, const uint32_t * value_list)
+ {
+ return window(
+ std::forward<C>(c),
+ [&](const Connection & c, const xcb_window_t & window)
+ {
+ xpp::x::create_window_checked(c, depth, window, parent,
+ x, y, width, height, border_width,
+ _class, visual,
+ value_mask, value_list);
+ },
+ [&](const Connection & c, const xcb_window_t & window)
+ {
+ xpp::x::destroy_window_checked(c, window);
+ });
+ }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::window<Connection, Interfaces ...>>
+{
+ typedef xcb_window_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_WINDOW_HPP
--- /dev/null
+#ifndef XPP_HPP
+#define XPP_HPP
+
+#include "generic.hpp"
+
+#include "atom.hpp"
+#include "colormap.hpp"
+#include "cursor.hpp"
+#include "drawable.hpp"
+#include "font.hpp"
+#include "fontable.hpp"
+#include "gcontext.hpp"
+#include "pixmap.hpp"
+#include "window.hpp"
+
+#include "event.hpp"
+#include "connection.hpp"
+
+#endif // XPP_HPP
--- /dev/null
+include ../../include/flags.makefile
+
+CXXFLAGS+=-g
+CXXFLAGS+=-Wextra
+
+CPPSRCS=$(shell find . -name '*.cpp')
+EXAMPLES=${CPPSRCS:./%.cpp=%}
+
+all: ${EXAMPLES}
+
+clean:
+ rm -f ${EXAMPLES}
+
+.PHONY: clean
--- /dev/null
+// This demo will allow the user to click on a window, grab the keyboard on it
+// and then print every key press and release event. Will exit when Escape is
+// pressed
+
+#include <iostream>
+
+#include <X11/Xlib.h> // XKeysymToString
+#include <X11/keysym.h> // XK_Escape
+#include <X11/cursorfont.h> // XC_cross
+
+#include "../xpp.hpp"
+
+// global variable to indicate whether the event loop should exit
+bool g_quit = false;
+
+// typedefs for convenience
+namespace x {
+ typedef xpp::connection<> connection;
+ typedef xpp::event::registry<connection &> registry;
+
+ typedef xpp::font<connection &> font;
+ typedef xpp::cursor<connection &> cursor;
+ typedef xpp::window<connection &> window;
+
+ typedef xpp::x::event::key_press<connection &> key_press;
+ typedef xpp::x::event::key_release<connection &> key_release;
+ typedef xpp::x::event::button_press<connection &> button_press;
+};
+
+// The event handler class
+// Implements the xpp::event::sink<..> interface with all events we are
+// interested in as template parameters
+template<typename Connection>
+class key_printer
+ : public xpp::event::sink<x::key_press, x::key_release, x::button_press>
+{
+ public:
+ template<typename C>
+ key_printer(C && c)
+ : m_c(std::forward<C>(c))
+ {}
+
+ // xpp::event::sink<x::key_press>::handle(...) interface
+ void handle(const x::key_press & e)
+ {
+ auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+ // take the first value from the kbd_mapping list
+ // This might throw, but for simplicity, no error handling here
+ auto keysym = *kbd_mapping.keysyms().begin();
+
+ if (keysym == XK_Escape) {
+ std::cerr << "quitting" << std::endl;
+ // parameter has a default value: XCB_TIME_CURRENT_TIME
+ m_c.ungrab_keyboard();
+ g_quit = true;
+ } else {
+ std::cerr << "key press: " << XKeysymToString(keysym) << std::endl;
+ }
+ }
+
+ // xpp::event::sink<x::key_release>::handle(...) interface
+ void handle(const x::key_release & e)
+ {
+ auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+ auto keysym = *kbd_mapping.keysyms().begin();
+ std::cerr << "key release: " << XKeysymToString(keysym) << std::endl;
+ }
+
+ // xpp::event::sink<x::button_press>::handle(...) interface
+ void handle(const x::button_press & e)
+ {
+ m_c.ungrab_pointer(XCB_TIME_CURRENT_TIME);
+
+ // event & reply accessors have a default template parameter, the c-type
+ // Usable with any type which is constructible from the c-type or
+ // connection + c-type
+ // xcb_window_t grab_window = e.event();
+ x::window grab_window = e.event<x::window>();
+
+ if (e->event == e->root) {
+ // xpp::window, etc. are assignable with the c-type
+ grab_window = e.child();
+ // xpp::window, etc. are implicitly convertible to c-type
+ auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+ grab_window = translate->child;
+ }
+
+ *m_c.grab_keyboard(true, grab_window,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+
+ std::cerr << "Grabbed " << grab_window
+ << ". Press Escape to quit." << std::endl;
+ }
+
+ private:
+ Connection m_c;
+};
+
+int main(int, char **)
+{
+ try {
+ // xpp::connection is implicitly convertible to xcb_connection_t *
+ // Hence, it can be used with all xcb_* c functions.
+ // However, this is not demonstrated here.
+ x::connection connection;
+ x::registry registry(connection);
+
+ key_printer<x::connection &> key_printer(connection);
+ registry.attach(0, &key_printer);
+
+ x::font font = x::font::open_checked(connection, "cursor");
+
+ // x::font, etc. is implicitly convertible to xcb_font_t
+ x::cursor cursor = x::cursor::create_glyph_checked(connection, font, font,
+ XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+ *connection.grab_pointer(false, connection.root(),
+ XCB_EVENT_MASK_BUTTON_PRESS,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ XCB_NONE, cursor);
+ // default value for time = XCB_TIME_CURRENT_TIME);
+
+ std::cerr << "Please click on a window" << std::endl;
+
+ while (! g_quit) {
+ connection.flush();
+ registry.dispatch(connection.wait_for_event());
+ }
+
+ } catch (const std::exception & error) {
+ std::cerr << "Exception (std::exception) in "
+ << __FILE__ << " @ line " << __LINE__ << ", what(): "
+ << error.what() << std::endl;
+ std::exit(EXIT_FAILURE);
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include <iostream>
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+// typedefs for convenience
+namespace x {
+ typedef xpp::connection<xpp::randr::extension,
+ xpp::damage::extension,
+ xpp::render::extension>
+ connection;
+
+ typedef xpp::event::registry<connection &,
+ xpp::randr::extension,
+ xpp::damage::extension,
+ xpp::render::extension>
+ registry;
+
+ typedef xpp::font<connection &> font;
+ typedef xpp::cursor<connection &> cursor;
+ typedef xpp::window<connection &> window;
+ typedef xpp::window<xcb_connection_t *> xcb_window;
+};
+
+int main(int, char **)
+{
+ x::connection connection;
+ x::registry registry(connection);
+
+ // Print out all available font paths
+ auto && paths = connection.get_font_path().path();
+ std::cerr << "paths "
+ << "(length: " << std::distance(paths.begin(), paths.end()) << "):"
+ << std::endl;
+ for (auto && path : paths) {
+ std::cerr << "path [" << path.length() << "]: " << path << std::endl;
+ }
+ std::cerr << std::endl;
+
+ // Print out all available fonts
+ auto && fonts = connection.list_fonts(8, 1, "*").names();
+ std::cerr << "fonts "
+ << "(length: " << std::distance(fonts.begin(), fonts.end()) << "):"
+ << std::endl;
+ for (auto && name : fonts) {
+ std::cerr << "font [" << name.length() << "]: " << name << std::endl;
+ }
+ std::cerr << std::endl;
+
+ // Print all windows and their subwindows
+ auto tree = connection.root<x::window>().query_tree();
+ std::cerr << "children: " << std::endl;
+ for (auto && child : tree.children<x::xcb_window>()) {
+ std::cerr << child << " ";
+ auto siblings = child.query_tree().children();
+ auto siblings_length = std::distance(siblings.begin(), siblings.end());
+ if (siblings_length > 0) {
+ std::cerr << std::hex << "[" << siblings_length
+ << " sibling" << (siblings_length > 1 ? "s" : "") << ": ";
+ for (auto && sibling : siblings) {
+ std::cerr << "0x" << sibling << (--siblings_length > 0 ? ", " : "");
+ }
+ std::cerr << std::dec << "]" << std::endl;
+ } else {
+ std::cerr << "[no siblings]" << std::endl;
+ }
+ }
+ std::cerr << std::endl;
+
+ // Creates an atom called "XPP_STRING_PROPERTY_DEMO" with a string property
+ // "xpp is working" on the root window
+ // check with `xprop -root XPP_STRING_PROPERTY_DEMO`
+ try {
+ auto my_string_atom =
+ xpp::x::intern_atom(connection, false, "XPP_STRING_PROPERTY_DEMO");
+
+ std::string my_string("xpp is working!");
+
+ std::cerr << "atom for \"XPP_STRING_PROPERTY_DEMO\": "
+ << my_string_atom.atom() << std::endl;
+
+ auto atom_name = connection.get_atom_name(my_string_atom.atom());
+ std::cerr << "atom name: " << atom_name.name() << std::endl;;
+
+ connection.change_property_checked(
+ XCB_PROP_MODE_REPLACE, connection.root(),
+ my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ // using Iterator begin + end here
+ my_string.begin(), my_string.end());
+
+ // this will deliberately fail because window = 0
+ // However, the previous call to change_property succeeded,
+ // so everything is just fine
+ connection.change_property_checked( XCB_PROP_MODE_REPLACE, 0,
+ my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ // using length() & c_str()
+ my_string.length(), my_string.c_str());
+
+ } catch (const std::exception & e) {
+ std::cerr << "change property failed: " << e.what() << std::endl;
+ }
+ std::cerr << std::endl;
+
+ // Get the _NET_CLIENT_LIST_STACKING property
+ // If an error occurs, it will be thrown only when trying to access the reply
+ std::string _net_client_list_stacking = "_NET_CLIENT_LIST_STACKING";
+ auto net_client_list_stacking_atom =
+ connection.intern_atom(false, _net_client_list_stacking);
+ auto net_client_list_stacking = connection.get_property(
+ false, connection.root(), net_client_list_stacking_atom.atom(),
+ XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+ try {
+ std::cerr << _net_client_list_stacking << " (xcb_window_t):" << std::hex;
+ for (auto && w : net_client_list_stacking.value<xcb_window_t>()) {
+ std::cerr << " 0x" << w;
+ }
+ std::cerr << std::dec << std::endl;
+
+ std::cerr << _net_client_list_stacking << " (x::window):";
+ for (auto && w : net_client_list_stacking.value<x::window>()) {
+ std::cerr << " " << w;
+ }
+ std::cerr << std::endl;
+
+ std::cerr << _net_client_list_stacking << " (x::xcb_window):";
+ for (auto && w : net_client_list_stacking.value<x::xcb_window>()) {
+ std::cerr << " " << w;
+ }
+ std::cerr << std::endl;
+
+ } catch (const std::exception & e) {
+ std::cerr << "Could not get " << _net_client_list_stacking << " property: "
+ << e.what() << std::endl;
+ }
+ std::cerr << std::endl;
+
+ // Randr needs query_version to work properly in subsequent calls
+ // If methods are ambiguous (like query_version, then the extension interface
+ // can be accessed through "extension_name()" (e.g. randr() or damage())
+ connection.randr().query_version(XCB_RANDR_MAJOR_VERSION,
+ XCB_RANDR_MINOR_VERSION);
+
+ connection.select_input_checked(connection.root(), XCB_RANDR_NOTIFY);
+
+ const auto & randr_ext = connection.extension<xpp::randr::extension>();
+
+ std::cerr << "RandR Extension" << std::endl;
+ std::cerr << "\tfirst_event: " << (int)randr_ext->first_event << std::endl;
+ std::cerr << "\tfirst_error: " << (int)randr_ext->first_error << std::endl;
+
+ const auto & damage_ext = connection.extension<xpp::damage::extension>();
+
+ std::cerr << "Damage Extension" << std::endl;
+ std::cerr << "\tfirst_event: " << (int)damage_ext->first_event << std::endl;
+ std::cerr << "\tfirst_error: " << (int)damage_ext->first_error << std::endl;
+
+ std::cerr << std::endl;
+
+ try {
+ // Produces XCB_RANDR_BAD_OUTPUT error
+ auto output_info = connection.get_output_info(-1);
+ output_info.get();
+ } catch (const std::exception & e) {
+ std::cerr << "get_output_info error: " << e.what() << std::endl;
+ }
+
+ try {
+ // Produces XCB_RANDR_BAD_CRTC error
+ auto crtc_info = connection.get_crtc_info(-1);
+ crtc_info.get();
+ } catch (const std::exception & e) {
+ std::cerr << "get_crtc_info error: " << e.what() << std::endl;
+ }
+
+ // Produces XCB_RANDR_BAD_OUTPUT error in event queue
+ auto output_info = connection.get_output_info_unchecked(-1);
+ output_info.get();
+
+ // Produces XCB_RANDR_BAD_CRTC error in event queue
+ auto crtc_info = connection.get_crtc_info_unchecked(-1);
+ crtc_info.get();
+
+ try {
+ // XCB_VALUE error
+ connection.change_output_property_checked(-1, -1, -1, 0, 0, 0, nullptr);
+ } catch (const std::exception & e) {
+ std::cerr << "change_output_property error: " << e.what() << std::endl;
+ }
+
+ // XCB_VALUE error in event queue
+ connection.change_output_property(-1, -1, -1, 0, 0, 0, nullptr);
+
+ try {
+ // XCB_RENDER_PICT_FORMAT error
+ auto pict_index_values = connection.query_pict_index_values(-1);
+ pict_index_values.get();
+ } catch (const std::exception & e) {
+ std::cerr << "query_pict_index_values error: " << e.what() << std::endl;
+ }
+
+ try {
+ // XCB_RENDER_PICTURE error
+ connection.change_picture_checked(-1, 0, nullptr);
+ } catch (const std::exception & e) {
+ std::cerr << "change_picture error: " << e.what() << std::endl;
+ }
+
+ // XCB_RENDER_PICT_FORMAT error in event queue
+ auto pict_index_values = connection.query_pict_index_values_unchecked(-1);
+ pict_index_values.get();
+
+ // XCB_RENDER_PICTURE error in event queue
+ connection.change_picture(-1, 0, nullptr);
+
+ // Poll the event queue a couple of times to get the errors
+ for (int i = 0; i < 5; ++i) {
+ connection.flush();
+ try {
+ registry.dispatch(connection.wait_for_event());
+ } catch (const std::exception & e) {
+ std::cerr << "std::exception in event queue: " << e.what() << std::endl;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+event
+iterator
--- /dev/null
+include ../../include/flags.makefile
+
+# CXX=clang
+CXXFLAGS+=-g
+# CXXFLAGS+=-Wextra
+# CXXFLAGS+=-ftime-report
+
+CPPSRCS=event.cpp \
+ requests.cpp \
+ iterator.cpp
+
+all: ${CPPSRCS}
+
+${CPPSRCS}:
+ ${CXX} ${LDFLAGS} ${CXXFLAGS} -o $(@:%.cpp=%) $@
+
+xlib-test: xlib-test.cpp
+ ${CXX} $(shell pkg-config --libs --cflags x11 xrandr) -o $@ $<
+
+version:
+
+clean:
+ rm -f ${CPPSRCS:%.cpp=%}
+
+.PHONY: ${CPPSRCS} clean
--- /dev/null
+Just a couple of experiments to try out concepts, language features, ideas.
+Might or might not compile, run or crash, etc.
--- /dev/null
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+
+#define CALLABLE(FUNCTION) callable<decltype(FUNCTION), FUNCTION>
+
+template<typename Signature, Signature & S>
+struct callable;
+
+template<typename Return,
+ typename ... Args, Return (&Function)(Args ...)>
+struct callable<Return(Args ...), Function> {
+ Return operator()(Args ... args)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return Function(args ...);
+ }
+};
+
+template<typename ... Arguments>
+class one_size_fits_them_all;
+
+// A generic template
+template<typename T, typename U, typename V,
+ typename F1, typename F2, typename F3>
+class one_size_fits_them_all<T, U, V, F1, F2, F3>
+{
+ public:
+ one_size_fits_them_all(void)
+ {
+ std::cerr << "generic one_size_fits_them_all" << std::endl
+ << __PRETTY_FUNCTION__ << std::endl << std::endl;
+ F1()();
+ F2()();
+ F3()();
+ std::cerr << std::endl;
+ }
+};
+
+// A specialized template
+template<typename T, typename Callable>
+class one_size_fits_them_all<T, int, int, void, void, Callable>
+{
+ public:
+ one_size_fits_them_all(void)
+ {
+ std::cerr << "specialized one_size_fits_them_all" << std::endl
+ << __PRETTY_FUNCTION__ << std::endl << std::endl;
+ Callable()();
+ std::cerr << std::endl;
+ }
+};
+
+void f1(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f2(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f3(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+int main(int argc, char ** argv)
+{
+ // generic template
+ auto generic = one_size_fits_them_all<
+ int, int, int, CALLABLE(f1), CALLABLE(f2), CALLABLE(f3)>();
+
+ // specialized template
+ auto specialized_int = one_size_fits_them_all<
+ int, int, int, void, void, CALLABLE(f1)>();
+
+ // specialized template
+ auto specialized_double = one_size_fits_them_all<
+ double, int, int, void, void, CALLABLE(f3)>();
+
+ return 0;
+}
--- /dev/null
+#include <iostream>
+#include <string>
+
+#include <xcb/xcb.h>
+
+#include "../event.hpp"
+#include "../core/value_iterator.hpp"
+#include "../core/connection.hpp"
+#include "../core/extension.hpp"
+#include "../core/error.hpp"
+
+#include "../request.hpp"
+#include "../request_ng.hpp"
+
+#define CHECKED_REQUEST(NAME) \
+ decltype(NAME ## _reply), NAME ## _reply, \
+ decltype(NAME), NAME
+
+#define UNCHECKED_REQUEST(NAME) \
+ decltype(NAME ## _reply), NAME ## _reply, \
+ decltype(NAME ## _unchecked), NAME ## _unchecked
+
+namespace xpp { namespace generic {
+
+// class get_screen_info_checked
+// : public checked_reply_request<xpp::connection<xpp::extension::randr>,
+// xpp::extension::randr,
+// CHECKED_REQUEST(xcb_randr_get_screen_info)>
+// {
+// public:
+// typedef checked_reply_request<xpp::connection<xpp::extension::randr>,
+// xpp::extension::randr,
+// CHECKED_REQUEST(xcb_randr_get_screen_info)>
+// base;
+// using base::base;
+// };
+
+// class get_screen_info_unchecked
+// : public unchecked_reply_request<UNCHECKED_REQUEST(xcb_randr_get_screen_info)>
+// {
+// public:
+// typedef unchecked_reply_request<UNCHECKED_REQUEST(xcb_randr_get_screen_info)>
+// base;
+// using base::base;
+// };
+
+// template<typename NoThrow = void>
+// class get_screen_info
+// : public std::conditional<std::is_same<NoThrow, std::nothrow_t>::value,
+// get_screen_info_unchecked,
+// get_screen_info_checked>::type
+// {
+// public:
+// typedef typename std::conditional<
+// std::is_same<NoThrow, std::nothrow_t>::value,
+// get_screen_info_unchecked,
+// get_screen_info_checked>::type
+// base;
+// using base::base;
+// };
+
+/*
+class query_tree_checked
+ : public checked_reply_request<xpp::connection<>, void,
+ CHECKED_REQUEST(xcb_query_tree)>
+{
+ public:
+ typedef checked_reply_request<xpp::connection<>, void,
+ CHECKED_REQUEST(xcb_query_tree)>
+ base;
+ using base::base;
+};
+
+class query_tree_unchecked
+ : public unchecked_reply_request<UNCHECKED_REQUEST(xcb_query_tree)>
+{
+ public:
+ typedef unchecked_reply_request<UNCHECKED_REQUEST(xcb_query_tree)>
+ base;
+ using base::base;
+};
+
+template<typename NoThrow = void>
+class query_tree
+ : public std::conditional<std::is_same<NoThrow, std::nothrow_t>::value,
+ query_tree_unchecked,
+ query_tree_checked
+ >::type
+{
+ public:
+ typedef typename std::conditional<
+ std::is_same<NoThrow, std::nothrow_t>::value,
+ query_tree_unchecked,
+ query_tree_checked
+ >::type
+ base;
+ using base::base;
+};
+*/
+
+// class query_tree_checked
+// : public xpp::generic::checked::request<xpp::extension::x,
+// CHECKED_REQUEST(xcb_query_tree)>
+// {
+// typedef xpp::generic::checked::request<xpp::extension::x,
+// CHECKED_REQUEST(xcb_query_tree)>
+// base;
+// using base::base;
+// };
+
+// class map_window_checked
+// : public ::test::generic::checked::request<xpp::extension::x,
+// CHECKED_REQUEST(xcb_map_window)>
+// {
+// typedef ::test::generic::checked::request<xpp::extension::x,
+// CHECKED_REQUEST(xcb_map_window)>
+// base;
+// using base::base;
+// };
+
+// class get_screen_info
+// // : public wrapper<xpp::connection<xpp::extension::randr>, xpp::extension::randr,
+// : public wrapper<xpp::connection<xpp::extension::randr>,
+// xpp::extension::randr, xpp::extension::x,
+// decltype(xcb_randr_get_screen_info_reply), xcb_randr_get_screen_info_reply,
+// decltype(xcb_randr_get_screen_info), xcb_randr_get_screen_info>
+// {
+// public:
+// // typedef wrapper<xpp::extension::randr,
+// typedef wrapper<xpp::connection<xpp::extension::randr>,
+// xpp::extension::randr, xpp::extension::x,
+// decltype(xcb_randr_get_screen_info_reply), xcb_randr_get_screen_info_reply,
+// decltype(xcb_randr_get_screen_info), xcb_randr_get_screen_info>
+// base;
+// using base::base;
+// };
+
+// class query_tree
+// // : public wrapper<xpp::extension::x,
+// : public wrapper<xpp::connection<>,
+// xpp::extension::x, xpp::extension::x,
+// decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+// decltype(xcb_query_tree), xcb_query_tree>
+// {
+// public:
+// typedef wrapper<xpp::connection<>,
+// xpp::extension::x, xpp::extension::x,
+// decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+// decltype(xcb_query_tree), xcb_query_tree>
+// base;
+// using base::base;
+// };
+
+// class dpms_capable
+// // : public wrapper<// xpp::connection<xpp::extension::dpms>, xpp::extension::dpms,
+// : public wrapper<xpp::extension::dpms,
+// decltype(xcb_dpms_capable_reply), xcb_dpms_capable_reply,
+// decltype(xcb_dpms_capable), xcb_dpms_capable>
+// {
+// public:
+// // typedef wrapper<// xpp::connection<xpp::extension::dpms>, xpp::extension::dpms,
+// typedef wrapper<xpp::extension::dpms,
+// decltype(xcb_dpms_capable_reply), xcb_dpms_capable_reply,
+// decltype(xcb_dpms_capable), xcb_dpms_capable>
+// base;
+// using base::base;
+// };
+
+// class render_query_pict_index_values
+// // : public wrapper<// xpp::connection<xpp::extension::render>, xpp::extension::render,
+// : public wrapper<xpp::extension::render,
+// decltype(xcb_render_query_pict_index_values_reply), xcb_render_query_pict_index_values_reply,
+// decltype(xcb_render_query_pict_index_values), xcb_render_query_pict_index_values>
+// {
+// public:
+// // typedef wrapper<// xpp::connection<xpp::extension::render>, xpp::extension::render,
+// typedef wrapper<xpp::extension::render,
+// decltype(xcb_render_query_pict_index_values_reply), xcb_render_query_pict_index_values_reply,
+// decltype(xcb_render_query_pict_index_values), xcb_render_query_pict_index_values>
+// base;
+// using base::base;
+// };
+
+// class render_query_filters
+// // : public wrapper<xpp::extension::render,
+// : public wrapper<xpp::connection<xpp::extension::render>,
+// xpp::extension::render, xpp::extension::x,
+// decltype(xcb_render_query_filters_reply), xcb_render_query_filters_reply,
+// decltype(xcb_render_query_filters), xcb_render_query_filters>
+// {
+// public:
+// // typedef wrapper<xpp::extension::render,
+// typedef wrapper<xpp::connection<xpp::extension::render>,
+// xpp::extension::render, xpp::extension::x,
+// decltype(xcb_render_query_filters_reply), xcb_render_query_filters_reply,
+// decltype(xcb_render_query_filters), xcb_render_query_filters>
+// base;
+// using base::base;
+// };
+
+// template<>
+// class query_tree<xcb_connection_t>
+// : public wrapper<xcb_connection_t, void,
+// decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+// decltype(xcb_query_tree), xcb_query_tree>
+// {
+// public:
+// typedef wrapper<xcb_connection_t, void,
+// decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+// decltype(xcb_query_tree), xcb_query_tree>
+// base;
+// using base::base;
+// };
+
+}; }; // namespace xpp::generic
+
+void error_dispatcher(xcb_generic_error_t * error)
+{
+ switch (error->error_code) {
+ case XCB_WINDOW:
+ xcb_window_error_t * e = (xcb_window_error_t *)error;
+ std::cerr << "XCB_WINDOW with bad_value: " << e->bad_value << std::endl;
+ // throw xpp::error::generic<XCB_WINDOW, xcb_window_error_t>(error);
+ throw xpp::generic::error<XCB_WINDOW, xcb_window_error_t>(error);
+ // throw xpp::x::window_error(error);
+ break;
+ }
+}
+
+void check_error_direct(xcb_generic_error_t * error)
+{
+ if (error) {
+ std::cerr << "error code: " << (int)error->error_code << std::endl;
+ try {
+ error_dispatcher(error);
+ } catch (const std::exception & exception) {
+ std::cerr << "caught exception: " << exception.what() << std::endl;
+ }
+ } else {
+ std::cerr << "no error occurred" << std::endl;
+ }
+}
+
+void check_error_event(xcb_connection_t * c)
+{
+ xcb_generic_event_t * event = xcb_poll_for_event(c);
+ xcb_flush(c);
+ if (event) {
+ uint8_t response = event->response_type & ~0x80;
+ std::cerr << "event response: " << (int)response << std::endl;
+ check_error_direct((xcb_generic_error_t *)event);
+ } else {
+ std::cerr << "no event available" << std::endl;
+ }
+}
+
+void
+map_window(xcb_connection_t * c, xcb_window_t window)
+{
+ xcb_map_window(c, window);
+}
+
+template<typename Extension>
+void
+map_window(const Extension & e, xcb_connection_t * c, xcb_window_t window)
+{
+ typedef typename Extension::error_dispatcher dispatcher;
+ dispatcher(/* e->first_error */)(
+ xcb_request_check(c, xcb_map_window_checked(c, window)));
+}
+
+template<typename ... Parameters>
+void
+map_window(const xpp::connection<Parameters ...> & c, xcb_window_t window)
+{
+ map_window(static_cast<const xpp::extension::x &>(c), c, window);
+}
+
+int main(int argc, char ** argv)
+{
+ xpp::connection<xpp::extension::randr,
+ xpp::extension::render,
+ xpp::extension::dpms> c("");
+ // xpp::connection<> c("");
+
+ // auto tree_1 = xpp::generic::query_tree<decltype(c), xpp::extension::x>(c, 0);
+ // auto tree_1 = xpp::generic::query_tree<decltype(c)>(c, 0);
+ // auto tree_1 = xpp::generic::query_tree<>(static_cast<xcb_connection_t *>(c), 0);
+
+ auto & randr = static_cast<xpp::randr::protocol &>(c);
+ auto & render = static_cast<xpp::render::protocol &>(c);
+ auto & dpms = static_cast<xpp::dpms::protocol &>(c);
+
+ auto & randr_ext = static_cast<xpp::extension::randr &>(c);
+ auto & render_ext = static_cast<xpp::extension::render &>(c);
+ auto & dpms_ext = static_cast<xpp::extension::dpms &>(c);
+
+ *randr.query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+ *render.query_version(XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION);
+ *dpms.get_version(XCB_DPMS_MAJOR_VERSION, XCB_DPMS_MINOR_VERSION);
+
+ std::cerr << "randr first_error: " << (int)randr_ext->first_error << std::endl;
+ std::cerr << "render first_error: " << (int)render_ext->first_error << std::endl;
+ std::cerr << "dpms first_error: " << (int)dpms_ext->first_error << std::endl;
+
+ // auto tree = xpp::generic::query_tree(c, 0);
+ // std::cerr << "tree go" << std::endl;
+ // try {
+ // tree.get();
+ // auto error = tree.error();
+ // std::cerr << error.get() << std::endl;
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "tree fin" << std::endl << std::endl;
+
+ // auto screen_info = xpp::generic::get_screen_info(c, 0);
+ // std::cerr << "screen_info go" << std::endl;
+ // try {
+ // screen_info.get();
+ // auto error = screen_info.error();
+ // std::cerr << error.get() << std::endl;
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "screen_info fin" << std::endl << std::endl;
+
+ // auto dpms_capable = xpp::generic::dpms_capable(c);
+ // std::cerr << "dpms_capable go" << std::endl;
+ // try {
+ // dpms_capable.get();
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "dpms_capable fin" << std::endl;
+
+ // auto pict_index_values = xpp::generic::render_query_pict_index_values(c, -1);
+ // std::cerr << "pict_index_values go" << std::endl;
+ // try {
+ // pict_index_values.get();
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "pict_index_values fin" << std::endl;
+
+ // auto filters = xpp::generic::render_query_filters(c, 0);
+ // std::cerr << "filters go" << std::endl;
+ // try {
+ // filters.get();
+ // auto error = filters.error();
+ // std::cerr << error.get() << std::endl;
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "filters fin" << std::endl << std::endl;
+
+
+
+ // // auto tree_2 = xpp::generic::query_tree<xcb_connection_t>(c, 0);
+ // auto tree_2 = xpp::generic::query_tree<>(c, 0);
+ // std::cerr << "tree_2 go" << std::endl;
+ // try {
+ // tree_2.get();
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+ // std::cerr << "tree_2 fin" << std::endl;
+
+ // auto reply = xpp::request::x::query_tree(c, 0);
+ // reply.get();
+ // reply.check((xpp::extension::x &)c);
+
+ // auto reply = xpp::request::x::query_tree(c, 0);
+ // reply.get();
+ // try {
+ // reply.check((xpp::extension::x &)c);
+ // } catch (const std::exception & exception) {
+ // std::cerr << "Exception: " << exception.what() << std::endl;
+ // }
+
+
+ // return 0;
+
+ /*
+ map_window(*c, 0);
+
+ try {
+ map_window(c, 0);
+ } catch (...) {}
+
+ try {
+ map_window((xpp::extension::x &)c, c, 0);
+ map_window((xpp::extension::x &)c, c, 0);
+ // map_window((xpp::extension::randr &)c, c, 0);
+ } catch (...) {}
+ */
+
+ // xcb_generic_error_t * generic_error = NULL;
+ // xcb_query_tree_reply_t * query_tree_reply = NULL;
+
+ // checked request with reply, error directly available
+ {
+ std::cerr << "checked request with reply, error directly available" << std::endl;
+ // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree(c, 5);
+ // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+ try {
+ auto screen_info = xpp::request::randr::get_screen_info(c, 0);
+ // auto tree = xpp::generic::query_tree<>(c, 5);
+ screen_info.get();
+ } catch (const xpp::x::error::window & e) {
+ std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+ } catch (const std::exception & e) {
+ std::cerr << "std::exception: " << e.what() << std::endl;
+ }
+ // auto reply = tree.get();
+ c.flush();
+ // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+ // check_error_direct(tree.error());
+ // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+ // query_tree_reply = NULL;
+ // check_error_direct(generic_error);
+ // check_error_direct(generic_error);
+ // generic_error = NULL;
+ }
+ std::cerr << std::endl;
+
+ // checked request with reply, error directly available
+ {
+ std::cerr << "checked request with reply, error directly available" << std::endl;
+ // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree(c, 5);
+ // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+ try {
+ auto tree = xpp::request::x::query_tree(c, 5);
+ // auto tree = xpp::generic::query_tree<>(c, 5);
+ tree.get();
+ } catch (const xpp::x::error::window & e) {
+ std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+ } catch (const std::exception & e) {
+ std::cerr << "std::exception: " << e.what() << std::endl;
+ }
+ // auto reply = tree.get();
+ c.flush();
+ // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+ // check_error_direct(tree.error());
+ // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+ // query_tree_reply = NULL;
+ // check_error_direct(generic_error);
+ // check_error_direct(generic_error);
+ // generic_error = NULL;
+ }
+ std::cerr << std::endl;
+
+ // // unchecked request with reply, error in event queue
+ // {
+ // std::cerr << "unchecked request with reply, error in event queue" << std::endl;
+ // // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree_unchecked(c, 17);
+ // // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+ // auto tree = xpp::generic::query_tree<std::nothrow_t>(c, 5);
+ // auto reply = tree.get();
+ // c.flush();
+ // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+ // // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+ // // std::cerr << "query_tree_reply error: " << generic_error << std::endl;
+ // // query_tree_reply = NULL;
+ // check_error_event(c);
+ // }
+ // std::cerr << std::endl;
+
+ // // checked request without reply, error directly available
+ // {
+ // std::cerr << "checked request without reply, error directly available" << std::endl;
+ // // xcb_void_cookie_t void_cookie = xcb_map_window_checked(c, 42);
+ // try {
+ // ::test::map_window(c, 0);
+ // // xpp::request::x::map_window(c, 0);
+ // } catch (const xpp::x::error::window & e) {
+ // std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+ // } catch (const std::exception & e) {
+ // std::cerr << "std::exception: " << e.what() << std::endl;
+ // }
+ // c.flush();
+ // // generic_error = xcb_request_check(c, void_cookie);
+ // // check_error_direct(generic_error);
+ // // generic_error = NULL;
+ // }
+ // std::cerr << std::endl;
+
+ // unchecked request without reply, error in event queue
+ {
+ std::cerr << "unchecked request without reply, error in event queue" << std::endl;
+ xcb_map_window(c, 0);
+ c.flush();
+ check_error_event(c);
+ }
+
+ return 0;
+}
--- /dev/null
+#include <iostream>
+
+#include "../event.hpp"
+#include "../connection.hpp"
+
+using namespace xpp;
+using namespace event;
+
+xcb_window_t
+get_window(xcb_button_press_event_t * const e)
+{
+ return e->event;
+}
+
+xcb_window_t
+get_window(xcb_motion_notify_event_t * const e)
+{
+ return e->event;
+}
+
+namespace test {
+
+class handler : public dispatcher
+ , public sink<button::press>
+ , public sink<button::release>
+ , public sink<motion::notify>
+{
+ public:
+ void handle(const button::press &)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ void handle(const button::release & e)
+ {
+ if (XCB_BUTTON_PRESS == (e->response_type & ~0x80)) {
+ std::cerr << __PRETTY_FUNCTION__ << " XCB_BUTTON_PRESS" << std::endl;
+ } else {
+ std::cerr << __PRETTY_FUNCTION__ << " XCB_BUTTON_RELEASE" << std::endl;
+ }
+ }
+
+ void handle(const motion::notify &)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+};
+
+class container : public direct::container {
+ public:
+ dispatcher * const
+ at(const unsigned int & window) const
+ {
+ return m_dispatcher.at(window);
+ }
+
+ std::unordered_map<unsigned int, dispatcher *> m_dispatcher;
+};
+
+struct foo {
+ void bar(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class foo_container : public any::container<foo> {
+ public:
+ foo * const at(const unsigned int & window)
+ {
+ return &m_foos.at(window);
+ }
+
+ std::unordered_map<unsigned int, foo> m_foos;
+};
+
+class foo_handler : public any::adapter<foo, button::press, 0, get_window> {
+ public:
+ using adapter::adapter;
+
+ void handle(foo * const f, const button::press & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " response_type: " << (int)(e->response_type & ~0x80) << std::endl;
+ f->bar();
+ }
+};
+
+}; // namespace test
+
+int main(int argc, char ** argv)
+{
+ connection c("");
+ source source(c);
+
+ auto tree = c.query_tree(c.root());
+
+ test::handler handler;
+ test::container container;
+
+ test::foo_container foo_container;
+ test::foo_handler foo_handler(source, foo_container);
+
+ for (auto & window : tree.children()) {
+ *(c.grab_pointer(false, window,
+ XCB_EVENT_MASK_BUTTON_PRESS
+ | XCB_EVENT_MASK_BUTTON_RELEASE
+ | XCB_EVENT_MASK_POINTER_MOTION,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ XCB_NONE, XCB_NONE, XCB_TIME_CURRENT_TIME));
+
+ container.m_dispatcher[window] = &handler;
+ foo_container.m_foos[window] = test::foo();
+ }
+
+ dispatcher * dispatcher[] =
+ { new direct::adapter<button::press, 0, get_window>(source, container)
+ , new direct::adapter<button::release, 0, get_window>(source, container)
+ , new direct::adapter<motion::notify, 0, get_window>(source, container)
+ };
+
+ source.run();
+
+ for (auto * d : dispatcher) {
+ delete d;
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include <iostream>
+
+#include "../connection.hpp"
+
+class test {
+ public:
+
+ template<typename Data>
+ class iterator {
+ public:
+ iterator(Data * const data, std::size_t index)
+ : m_data(data), m_index(index)
+ {}
+
+ bool operator==(const iterator & other)
+ {
+ return m_index == other.m_index;
+ }
+
+ bool operator!=(const iterator & other)
+ {
+ return ! (*this == other);
+ }
+
+ const Data & operator*(void)
+ {
+ return m_data[m_index];
+ }
+
+ // prefix
+ iterator & operator++(void)
+ {
+ ++m_index;
+ return *this;
+ }
+
+ // postfix
+ iterator operator++(int)
+ {
+ auto copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+ // prefix
+ iterator & operator--(void)
+ {
+ --m_index;
+ return *this;
+ }
+
+ // postfix
+ iterator operator--(int)
+ {
+ auto copy = *this;
+ --(*this);
+ return copy;
+ }
+
+ private:
+ Data * const m_data;
+ std::size_t m_index = 0;
+ };
+
+
+ template<typename T>
+ iterator<T> begin(void)
+ {
+ throw "This must not happen!";
+ }
+
+ template<typename T>
+ iterator<T> end(void)
+ {
+ throw "This must not happen!";
+ }
+
+ // private:
+ std::vector<int> m_ints;
+ std::vector<double> m_doubles;
+
+};
+
+template<>
+test::iterator<int> test::begin<int>(void)
+{
+ return iterator<int>(m_ints.data(), 0);
+}
+
+template<>
+test::iterator<double> test::begin<double>(void)
+{
+ return iterator<double>(m_doubles.data(), 0);
+}
+
+template<>
+test::iterator<int> test::end<int>(void)
+{
+ return iterator<int>(m_ints.data(), m_ints.size());
+}
+
+template<>
+test::iterator<double> test::end<double>(void)
+{
+ return iterator<double>(m_doubles.data(), m_ints.size());
+}
+
+int main(int argc, char ** argv)
+{
+ xpp::connection c("");
+
+ auto tree = c.query_tree(c.root());
+
+ std::cerr << "#windows (children_len): " << tree->children_len << std::endl;
+ std::cerr << "#windows (length): " << tree->length << std::endl;
+
+ std::cerr << std::hex;
+ for (auto & window : tree.children()) {
+ std::cerr << "0x" << window << "; ";
+ }
+ std::cerr << std::dec << std::endl;;
+
+ std::cerr << std::hex;
+ for (auto it = tree.children().begin(); it != tree.children().end(); ++it) {
+ std::cerr << "0x" << *it << "; ";
+ }
+ std::cerr << std::dec << std::endl;;
+
+ std::cerr << std::hex;
+ auto it = tree.children().begin();
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << "++it: " << *(++it) << std::endl;
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << "it++: " << *(it++) << std::endl;
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << "++it: " << *(++it) << std::endl;
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << "--it: " << *(--it) << std::endl;
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << "it--: " << *(it--) << std::endl;
+ std::cerr << "it : " << *it << std::endl;
+ std::cerr << std::dec << std::endl;;
+
+ auto atom = c.intern_atom(false, "_NET_CLIENT_LIST_STACKING");
+ auto properties = c.get_property<xcb_window_t>(
+ false, c.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+ std::cerr << std::hex;
+ for (auto & window : properties) {
+ std::cerr << "0x" << window << "; ";
+ }
+ std::cerr << std::dec << std::endl;;
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include <iostream>
+
+// #include "../request.hpp"
+// #include "../core/connection.hpp"
+// #include "../core/window.hpp"
+// #include <xcb/xcb.h>
+// #include "../gen/sure_dude.hpp"
+// #include "../gen/xproto-stub.hpp"
+
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h> // XC_cross
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+
+/*
+namespace xpp { namespace x { namespace error {
+
+void
+dispatcher::operator()(const std::shared_ptr<xcb_generic_error_t> &) const
+{
+ throw std::runtime_error(
+ std::string(__PRETTY_FUNCTION__)
+ + "\n\thandling errors like a boss with first_error = "
+ + std::to_string((uint32_t)m_first_error));
+}
+
+}; }; }; // namespace xpp::x::error
+*/
+
+// namespace my {
+//
+// struct dispatcher {
+// dispatcher(xcb_connection_t *) {}
+// void
+// operator()(const std::shared_ptr<xcb_generic_error_t> &) const
+// {
+// std::cerr << __PRETTY_FUNCTION__ << "\n\thandling errors like a boss" << std::endl;
+// }
+// };
+//
+// }; // namespace my
+
+/*
+namespace xpp {
+
+struct dummy_extension {
+ uint8_t first_error = 42;
+};
+
+struct connection
+ : public dummy_extension
+ , public xpp::x::extension
+ , public xpp::x::error::dispatcher
+ // , public xpp::x::protocol<connection>
+ // , virtual public xpp::generic::connection<xpp::connection>
+ , public xpp::x::protocol<connection &>
+ , virtual public xpp::generic::connection<xpp::connection &>
+{
+ connection(xcb_connection_t * c)
+ : xpp::x::error::dispatcher(
+ static_cast<dummy_extension &>(*this).first_error)
+ , m_c(c)
+ , m_data(new int)
+ {
+ m_data = std::make_shared<int>();
+ std::cerr << "connection c'tor: m_data.use_count(): "
+ << m_data.use_count() << std::endl;
+ }
+
+ ~connection(void)
+ {
+ std::cerr << "~connection d'tor: m_data.use_count(): "
+ << m_data.use_count() << std::endl;
+ }
+
+ connection(connection & other)
+ : xpp::x::error::dispatcher(
+ static_cast<const dummy_extension &>(other).first_error)
+ , m_c(other.m_c)
+ // , m_data(other.m_data)
+ {
+ std::cerr << "connection copy c'tor" << std::endl;
+ }
+
+ connection(connection && other) = default;
+ // connection(const connection && other)
+ // : xpp::x::error::dispatcher(
+ // static_cast<const dummy_extension &&>(other).first_error)
+ // , m_c(other.m_c)
+ // {
+ // std::cerr << "connection move c'tor" << std::endl;
+ // }
+
+ operator xcb_connection_t * const(void) const
+ {
+ return m_c;
+ }
+
+ xpp::connection &
+ // xpp::connection
+ get(void)
+ {
+ return *this;
+ }
+
+ xcb_connection_t * m_c;
+ // std::vector<int> m_data;
+ std::shared_ptr<int> m_data;
+};
+
+class window
+ : public xpp::x::window<xpp::connection &>
+ // , virtual public xpp::iterable<void>
+ , virtual public xpp::iterable<xcb_window_t>
+ // , virtual public xpp::xcb::type<const xcb_window_t &>
+ // , virtual protected xpp::generic::connection<connection &>
+{
+ public:
+ window(xpp::connection & c, const xcb_window_t & window)
+ : m_c(c)
+ , m_window(window)
+ {}
+
+ // xpp::iterable<const xcb_window_t &>
+ virtual
+ void
+ operator=(xcb_window_t window)
+ {
+ m_window = window;
+ }
+
+ virtual operator const xcb_window_t &(void) const
+ {
+ return m_window;
+ }
+
+ protected:
+ xpp::connection & m_c;
+ xcb_window_t m_window;
+
+ xpp::connection &
+ get(void)
+ {
+ return m_c;
+ }
+};
+
+struct dummy_atom {
+ dummy_atom(const xcb_atom_t & atom, xcb_connection_t * const) : m_atom(atom) {}
+ xcb_atom_t m_atom;
+};
+
+}; // xpp
+*/
+
+// namespace xpp { namespace generic {
+//
+// template<REPLY_TEMPLATE>
+// struct reply_getter<xpp::connection, REPLY_SIGNATURE, checked_tag>
+// {
+// std::shared_ptr<Reply>
+// operator()(const xpp::connection & c, const Cookie & cookie)
+// {
+// std::cerr << "reply_getter special NO error" << std::endl;
+// return std::shared_ptr<Reply>(ReplyFunction(c, cookie, nullptr), std::free);
+// }
+// };
+//
+// }; };
+
+bool g_quit = false;
+
+namespace x {
+ typedef xpp::connection<xpp::randr::extension,
+ xpp::damage::extension,
+ xpp::render::extension>
+ connection;
+
+ typedef xpp::event::registry<connection &,
+ xpp::randr::extension,
+ xpp::damage::extension,
+ xpp::render::extension>
+ registry;
+
+ typedef xpp::font<connection &> font;
+ typedef xpp::cursor<connection &> cursor;
+ typedef xpp::window<connection &> window;
+ typedef xpp::window<xcb_connection_t *> xcb_window;
+
+ typedef xpp::x::event::key_press<connection &> key_press;
+ typedef xpp::x::event::key_release<connection &> key_release;
+ typedef xpp::x::event::button_press<connection &> button_press;
+ typedef xpp::randr::event::notify<connection &> randr_notify;
+ typedef xpp::randr::event::screen_change_notify<connection &> randr_screen_change_notify;
+ typedef xpp::damage::event::notify<connection &> damage_notify;
+};
+
+class one_event
+ : public xpp::event::sink<x::key_press>
+{
+ public:
+ void handle(const x::key_press &) {}
+};
+
+class two_event
+ : public xpp::event::sink<x::key_press, x::key_release>
+{
+ public:
+ void handle(const x::key_press &) {}
+ void handle(const x::key_release &) {}
+};
+
+class more_events
+ : public xpp::event::sink<x::randr_notify, x::randr_screen_change_notify, x::damage_notify>
+{
+ void handle(const x::randr_notify &)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+ void handle(const x::randr_screen_change_notify &)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+ void handle(const x::damage_notify &) {}
+};
+
+template<typename Connection>
+class key_printer
+ : public xpp::event::sink<x::key_press,
+ x::key_release,
+ x::button_press>
+ // xpp::randr::event::notify,
+ // xpp::damage::event::notify,
+ // xpp::xfixes::event::selection_notify,
+ // xpp::screensaver::event::notify
+ // >
+{
+ public:
+ template<typename C>
+ key_printer(C && c)
+ : m_c(std::forward<C>(c))
+ {}
+
+ void handle(const x::key_press & e)
+ {
+ auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+ auto keysym = *kbd_mapping.keysyms().begin();
+
+ if (keysym == XK_Escape) {
+ std::cerr << "quitting" << std::endl;
+ m_c.ungrab_keyboard(XCB_TIME_CURRENT_TIME);
+ g_quit = true;
+ } else {
+ std::cerr << "key pressed: " << XKeysymToString(keysym) << std::endl;
+ }
+ }
+
+ void handle(const x::key_release & e)
+ {
+ auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+ auto keysym = *kbd_mapping.keysyms().begin();
+ std::cerr << "key released: " << XKeysymToString(keysym) << std::endl;
+ }
+
+ void handle(const x::button_press & e)
+ {
+ m_c.ungrab_pointer(XCB_TIME_CURRENT_TIME);
+
+ std::cerr << "root: 0x"
+ << std::hex << e->root << std::dec
+ << "; event: 0x"
+ << std::hex << e->event << std::dec
+ << "; child: 0x"
+ << std::hex << e->child << std::dec
+ << std::endl;
+
+ // xcb_window_t w = e.event();
+ x::window grab_window = e.event<x::window>();
+ std::cerr << "grab_window: " << grab_window << std::endl;
+
+ if (e->event == e->root) {
+ grab_window = e.child();
+ std::cerr << "new grab_window: " << grab_window << std::endl;
+ auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+ grab_window = translate->child;
+ }
+
+ std::cerr << "grabbing "
+ << std::hex << grab_window << std::dec
+ << std::endl;
+
+ *m_c.grab_keyboard(true, grab_window,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+ // g_quit = true;
+ }
+
+ // void handle(const xpp::randr::event::notify & e)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ // void handle(const xpp::damage::event::notify & e)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ // void handle(const xpp::xfixes::event::selection_notify & e)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ // void handle(const xpp::screensaver::event::notify & e)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ // does not work
+ // template<int OpCode, typename Event>
+ // void handle(const xpp::generic::event<OpCode, Event> &)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ private:
+ Connection m_c;
+};
+
+xcb_screen_t *
+screen_of_display(xcb_connection_t * c, int screen)
+{
+ xcb_screen_iterator_t iter;
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(c));
+ for (; iter.rem; --screen, xcb_screen_next(&iter))
+ if (screen == 0)
+ return iter.data;
+
+ return NULL;
+}
+
+int main(int argc, char ** argv)
+{
+ // int default_screen;
+ // xcb_connection_t * c = xcb_connect(nullptr, &default_screen);
+ // xcb_screen_t * screen = screen_of_display(c, default_screen);
+
+ // x::error_handler eh;
+ x::connection connection;
+ // xcb_connection_t * xcb_c = nullptr;
+
+ try {
+ x::window window(connection, argc > 1 ? std::strtol(argv[1], NULL, 10) : 0);
+ auto tree = window.query_tree();
+ std::cerr << "children of window (" << window << "): ";
+ for (auto && child : tree.children<x::window>()) {
+ std::cerr << child << ", ";
+ }
+ std::cerr << std::endl;
+ } catch (const std::exception & exception) {
+ std::cerr << std::endl;
+ std::cerr << "window exception: " << exception.what() << std::endl;
+ }
+
+ auto net_client_list_stacking_atom = connection.intern_atom(
+ false, "_NET_CLIENT_LIST_STACKING");
+ auto net_client_list_stacking = connection.get_property(
+ false, connection.root(), net_client_list_stacking_atom.atom(),
+ XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+ std::cerr << "_NET_CLIENT_LIST_STACKING (xcb_window_t):" << std::hex;
+ for (auto && w : net_client_list_stacking.value<xcb_window_t>()) {
+ std::cerr << " 0x" << w;
+ }
+ std::cerr << std::dec << std::endl;
+
+ std::cerr << "_NET_CLIENT_LIST_STACKING (x::window):";
+ for (auto && w : net_client_list_stacking.value<x::window>()) {
+ std::cerr << " " << w;
+ }
+ std::cerr << std::endl;
+
+ std::cerr << "_NET_CLIENT_LIST_STACKING (x::xcb_window):";
+ for (auto && w : net_client_list_stacking.value<x::xcb_window>()) {
+ std::cerr << " " << w;
+ }
+ std::cerr << std::endl;
+
+// #ifndef __clang__
+ // static_cast<xpp::randr::protocol<const x::connection &> &>(connection)
+ // .query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+ // connection.interface<xpp::randr::extension>()
+ connection.randr().query_version(
+ XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+ const xpp::randr::extension & randr = connection.extension<xpp::randr::extension>();
+ // xpp::randr::extension & randr = static_cast<xpp::randr::extension &>(connection);
+ std::cerr << "RandR Extension" << std::endl;
+ std::cerr << "\tfirst_event: " << (int)randr->first_event << std::endl;
+ std::cerr << "\tfirst_error: " << (int)randr->first_error << std::endl;
+
+ connection.select_input_checked(connection.root(), XCB_RANDR_NOTIFY);
+
+ const xpp::damage::extension & damage = connection.extension<xpp::damage::extension>();
+ std::cerr << "Damage Extension" << std::endl;
+ std::cerr << "\tfirst_event: " << (int)damage->first_event << std::endl;
+ std::cerr << "\tfirst_error: " << (int)damage->first_error << std::endl;
+// #endif
+
+ x::registry registry(connection);
+
+ // key_printer<x::connection &> key_printer(connection);
+ std::vector<key_printer<x::connection &>> key_printers(100, connection);
+ // std::vector<key_printer<x::connection &> *> key_printers(100, new key_printer<x::connection &>(connection));
+
+ const int n = 2;
+ // registry.attach(0, &key_printer);
+ for (int i = 0; i < n; ++i) {
+ registry.attach(0, &key_printers[i]);
+ // registry.attach(0, key_printers[i]);
+ }
+
+ for (int i = 0; i < n - 1; ++i) {
+ registry.detach(0, &key_printers[i]);
+ // registry.detach(0, key_printers[i]);
+ }
+
+ one_event oe;
+ two_event te;
+ more_events me;
+
+ registry.attach(0, &oe);
+ registry.attach(0, &te);
+ registry.detach(0, &oe);
+ registry.detach(0, &te);
+
+ registry.attach(0, &me);
+ // registry.detach(0, &me);
+
+
+ // auto font = connection.generate_id();
+ // connection.open_font(font, "cursor");
+
+ // auto cursor = connection.generate_id();
+ // connection.create_glyph_cursor(cursor, font, font,
+ // XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+ // connection.close_font(font);
+ // connection.close_cursor(cursor);
+ // x::cursor cursor_xid = x::cursor(connection, 0);
+
+
+ try {
+ x::font font = x::font::open_checked(connection, "cursor");
+
+ x::cursor cursor = x::cursor::create_glyph_checked(connection, font, font,
+ XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+ *connection.grab_pointer(false, connection.root(),
+ XCB_EVENT_MASK_BUTTON_PRESS,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ XCB_NONE, cursor, XCB_TIME_CURRENT_TIME);
+
+ std::cerr << "Please click on a window" << std::endl;
+
+ } catch (const std::exception & error) {
+ std::cerr << "Exception (std::exception) in "
+ << __FILE__ << " @ line " << __LINE__ << ", what(): "
+ << error.what() << std::endl;
+ std::exit(EXIT_FAILURE);
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "Exception (std::shared_ptr<xcb_generic_error_t>) in "
+ << __FILE__ << " @ line " << __LINE__ << ", error_code: "
+ << (int)error->error_code << std::endl;
+ std::exit(EXIT_FAILURE);
+ } catch (...) {
+ std::cerr << "Exception (...) in "
+ << __FILE__ << " @ line " << __LINE__
+ << std::endl;
+ std::exit(EXIT_FAILURE);
+ }
+
+
+ // xcb_randr_get_output_info_cookie_t goic = xcb_randr_get_output_info_unchecked(connection, -1, XCB_TIME_CURRENT_TIME);
+ // xcb_randr_get_output_info_reply_t * goir = xcb_randr_get_output_info_reply(connection, goic, nullptr);
+
+ // XCB_RANDR_BAD_OUTPUT
+ auto output_info = connection.get_output_info_unchecked(-1);
+ output_info.get();
+
+ // XCB_RANDR_BAD_CRTC
+ // auto crtc_info = connection.get_crtc_info_unchecked(-1);
+ // crtc_info.get();
+
+ // auto output_property = connection.query_output_property_unchecked(-1, -1);
+ // output_property.get();
+
+ connection.change_output_property(-1, -1, -1, 0, 0, 0, nullptr);
+
+ // connection.map_window(-1);
+
+ // auto & damage_proto = static_cast<xpp::damage::protocol<const x::connection &> &>(connection);
+ // damage_proto.create(-1, -1, 0);
+ // damage_proto.destroy(-1);
+ // damage_proto.subtract(-1, 0, 0);
+
+ connection.query_pict_index_values_unchecked(-1);
+ connection.change_picture(-1, 0, nullptr);
+
+// clang_complete does not like this
+// causes vim to segfault
+// #if not defined __clang__
+
+ while (! g_quit) {
+ connection.flush();
+ try {
+ registry.dispatch(connection.wait_for_event());
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "Caught std::shared_ptr<xcb_generic_error_t> in event loop "
+ << "(code: " << (int)error->error_code << ")" << std::endl;
+ } catch (const std::exception & error) {
+ std::cerr << "Caught std::exception in event loop: " << error.what() << std::endl;
+ } catch (...) {
+ std::cerr << "Something really bad has happened" << std::endl;
+ }
+ }
+
+// #endif
+
+ // xpp::x::event::key_press<x::connection &>
+ // xek_1(connection, std::shared_ptr<xcb_generic_event_t>((xcb_generic_event_t *)new xcb_key_press_event_t));
+ // xpp::x::event::key_press<xcb_connection_t *>
+ // xek_2(connection, std::shared_ptr<xcb_generic_event_t>(((xcb_generic_event_t *)new xcb_key_press_event_t)));
+ // xpp::x::event::key_press<xcb_connection_t *>
+ // xek_3(xcb_c, std::shared_ptr<xcb_generic_event_t>(((xcb_generic_event_t *)new xcb_key_press_event_t)));
+
+ // xpp::window<x::connection> w_1(connection);
+ // xpp::window<xcb_connection_t *> w_2(xcb_c);
+ // static_cast<xcb_window_t &>(w_1) = 12;
+
+ // if (xcb_connection_has_error(c)) {
+ // std::cerr << "Connection has error" << std::endl;
+ // return EXIT_FAILURE;
+ // }
+
+ // xpp::x::error::dispatcher ed(connection);
+
+ // xcb_void_cookie_t mwc = xcb_map_window_checked(c, -1);
+ // xcb_flush(c);
+ // xcb_generic_error_t * error = xcb_request_check(c, mwc);
+ // xcb_flush(c);
+ // if (error) {
+ // std::free(error);
+ // std::cerr << "mwc error" << std::endl;
+ // }
+
+ /*
+ std::shared_ptr<xcb_query_tree_reply_t> qtr;
+
+ typedef xpp::fixed::detail::simple<
+ x::connection &, xcb_window_t,
+ SIGNATURE(xcb_query_tree_children),
+ SIGNATURE(xcb_query_tree_children_length)>
+ simple_query_tree_iterator;
+ simple_query_tree_iterator(connection, qtr, 0);
+
+ typedef xpp::fixed::detail::object<
+ x::connection &, x::window,
+ SIGNATURE(xcb_query_tree_children),
+ SIGNATURE(xcb_query_tree_children_length)>
+ object_query_tree_iterator;
+ object_query_tree_iterator(connection, qtr, 0);
+ */
+
+ // typedef xpp::iterator<
+ // x::connection, x::window,
+ // SIGNATURE(xcb_query_tree_children),
+ // SIGNATURE(xcb_query_tree_children_length)>
+ // query_tree_iter;
+
+ // auto qt_iter = query_tree_iter(connection, qtr, 0);
+
+ // typedef xpp::generic::list<x::connection,
+ // xcb_query_tree_reply_t,
+ // query_tree_iter>
+ // query_tree_list;
+
+ // auto qt_list = query_tree_list(connection, qtr);
+ // auto qt_list_begin = qt_list.begin();
+
+ // std::shared_ptr<xcb_query_tree_reply_t> qtr;
+ // auto simple_iter = xpp::fixed::iterator::simple<
+ // x::connection, xcb_window_t, xcb_window_t, xcb_query_tree_reply_t,
+ // xcb_query_tree_children,
+ // xcb_query_tree_children_length>(connection, qtr, 0);
+
+ // auto object_iter = xpp::fixed::iterator::object<
+ // x::connection, xcb_window_t, x::window, xcb_query_tree_reply_t,
+ // xcb_query_tree_children,
+ // xcb_query_tree_children_length>(connection, qtr, 0);
+
+ // std::shared_ptr<xcb_get_font_path_reply_t> fpr;
+ // typedef xpp::iterator<x::connection,
+ // xcb_str_t,
+ // xcb_str_t,
+ // SIGNATURE(xcb_str_next),
+ // SIGNATURE(xcb_str_sizeof),
+ // SIGNATURE(xcb_get_font_path_path_iterator)>
+ // font_path_iter;
+
+ std::cerr << "fonts:" << std::endl;
+ auto fonts = connection.list_fonts(8, 1, "*");
+ for (auto && name : fonts.names()) {
+ std::cerr << "font [" << name.length() << "]: " << name << std::endl;
+ }
+
+ std::cerr << "paths:" << std::endl;
+ // auto font_paths = connection.get_font_path();
+ for (auto && path : connection.get_font_path().path()) {
+ std::cerr << "path [" << path.length() << "]: " << path << std::endl;
+ }
+
+ auto tree = connection.root<x::window>().query_tree();
+ std::cerr << "children: ";
+ for (auto && child : tree.children<x::xcb_window>()) {
+ std::cerr << child << ", ";
+ auto siblings = child.query_tree();
+ std::cerr << "(siblings: ";
+ for (auto && sibling : siblings.children()) {
+ std::cerr << sibling << ", ";
+ }
+ std::cerr << "), " << std::endl;
+ }
+ std::cerr << std::endl;
+
+ // auto tree_2 = connection.query_tree(screen->root);
+ // std::cerr << "children: ";
+ // for (auto & w : tree_2.children<x::window>()) {
+ // std::cerr << w << ", ";
+ // }
+ // std::cerr << std::endl;
+
+ // xcb_window_t window = 27263111;
+ xcb_window_t window = 0;
+
+ // test::map_window(c, window);
+ // test::map_window(c, window, 0);
+ xpp::x::map_window(connection, window);
+ connection.map_window(window);
+
+ try {
+ // test::map_window_checked(c, window);
+ // xpp::x::map_window_checked(c, window);
+ connection.map_window_checked(window);
+ } catch (const std::exception & e) {
+ std::cerr << "CATCH: map_window exception:" << std::endl
+ << "\twhat(): " << e.what() << std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "CATCH: map_window error: " << (int)error->error_code << std::endl;
+ }
+
+ // auto attrs_0 = test::get_window_attributes(c, window);
+ // auto attrs_1 = test::get_window_attributes(connection, window);
+ // auto attrs_2 = test::get_window_attributes_unchecked(connection, window);
+
+ xpp::x::reply::checked::get_window_attributes<xcb_connection_t *>
+ gwar(connection, window);
+
+ auto attrs_0 = connection.get_window_attributes(window);
+ // auto attrs_0 = xpp::x::get_window_attributes(c, window);
+ auto attrs_1 = xpp::x::get_window_attributes(connection, window);
+ auto attrs_2 = xpp::x::get_window_attributes_unchecked(connection, window);
+
+ try {
+ attrs_0.get();
+ } catch (const std::exception & e) {
+ std::cerr << "CATCH: attrs_0 exception:" << std::endl
+ << "\twhat(): " << e.what() << std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "CATCH: attrs_0 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+ }
+
+ if (! attrs_0) {
+ std::cerr << "attrs_0 invalid" << std::endl;
+ } else {
+ std::cerr << "attrs_0 valid" << std::endl;
+ }
+
+ try {
+ attrs_1.get();
+ } catch (const std::exception & e) {
+ std::cerr << "CATCH: attrs_1 exception:" << std::endl
+ << "\twhat(): " << e.what() << std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "CATCH: attrs_1 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+ }
+
+ if (! attrs_1) {
+ std::cerr << "attrs_1 invalid" << std::endl;
+ } else {
+ std::cerr << "attrs_1 valid" << std::endl;
+ }
+
+ try {
+ attrs_2.get();
+ } catch (const std::exception & e) {
+ std::cerr << "CATCH: attrs_2 exception:" << std::endl
+ << "\twhat(): " << e.what() << std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "CATCH: attrs_2 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+ }
+
+ if (! attrs_2) {
+ std::cerr << "attrs_2 invalid" << std::endl;
+ } else {
+ std::cerr << "attrs_2 valid" << std::endl;
+ }
+
+ try {
+ auto my_string_atom = xpp::x::intern_atom(connection, false, "MY_STRING");
+ std::string my_string("SUPER COOL");
+
+ std::cerr << "atom \"MY_STRING\": " << my_string_atom.atom() << std::endl;;
+
+ auto atom_name = connection.get_atom_name(my_string_atom.atom());
+ std::cerr << "atom name: " << atom_name.name() << std::endl;;
+
+ // xpp::x::change_property_checked(c,
+ connection.change_property_checked(
+ XCB_PROP_MODE_REPLACE, connection.root(),
+ my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ my_string.begin(), my_string.end());
+
+ // xpp::x::change_property_checked(c,
+ connection.change_property_checked(
+ XCB_PROP_MODE_REPLACE, 0,
+ my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ my_string.length(), my_string.c_str());
+ } catch (const std::exception & e) {
+ std::cerr << "change property failed: " << e.what() << std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr << "generic error: " << (int)error->error_code << std::endl;
+ }
+
+ connection.flush();
+
+ // xcb_disconnect(c);
+
+
+ /*
+ std::cerr << std::endl << "intern_atom 1" << std::endl;
+ auto atom_reply_1 =
+ xpp::x::intern_atom_unchecked(c, false, 25, "_NET_CLIENT_LIST_STACKING");
+ atom_reply_1.get();
+ if (atom_reply_1) {
+ std::cerr<<"atom_reply_1 success"<<std::endl;
+ }
+
+ std::cerr << std::endl << "intern_atom 2" << std::endl;
+ auto atom_reply_2 = xpp::x::intern_atom(c, false, "_NET_CLIENT_LIST_STACKING");
+ atom_reply_2.get();
+ if (atom_reply_2) {
+ std::cerr<<"atom_reply_2 success"<<std::endl;
+ }
+
+ std::cerr << std::endl << "intern_atom 3" << std::endl;
+ auto atom_reply_3 = xpp::x::intern_atom(c, false, "_NET_CLIENT_LIST_STAC");
+ try {
+ atom_reply_3.get();
+ std::cerr<<"atom_reply_3 success"<<std::endl;
+ } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+ std::cerr<<"CATCH: atom_reply_3 success, error_code: "
+ << (int)error->error_code <<std::endl;
+ }
+
+ std::cerr << std::endl << "intern_atom 1 reply: ";
+ xcb_atom_t atom = atom_reply_1->atom;
+ std::cerr << (int)atom << std::endl;
+
+ std::cerr << std::endl << "intern_atom 2 reply: ";
+ auto dummy_atom = atom_reply_2.atom<xpp::dummy_atom>();
+ std::cerr << (int)dummy_atom.m_atom << std::endl;
+
+ auto my_string_atom = xpp::x::intern_atom(c, true, "MY_STRING");
+ std::string my_string("LADIDA");
+ xpp::x::change_property(
+ c, XCB_PROP_MODE_REPLACE, 0, my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ my_string.begin(), my_string.end());
+ */
+
+ /*
+ std::cerr << std::endl << "intern_atom 1" << std::endl;
+ auto atom_reply_1 = intern_atom(c, false, 25, "_NET_CLIENT_LIST_STACKING");
+
+ std::cerr << std::endl << "intern_atom 2" << std::endl;
+ auto atom_reply_2 = intern_atom(c, false, std::string("_NET_CLIENT_LIST_STACKING"));
+
+ std::cerr << std::endl << "intern_atom 3" << std::endl;
+ auto atom_reply_3 = intern_atom<xpp::connection,
+ my::dispatcher,
+ xpp::x::error::dispatcher>
+ (c, false, "FOO BAZ");
+ atom_reply_3.get();
+
+ std::cerr << std::endl << "intern_atom 1 reply: ";
+ xcb_atom_t atom = atom_reply_1->atom;
+ std::cerr << (int)atom << std::endl;
+
+ std::cerr << std::endl << "intern_atom 2 reply: ";
+ auto dummy_atom = atom_reply_2.atom<xpp::dummy_atom>();
+ std::cerr << (int)dummy_atom.m_atom << std::endl;
+
+ std::cerr << std::endl << "map_window 1" << std::endl;
+ map_window(connection, 0);
+ std::cerr << std::endl << "map_window 2" << std::endl;
+ map_window<xpp::connection>(c, 0);
+ std::cerr << std::endl << "map_window 3" << std::endl;
+ map_window<xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(connection, 0);
+
+ auto my_string_atom =
+ intern_atom<xpp::connection, my::dispatcher>(connection, true, "MY_STRING");
+ std::string my_string("LADIDA");
+ change_property<xpp::connection, my::dispatcher>(
+ c, XCB_PROP_MODE_REPLACE, 0, my_string_atom.atom(), XCB_ATOM_STRING, 8,
+ my_string.begin(), my_string.end());
+ */
+
+ /*
+ std::cerr << std::endl << "auto wa_1" << std::endl;
+ auto wa_1 = get_window_attributes(c, 0);
+ std::cerr << std::endl << "auto wa_2" << std::endl;
+ auto wa_2 = get_window_attributes<xpp::connection,
+ my::dispatcher,
+ xpp::x::error::dispatcher>
+ (connection, 0);
+ std::cerr << std::endl << "auto wa_3" << std::endl;
+ auto wa_3 = get_window_attributes<xpp::connection,
+ my::dispatcher,
+ xpp::x::error::dispatcher>
+ (connection, 0);
+
+ std::cerr << std::endl << "wa_1" << std::endl;
+ auto r1 = wa_1.get();
+ std::cerr << std::endl << "wa_2" << std::endl;
+ auto r2 = wa_2.get();
+ std::cerr << std::endl << "wa_3" << std::endl;
+ auto r3 = wa_3.get();
+ */
+
+ // try {
+ // std::cerr << "YOLOLOLOL" << std::endl;
+ // xcb_colormap_t cm = wa_3.colormap();
+ // cm = wa_1->colormap;
+ // cm = r1->colormap;
+ // } catch (...) {}
+
+ // map_window<xpp::connection>(c, 0);
+ // map_window<xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(c, 0);
+
+ // auto wa_cookie = get_window_attributes_cookie(c, 0);
+ // auto wa_reply_1 = get_window_attributes_reply<xpp::connection>(connection, wa_cookie);
+ // auto wa_reply_2 = get_window_attributes_reply<
+ // xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(
+ // connection, get_window_attributes_cookie()(c, 0));
+
+ // wa_reply_1.get();
+ // wa_reply_2.get();
+
+ // auto wa = get_window_attributes(c, 0);
+ // auto wa_error = get_window_attributes(ed, c, 0);
+ // wa_error.get();
+
+ // map_window_cookie<xpp::connection>()(connection, 0);
+ // map_window_cookie<xpp::connection>()(ed, connection, 0);
+ // map_window_cookie<xpp::connection>()(connection, connection, 0);
+ // map_window_cookie<xpp::connection>()(xpp::x::error::dispatcher(), connection, 0);
+
+ // get_window_attributes_cookie wac_1(c, 0);
+ // get_window_attributes_cookie wac_2(c, 0);
+
+ // get_window_attributes_reply<> war_1(connection, wac_1);
+ // get_window_attributes_reply<> war_2(connection, wac_2);
+
+ // get_window_attributes_reply<my::dispatcher, xpp::x::error::dispatcher>
+ // war_3(connection, get_window_attributes_cookie()(c, 0), my::dispatcher(), xpp::x::error::dispatcher());
+
+ // war_1.get();
+ // war_2.get();
+ // war_3.get();
+
+
+ // map_window_cookie<xpp::connection> mwc_unchecked;
+ // mwc_checked(, connection, 0);
+ // mwc_checked(xpp::x::error::dispatcher(), connection, 0);
+ // mwc_unchecked(connection, 0);
+ // map_window_cookie<checked, xpp::connection> mwc_checked;
+ // map_window_cookie<unchecked, xpp::connection> mwc_unchecked;
+
+ // map_window(c, 0);
+ // map_window<xpp::generic::checked>(c, 0);
+ // auto reply = get_window_attributes(connection, 0);
+ // reply.get();
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include <iostream>
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h> // XC_cross
+
+namespace x {
+ typedef xpp::connection<
+ // xpp::randr::extension,
+ // xpp::damage::extension,
+ // xpp::render::extension
+ >
+ connection;
+
+ typedef xpp::event::registry<connection &
+ // xpp::randr::extension,
+ // xpp::damage::extension,
+ // xpp::render::extension>
+ >
+ registry;
+
+ typedef xpp::font<connection &> font;
+ typedef xpp::cursor<connection &> cursor;
+ typedef xpp::window<connection &> window;
+ typedef xpp::window<xcb_connection_t *> xcb_window;
+
+ typedef xpp::x::event::key_press<connection &> key_press;
+ typedef xpp::x::event::key_release<connection &> key_release;
+ typedef xpp::x::event::button_press<connection &> button_press;
+ typedef xpp::randr::event::notify<connection &> randr_notify;
+ typedef xpp::randr::event::screen_change_notify<connection &> randr_screen_change_notify;
+ typedef xpp::damage::event::notify<connection &> damage_notify;
+};
+
+int main(int argc, char ** argv)
+{
+ x::connection connection;
+ xcb_font_t font = 0;
+ auto cursor_1 = x::cursor(connection, 0);
+ auto cursor_2 = x::cursor::create_glyph(connection, font, font,
+ XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+ return 0;
+}
--- /dev/null
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <algorithm>
+
+namespace test1 {
+
+template<typename T>
+struct interface {
+ static std::size_t size_of(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return sizeof(T);
+ }
+};
+
+template<>
+struct interface<void> {
+ static std::size_t size_of(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return sizeof(char);
+ }
+};
+
+template<typename T>
+struct A : public interface<T> {
+ A(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+ << this->size_of() << std::endl;
+ }
+
+ static std::size_t size_of(void)
+ {
+ std::cerr << "YOLOLOL" << std::endl;
+ return 0;
+ }
+};
+
+// template<typename T>
+struct B : public interface<int> {
+ B(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+ << this->size_of() << std::endl;
+ }
+};
+
+// template<typename T>
+struct C : public interface<double> {
+ C(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+ << this->size_of() << std::endl;
+ }
+};
+
+int main(int argc, char ** argv)
+{
+ test1::A<int> t1_1;
+ std::cerr << std::endl;
+ test1::A<double> t1_2;
+ std::cerr << std::endl;
+ test1::A<char> t1_3;
+ std::cerr << std::endl;
+ test1::A<uint16_t> t1_4;
+ std::cerr << std::endl;
+ test1::A<void> t1_5;
+ std::cerr << std::endl;
+
+ test1::B t2_1;
+ std::cerr << std::endl;
+
+ test1::C t3_1;
+ std::cerr << std::endl;
+
+ return 0;
+}
+
+};
+
+namespace test2 {
+
+struct interface {
+ static void test(void);
+};
+
+struct A : public interface {
+ static void test(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " (A::test)" << std::endl;
+ }
+ A(void)
+ {
+ test();
+ }
+};
+
+struct B : public interface {
+ static void test(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " (B::test)" << std::endl;
+ }
+ B(void)
+ {
+ test();
+ }
+};
+
+void test(const interface & i)
+{
+ A::test();
+ B::test();
+ // decltype(i)::test();
+}
+
+int main(int argc, char ** argv)
+{
+ test2::A a;
+ test2::B b;
+ test2::test(a);
+ // test2::test();
+
+ return 0;
+}
+
+};
+
+namespace test3 {
+
+template<typename T>
+struct Base {
+ T t;
+};
+
+template<int N>
+struct select_type;
+
+template<>
+struct select_type<1> {
+ Base<int> base;
+};
+
+template<>
+struct select_type<2> {
+ Base<double> base;
+};
+
+int main(int argc, char ** argv)
+{
+ constexpr int i = 0;
+ test3::select_type<1> st_1;
+ test3::select_type<2> st_2;
+
+ return 0;
+}
+
+};
+
+namespace test4 {
+
+template<typename T>
+struct i {
+ virtual bool f(T) = 0;
+};
+
+namespace a {
+ struct a : public i<int> {
+ bool f(int i)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return i == m_i;
+ }
+ int m_i = 0;
+ };
+};
+
+namespace b {
+ struct b : public i<int> {
+ bool f(int i)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return i == m_i;
+ }
+ int m_i = 1;
+ };
+};
+
+namespace c {
+ struct c : public i<int> {
+ bool f(int i)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return i == m_i;
+ }
+ int m_i = 2;
+ };
+};
+
+template<typename ... IS>
+struct z : public IS ... {
+
+ // template<typename ... VS>
+ void
+ run(int i)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " with i = " << i << std::endl;
+ f(i, static_cast<IS *>(this) ...);
+ }
+
+ template<typename I, typename ... ISS>
+ void
+ f(int v, I * i, ISS ... is)
+ {
+ if (! i->f(v)) {
+ f(v, is ...);
+ } else {
+ std::cerr << "We've got a winner!" << std::endl;
+ }
+ }
+
+ template<typename I>
+ void
+ f(int v, I * i)
+ {
+ i->f(v);
+ }
+
+};
+
+int main(int argc, char ** argv)
+{
+ test4::z<test4::a::a, test4::b::b, test4::c::c> z;
+ for (auto i : { 0, 1, 2, 3 }) {
+ z.run(i);
+ }
+ return 0;
+}
+
+};
+
+namespace test5 {
+
+struct pod_generic {
+ int id;
+ int m_int;
+};
+
+struct pod_int {
+ int id = 0;
+ int m_int;
+};
+
+struct pod_double {
+ int id = 1;
+ double m_double;
+};
+
+struct pod_string {
+ int id = 2;
+ std::string m_string;
+};
+
+template<int OpCode>
+struct my_pod_wrapper {
+ static const int opcode = OpCode;
+};
+
+struct my_pod_int_wrapper : public my_pod_wrapper<0> {
+ my_pod_int_wrapper(pod_generic * pg)
+ : m_pi((pod_int *)pg) {}
+ pod_int * m_pi;
+};
+
+struct my_pod_double_wrapper : public my_pod_wrapper<1> {
+ my_pod_double_wrapper(pod_generic * pg)
+ : m_pd((pod_double *)pg) {}
+ pod_double * m_pd;
+};
+
+struct my_pod_string_wrapper : public my_pod_wrapper<2> {
+ my_pod_string_wrapper(pod_generic * pg)
+ : m_ps((pod_string *)pg) {}
+ pod_string * m_ps;
+};
+
+class dispatcher {
+ public:
+ virtual ~dispatcher(void) {}
+ template<typename E> void dispatch(const E & e);
+};
+
+template<typename ... Events>
+class sink;
+
+template<typename E>
+class sink<E> : virtual public dispatcher {
+ public:
+ virtual void handle(const E & e) = 0;
+};
+
+template<typename Event, typename ... Events>
+class sink<Event, Events ...>
+ : virtual public sink<Event>
+ , virtual public sink<Events> ...
+{};
+
+template<typename E>
+void dispatcher::dispatch(const E & e)
+{
+ dynamic_cast<sink<E> *>(this)->handle(e);
+}
+
+template<int ExtensionId>
+class pod_dispatcher {
+ public:
+ template<typename Dispatcher>
+ bool
+ operator()(pod_generic * pg, const Dispatcher & D) const
+ {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ std::cerr << "POD_DISPATCHER<" << ExtensionId << ">" << std::endl;
+ switch (pg->id) {
+ case 0:
+ // std::cerr << "dispatch with my_pod_int_wrapper" << std::endl;
+ D(my_pod_int_wrapper(pg));
+ return true;
+
+ case 1:
+ // std::cerr << "dispatch with my_pod_double_wrapper" << std::endl;
+ D(my_pod_double_wrapper(pg));
+ return true;
+
+ case 2:
+ // std::cerr << "dispatch with my_pod_string_wrapper" << std::endl;
+ D(my_pod_string_wrapper(pg));
+ return true;
+ };
+
+ return false;
+ }
+};
+
+template<>
+class pod_dispatcher<1> {
+ public:
+ template<typename Dispatcher>
+ bool
+ operator()(pod_generic * pg, const Dispatcher & D) const
+ {
+ std::cerr << "POD_DISPATCHER<1>" << std::endl;
+ switch (pg->id) {
+ case 0:
+ // std::cerr << "dispatch with my_pod_int_wrapper" << std::endl;
+ D(my_pod_int_wrapper(pg));
+ return true;
+
+ case 1:
+ // std::cerr << "dispatch with my_pod_double_wrapper" << std::endl;
+ D(my_pod_double_wrapper(pg));
+ return true;
+
+ case 2:
+ // std::cerr << "dispatch with my_pod_string_wrapper" << std::endl;
+ D(my_pod_string_wrapper(pg));
+ return true;
+ };
+
+ return false;
+ }
+};
+
+class a : public sink<my_pod_int_wrapper,
+ my_pod_double_wrapper,
+ my_pod_string_wrapper>
+{
+ public:
+ virtual void handle(const my_pod_int_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ virtual void handle(const my_pod_string_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class b : public sink<my_pod_string_wrapper,
+ my_pod_double_wrapper>
+{
+ public:
+ virtual void handle(const my_pod_string_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class c : public sink<my_pod_double_wrapper>
+{
+ public:
+ virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+template<typename ... POD_Dispatcher>
+struct handler_registry {
+
+ template<typename D>
+ bool
+ dispatch(pod_generic * pg) const
+ {
+ std::cerr << "RecursiveDispatchEnd_EvenBetter" << std::endl;
+ return D()(pg, *this);
+ }
+
+ template<typename D1, typename D2, typename ... Dispatcher>
+ bool
+ dispatch(pod_generic * pg) const
+ {
+ std::cerr << "BigFatDispatch_ManFeelsGood" << std::endl;
+ D1()(pg, *this);
+ return dispatch<D2, Dispatcher ...>(pg);
+ }
+
+ bool
+ dispatch(pod_generic * pg) const
+ {
+ std::cerr << "InitialDispatch_JustAbitWeiry" << std::endl;
+ return dispatch<POD_Dispatcher ...>(pg);
+ }
+
+ template<typename Event>
+ void
+ operator()(const Event & e) const
+ {
+ try {
+ for (auto & item : m_dispatcher.at(Event::opcode)) {
+ item.second->dispatch(e);
+ }
+ } catch (...) {}
+ }
+
+ template<typename Event1, typename Event2, typename ... Events>
+ void
+ attach(sink<Event1, Event2, Events ...> * s)
+ {
+ attach(Event1::opcode, s);
+ attach((sink<Event2, Events ...> *)s);
+ }
+
+ template<typename Event>
+ void
+ attach(sink<Event> * s)
+ {
+ attach(Event::opcode, s);
+ }
+
+ void attach(unsigned int opcode, dispatcher * d)
+ {
+ m_dispatcher[opcode].emplace(0, d);
+ }
+
+ std::unordered_map<unsigned int,
+ std::multimap<unsigned int, dispatcher *>> m_dispatcher;
+};
+
+template<typename I>
+void foo(int i, I j) {}
+
+int main(int argc, char ** argv)
+{
+ int i = 42;
+ std::string s = "42";
+
+ pod_int pi;
+ pod_double pd;
+ pod_string ps;
+
+ test5::a a;
+ test5::b b;
+ test5::c c;
+
+ handler_registry<pod_dispatcher<0>,
+ pod_dispatcher<1>,
+ pod_dispatcher<2>> registry;
+
+ registry.attach(&a);
+ registry.attach(&b);
+ registry.attach(&c);
+
+ registry.dispatch((test5::pod_generic *)&pi);
+ registry.dispatch((test5::pod_generic *)&ps);
+ registry.dispatch((test5::pod_generic *)&pd);
+
+ return 0;
+}
+
+};
+
+namespace test6 {
+ template <typename... Types>
+ struct foo {};
+
+ template < typename... Types1, template <typename...> class T
+ , typename... Types2, template <typename...> class V
+ , typename U >
+ void
+ bar(const T<Types1...>&, const V<Types2...>&, const U& u)
+ {
+ std::cout << sizeof...(Types1) << std::endl;
+ std::cout << sizeof...(Types2) << std::endl;
+ std::cout << u << std::endl;
+ }
+
+ int main(int argc, char ** argv)
+ {
+ foo<char, int, float> f1;
+ foo<char, int> f2;
+ bar(f1, f2, 9);
+ return 0;
+ }
+};
+
+namespace test7 {
+
+static constexpr std::size_t * my_ext_1 = nullptr;
+static constexpr std::size_t * my_ext_2 = nullptr;
+static constexpr std::size_t * my_ext_3 = nullptr;
+static constexpr std::size_t * my_ext_4 = nullptr;
+
+// static const std::size_t id_4_0 = reinterpret_cast<std::size_t>(&my_ext_4 + 0);
+// static const std::size_t id_4_1 = reinterpret_cast<std::size_t>(&my_ext_4 + 1);
+// static const std::size_t id_4_2 = reinterpret_cast<std::size_t>(&my_ext_4 + 2);
+
+namespace p1 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_1> {
+ void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+namespace p2 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_2> {
+ void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+namespace p3 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_3> {
+ void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+struct proto_1 {
+ typedef p1::ext<my_ext_1> ext;
+ template<typename Handler, typename Event>
+ void dispatch(const Handler & h, const Event & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ e.call(static_cast<const typename Event::ext &>(h));
+ }
+};
+
+struct proto_2 {
+ typedef p2::ext<my_ext_2> ext;
+ template<typename Handler, typename Event>
+ void dispatch(const Handler & h, const Event & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ e.call(static_cast<const typename Event::ext &>(h));
+ }
+};
+
+struct proto_3 {
+ typedef p3::ext<my_ext_3> ext;
+ template<typename Handler, typename Event>
+ void dispatch(const Handler & h, const Event & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ e.call(static_cast<const typename Event::ext &>(h));
+ }
+};
+
+struct event_1 {
+ typedef p1::ext<my_ext_1> ext;
+ typedef proto_1 proto;
+ // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct event_2 {
+ typedef p2::ext<my_ext_2> ext;
+ typedef proto_2 proto;
+ // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct event_3 {
+ typedef p3::ext<my_ext_3> ext;
+ typedef proto_3 proto;
+ // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct pod_event_1 {
+ const int id = 1;
+};
+
+struct pod_event_2 {
+ const int id = 2;
+};
+
+struct pod_event_3 {
+ const int id = 3;
+};
+
+template<typename ... Protos>
+struct proto
+ : public Protos ...
+ , public Protos::ext ...
+{
+
+ template<typename Event>
+ void run(const Event & event)
+ {
+ static_cast<typename Event::proto *>(this)->dispatch(*this, event);
+ }
+
+};
+
+int main(int argc, char ** argv)
+{
+ std::cerr << "my_ext_1: " << my_ext_1 << std::endl;
+ std::cerr << "my_ext_2: " << my_ext_2 << std::endl;
+ std::cerr << "my_ext_3: " << my_ext_3 << std::endl;
+
+ proto<proto_1, proto_2, proto_3> p;
+
+ event_1 e_1;
+ event_2 e_2;
+ event_3 e_3;
+
+ p.run(e_1);
+ p.run(e_2);
+ p.run(e_3);
+
+ return 0;
+}
+
+}; // namespace test7
+
+namespace test8 {
+
+struct a {
+ void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b {
+ void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct c {
+ void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct wrap_hull
+{
+ virtual void do_cool_stuff(void) = 0;
+};
+
+template<typename ... Args>
+struct wrap
+ : public wrap_hull
+ , public Args ...
+{
+ void do_cool_stuff(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+template<typename ... Args>
+struct test : public Args ...
+{
+ void trigger(void)
+ {
+ run<Args ...>();
+ }
+
+ template<typename Arg, typename Next, typename ... Rest>
+ void run(void)
+ {
+ run<Arg>();
+ run<Next, Rest ...>();
+ }
+
+ template<typename Arg>
+ void run(void)
+ {
+ Arg()();
+ }
+
+ template<typename Arg, typename Next, typename ... Rest>
+ void
+ insert(wrap<Arg, Next, Rest ...> * w)
+ {
+ insert<Arg, Next, Rest ...>(
+ reinterpret_cast<wrap<Arg> *>(w),
+ reinterpret_cast<wrap<Next, Rest> *>(w) ...);
+ }
+
+ template<typename Arg, typename Next, typename ... Rest>
+ void
+ insert(wrap<Arg> * w, wrap<Next, Rest ...> * ws ...)
+ {
+ insert<Arg>(w);
+ insert<Next, Rest ...>(
+ reinterpret_cast<wrap<Next> *>(w),
+ reinterpret_cast<wrap<Rest> *>(w) ...);
+ }
+
+ template<typename Arg>
+ void
+ insert(wrap_hull * wh)
+ {
+ m_wraps.push_back(wh);
+ }
+
+ void call_objects(void)
+ {
+ for (auto * wh : m_wraps) {
+ wh->do_cool_stuff();
+ }
+ }
+
+ std::vector<wrap_hull *> m_wraps;
+};
+
+int main(int argc, char ** argv)
+{
+ struct test<a, b, c> t;
+ t.trigger();
+
+ struct wrap<a, b, c> w;
+ t.insert(&w);
+
+ t.call_objects();
+
+ return 0;
+}
+
+}; // namespace test8
+
+int main(int argc, char ** argv)
+{
+ // return test1::main(argc, argv);
+ // return test2::main(argc, argv);
+ // return test3::main(argc, argv);
+ // return test4::main(argc, argv);
+ // return test5::main(argc, argv);
+ // return test6::main(argc, argv);
+ // return test7::main(argc, argv);
+ return test8::main(argc, argv);
+}
--- /dev/null
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+
+#define CALLABLE(FUNCTION) callable<decltype(FUNCTION), FUNCTION>
+
+template<typename Signature, Signature & S>
+struct callable;
+
+template<typename Return,
+ typename ... Args, Return (&Function)(Args ...)>
+struct callable<Return(Args ...), Function> {
+ Return operator()(Args ... args)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ return Function(args ...);
+ }
+};
+
+template<typename ... Arguments>
+class one_size_fits_them_all;
+
+// A generic template
+template<typename T, typename U, typename V,
+ typename F1, typename F2, typename F3>
+class one_size_fits_them_all<T, U, V, F1, F2, F3>
+{
+ public:
+ one_size_fits_them_all(void)
+ {
+ std::cerr << "generic one_size_fits_them_all" << std::endl
+ << __PRETTY_FUNCTION__ << std::endl << std::endl;
+ F1()();
+ F2()();
+ F3()();
+ std::cerr << std::endl;
+ }
+};
+
+// A specialized template
+template<typename T, typename Callable>
+class one_size_fits_them_all<T, int, int, void, void, Callable>
+{
+ public:
+ one_size_fits_them_all(void)
+ {
+ std::cerr << "specialized one_size_fits_them_all" << std::endl
+ << __PRETTY_FUNCTION__ << std::endl << std::endl;
+ Callable()();
+ std::cerr << std::endl;
+ }
+};
+
+void f1(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f2(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f3(void)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+template<typename T>
+struct interface {
+ static std::size_t size_of(void) { return sizeof(T); }
+};
+
+template<typename T>
+struct test : public interface<T> {
+ test(void)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << " size_of(): " << this->size_of() << std::endl;
+ }
+};
+
+int main(int argc, char ** argv)
+{
+ // generic template
+ auto generic = one_size_fits_them_all<
+ int, int, int, CALLABLE(f1), CALLABLE(f2), CALLABLE(f3)>();
+
+ // specialized template
+ auto specialized_int = one_size_fits_them_all<
+ int, int, int, void, void, CALLABLE(f1)>();
+
+ // specialized template
+ auto specialized_double = one_size_fits_them_all<
+ double, int, int, void, void, CALLABLE(f3)>();
+
+ test<int> t1;
+ test<double> t2;
+ test<char> t3;
+ test<uint16_t> t4;
+
+ return 0;
+}
--- /dev/null
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+#include <chrono>
+
+#include <list>
+
+template<typename T>
+struct is_callable {
+private:
+ typedef char(&yes)[1];
+ typedef char(&no)[2];
+
+ struct Dummy {};
+ struct Fallback { void operator()(); };
+ // struct Derived : T, Fallback { };
+ struct Derived : std::conditional<! std::is_fundamental<T>::value,
+ T,
+ Dummy>::type,
+ Fallback { };
+
+ template<typename U, U> struct Check;
+
+ template<typename>
+ static yes test(...);
+
+ template<typename C>
+ static no test(Check<void (Fallback::*)(), &C::operator()>*);
+
+public:
+ static const bool value = sizeof(test<Derived>(0)) == sizeof(yes);
+};
+
+
+// #include <X11/Xlib.h>
+// #include <X11/keysymdef.h>
+// #include <X11/extensions/Xrandr.h>
+// #include <X11/cursorfont.h> // XC_cross
+
+// #include <xcb/xcbext.h>
+
+// #include "../event.hpp"
+// #include "../core/value_iterator.hpp"
+// #include "../core/connection.hpp"
+
+// template<typename Iterator>
+// void
+// test(Iterator begin, Iterator end)
+// {
+// std::cerr << "before initializer:";
+// for (auto it = begin; it != end; ++it) {
+// std::cerr << " " << *it;
+// }
+// std::cerr << std::endl;
+//
+// // auto vector = { begin, end };
+// // std::vector<typename value_trait<Iterator>::value_type> vector(begin, end);
+//
+// std::vector<typename value_type<Iterator,
+// ! std::is_pointer<Iterator>::value
+// >::type>
+// vector(begin, end);
+//
+// // std::initializer_list<Iterator> vector = { begin, end };
+//
+// std::cerr << "after initializer (size: " << vector.size() << "):";
+// for (auto & v : vector) {
+// std::cerr << " " << v;
+// }
+// std::cerr << std::endl;
+// }
+
+struct foo {
+ static void create(int * c, unsigned int xid)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ static void destroy(int * c, unsigned int xid)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+};
+
+struct bar {
+ static void create(int * c, unsigned int xid, double d)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+ static void destroy(int * c, unsigned int xid)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+};
+
+template<typename Xid, typename ... Parameters>
+struct allocator {
+
+ template<void (*Allocate)(int *, Xid, Parameters ...)>
+ struct allocate
+ {
+ void
+ operator()(int * c, Xid xid, Parameters ... parameters)
+ {
+ Allocate(c, xid, parameters ...);
+ }
+ };
+
+ template<void (*Deallocate)(int *, Xid)>
+ struct deallocate
+ {
+ void
+ operator()(int * c, Xid xid)
+ {
+ Deallocate(c, xid);
+ }
+ };
+
+};
+
+template<typename Xid, typename Allocate = void, typename Deallocate = void>
+struct xid {
+
+ template<typename ... Parameters>
+ xid(Parameters ... parameters)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ m_xid = std::shared_ptr<Xid>(new Xid(0), // xcb_generate_id(c)),
+ [&](Xid * xid)
+ {
+ Deallocate()(NULL, *xid);
+ delete xid;
+ });
+ Allocate()(NULL, 0, parameters ...);
+ }
+
+ std::shared_ptr<Xid> m_xid;
+};
+
+template<typename Xid>
+struct xid<Xid, void, void>
+{
+ Xid xid;
+};
+
+template<typename Signature1, Signature1 & S1, typename Signature2, Signature2 & S2>
+struct wrapper;
+
+template<typename Return1,
+ typename ... Args1,
+ Return1(&F1)(Args1 ...),
+ typename Return2,
+ typename ... Args2,
+ Return2(&F2)(Args2 ...)>
+struct wrapper<Return1(Args1 ...), F1, Return2(Args2 ...), F2> {
+ static
+ void
+ allocate(Args1 ... args)
+ {
+ F1(args ...);
+ }
+ static
+ void
+ deallocate(Args2 ... args)
+ {
+ F2(args ...);
+ }
+};
+
+struct caller {
+ template<typename Callee>
+ void operator()(const Callee & callee, int i)
+ {
+ switch (i) {
+ case 0: callee(42);
+ break;
+ case 1: callee(2.73);
+ break;
+ case 2: callee(std::string("foo"));
+ break;
+ };
+ }
+};
+
+struct callee {
+ void operator()(int i) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ template<typename Arg>
+ void operator()(const Arg &) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+namespace mylib {
+
+namespace request {
+ enum { checked, unchecked };
+};
+
+namespace extension {
+
+using mylib::request::checked;
+using mylib::request::unchecked;
+
+namespace request {
+template<int RequestType = unchecked>
+struct void_request {
+ void operator()(void) const noexcept(RequestType == unchecked)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ };
+};
+};
+
+// template<>
+// struct void_request<checked> {
+// void operator()(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; };
+// };
+
+namespace request {
+template<int RequestType = checked>
+struct reply_request {
+ void operator()(void) const noexcept(RequestType == unchecked)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ };
+};
+};
+
+// template<>
+// struct reply_request<unchecked> {
+// void operator()(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; };
+// };
+
+struct interface {
+ template<int RequestType = unchecked>
+ void void_request(void) const { request::void_request<RequestType>()(); }
+ template<int RequestType = checked>
+ void reply_request(void) const { request::reply_request<RequestType>()(); }
+};
+
+}; // extension
+
+struct c : public extension::interface
+{};
+
+}; // mylib
+
+namespace test00 {
+
+enum { checked, unchecked };
+
+template<std::size_t check = unchecked>
+struct template_struct {
+ template<typename Connection, typename ... Parameter>
+ template_struct(Connection c, Parameter ... parameter) {}
+};
+
+template_struct<> ts(nullptr);
+
+template<std::size_t check = unchecked, typename Connection, typename ... Parameter>
+void
+template_function(Connection c, Parameter ... parameter)
+{
+ if (check == checked) {
+ std::cerr << "if (check) { // check == checked" << std::endl;
+ } else {
+ std::cerr << "} else { // check == unchecked" << std::endl;
+ }
+}
+
+struct an_interface {
+ template<typename std::size_t Check = unchecked, typename ... Parameter>
+ void
+ template_function(Parameter ... parameter)
+ {
+ test00::template_function<Check>(m_c, parameter ...);
+ }
+ double m_c;
+};
+
+void test(void)
+{
+// two t;
+// // t.do_it_void();
+// // t.do_it_void().unchecked();
+// auto reply_1 = t.do_it_reply();
+// auto reply_2 = t.do_it_reply().unchecked();
+// // reply.checked();
+// // auto reply = t.do_it_reply().checked();
+
+ // template_function(t);
+ // template_function(t, reply_1, reply_2);
+ // template_function<checked>(reply_1, reply_2);
+ // template_function<checked>(reply_2);
+
+ an_interface i;
+ i.template_function(0);
+ i.template_function<checked>(0);
+}
+
+};
+
+namespace test01 {
+
+struct a {
+ a(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ a(const std::string & string) : m_string(string) {}
+ a(const a & other) : m_string(other.m_string) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ a(a && other) : m_string(std::move(other.m_string)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ ~a(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ std::string m_string = "struct a";
+};
+
+struct b {
+ // b(const a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ // template<typename A>
+ // b(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ // b(a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ // b(a && a) : m_a(std::move(a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ b(a && a) : m_a(std::move(a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ b(const a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+ // b(const b & other) : m_a(other.m_a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ b(b && other) : m_a(std::move(other.m_a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+ ~b(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+ // const a & m_a;
+ a m_a;
+};
+
+b
+// foo(const a & a)
+foo(const a & a)
+{
+ // return b(std::move(a));
+ return b(a);
+}
+
+void test(void)
+{
+ // b bb = foo(a());
+ // b bb = foo(std::move(a()));
+ a aa("a string on the stack");
+ // b b1 = foo(aa);
+ std::cerr << "(before move) aa.m_string: " << aa.m_string << std::endl;
+ b b2 = foo(std::move(aa));
+ std::cerr << "(after move) aa.m_string: " << aa.m_string << std::endl;
+ // std::cerr << "b1.m_a.m_string: " << b1.m_a.m_string << std::endl;
+ std::cerr << "b2.m_a.m_string: " << b2.m_a.m_string << std::endl;
+}
+
+};
+
+namespace test02 {
+
+struct a {
+ static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b : a {
+ static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct c : b {
+ static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+// struct d : a, b {
+// static void foo(void)
+// {
+// std::cerr << __PRETTY_FUNCTION__ << std::endl;
+// a::foo();
+// b::foo();
+// }
+// };
+
+void test(void)
+{
+ a::foo();
+ b::foo();
+ c::foo();
+ // d::foo();
+}
+
+};
+
+namespace test03 {
+
+struct connection {};
+
+struct baz {
+ int member;
+};
+
+struct fro {
+ // fro(const int & i)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << ": " << i << std::endl;
+ // }
+
+ fro(const int & i, connection *)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << ": " << i << std::endl;
+ }
+};
+
+struct foo {
+ foo(const int & i, double d)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << ": " << i << "; d: " << d << std::endl;
+ }
+
+ // foo(int i, connection *, double d)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << ": " << i << "; d: " << d << std::endl;
+ // }
+};
+
+namespace reply_member {
+
+template<typename ReturnType>
+class get_with_object {
+ public:
+ template<typename Arg, typename ... Parameter>
+ ReturnType
+ operator()(Arg & a, connection *, Parameter ... parameter)
+ {
+ return ReturnType { a, parameter ... };
+ }
+};
+
+template<typename ReturnType>
+class get_with_object_and_connection {
+ public:
+ template<typename Arg, typename ... Parameter>
+ ReturnType
+ operator()(const Arg & a, connection * i, Parameter ... parameter)
+ {
+ return ReturnType { a, i, parameter ... };
+ }
+};
+
+template<typename ReturnType>
+class get_fundamental {
+ public:
+ template<typename Arg>
+ ReturnType
+ operator()(const Arg & arg, connection *)
+ {
+ return ReturnType { arg };
+ }
+};
+
+template<typename MemberType, typename ReturnType, typename ... Parameter>
+class get
+ : public std::conditional<
+ std::is_constructible<ReturnType, MemberType>::value,
+ get_fundamental<ReturnType>,
+ typename std::conditional<
+ std::is_constructible<ReturnType,
+ MemberType,
+ connection *,
+ Parameter ...>::value,
+ get_with_object_and_connection<ReturnType>,
+ get_with_object<ReturnType>
+ >::type
+ >::type
+{};
+
+};
+
+struct c {
+ template<typename ReturnType = int, typename ... Parameter>
+ ReturnType
+ c_get(Parameter ... parameter)
+ {
+ using get = reply_member::get<decltype(b.member), ReturnType, Parameter ...>;
+ return get()(b.member, i, parameter ...);
+ }
+
+ baz b;
+ connection * i;
+};
+
+void
+test(void)
+{
+ c cc;
+ cc.b.member = 42;
+
+ auto m_int = cc.c_get();
+ std::cerr << "m: " << m_int << std::endl;
+
+ // auto m_fro_i = cc.c_get<fro>();
+ // auto m_foo_d = cc.c_get<foo>(3.14);
+}
+
+}; // test03
+
+namespace test04 {
+
+struct a {
+ a(const std::string & s) : m_s(s) {}
+ void operator()(const std::string & s) const
+ {
+ std::cerr << "a: " << s << "; m_s: " << m_s << std::endl;
+ }
+ std::string m_s;
+};
+
+struct b {
+ void operator()(const std::string & s) const
+ {
+ std::cerr << "b: " << s << std::endl;
+ }
+};
+
+struct c : a, b {
+ c(void) : a("c : a") {}
+ // void operator() (const std::string & s) { a::operator()(s); b::operator()(s); }
+};
+
+struct d {
+ void
+ operator()(const std::string & s) const
+ {
+ std::cerr << "d: " << s << std::endl;
+ }
+};
+
+template<typename Base, typename Arg>
+void
+check(const Base & base, const Arg & arg)
+{
+ base(arg);
+}
+
+template<typename Base, typename Arg, typename E>
+void
+check(const Base & base, const Arg & arg)
+{
+ static_cast<const E &>(base)(arg);
+}
+
+template<typename Base, typename Arg, typename E, typename Next, typename ... Rest>
+void
+check(const Base & base, const Arg & arg)
+{
+ check<Base, Arg, E>(base, arg);
+ check<Base, Arg, Next, Rest ...>(base, arg);
+}
+
+template<typename E, typename ... ES>
+struct foo_base {
+ foo_base(const E & e)
+ : m_e(e)
+ {}
+
+ E m_e;
+
+ void
+ check_error(const std::string & s)
+ {
+ check<E, std::string, ES ...>(m_e, s);
+ }
+};
+
+template<>
+struct foo_base<void> {
+ foo_base(void)
+ {}
+
+ void
+ check_error(const std::string & s)
+ {
+ std::cerr << "foo_base<void>" << std::endl;
+ }
+};
+
+template<typename E = void, typename ... ES>
+struct foo
+ : public foo_base<E, ES ...>
+{
+ typedef foo_base<E, ES ...> base;
+
+ using base::foo_base;
+
+ template<typename ... P2>
+ void
+ operator()(const std::string & s, P2 ... p2)
+ {
+ base::check_error(s);
+ }
+
+}; // struct foo;
+
+template<typename T>
+using decay = typename std::decay<T>::type;
+
+template<typename Condition, typename T = void>
+using enable_if =
+ typename std::enable_if<Condition::value, T>::type;
+
+template<typename Condition, typename T = void>
+using disable_if =
+ typename std::enable_if<! Condition::value, T>::type;
+
+// template<typename P, typename ... PS>
+// typename std::enable_if<is_callable<decay<P>>::value, void>::type
+// foo_function_dispatch(const std::string & s, P && p, PS ... ps)
+// {
+// std::cerr << __PRETTY_FUNCTION__ << std::endl;
+// foo_function(p, s, ps ...);
+// }
+
+// template<typename P, typename ... PS>
+// typename std::enable_if<! is_callable<decay<P>>::value, void>::type
+// foo_function_dispatch(const std::string & s, P && p, PS ... ps)
+// {
+// std::cerr << __PRETTY_FUNCTION__ << std::endl;
+// foo_function(s, p, ps ...);
+// }
+
+template<typename Parameter,
+ typename ... Parameters,
+ typename = disable_if<is_callable<decay<Parameter>>>>
+void
+foo_function(const std::string & s, Parameter && p, Parameters ... ps)
+{
+ foo<>()(s, ps ...);
+}
+
+template<typename ... ErrorHandlers,
+ typename ErrorHandler,
+ typename ... Parameters,
+ typename = enable_if<is_callable<decay<ErrorHandler>>>>
+void
+foo_function(const std::string & s, ErrorHandler && e, Parameters ... ps)
+{
+ (foo<ErrorHandler, ErrorHandlers ...>(e))(s, ps ...);
+}
+
+struct foo_iface {
+
+ foo_iface(void) {}
+ foo_iface(const std::string & s) : m_s(s) {}
+
+ template<typename ... ErrorHandlers, typename ... Parameters>
+ void
+ foo_method(Parameters ... ps)
+ {
+ foo_function<ErrorHandlers ...>(m_s, ps ...);
+ }
+
+ // template<typename P,
+ // typename ... PS,
+ // typename = disable_if<is_callable<decay<P>>>>
+ // void
+ // foo_method(P && p, PS ... ps)
+ // {
+ // foo_function(m_s, p, ps ...);
+ // }
+
+ // template<typename ... ErrorHandlers,
+ // typename E,
+ // typename ... PS,
+ // typename = enable_if<is_callable<decay<E>>>>
+ // void
+ // foo_method(E && e, PS ... ps)
+ // {
+ // foo_function<ErrorHandlers ...>(m_s, e, ps ...);
+ // }
+
+ std::string m_s = "foo_iface";
+};
+
+// template<typename ... ES, typename E, typename ... PS, typename P>
+// void
+// foo_with_error(const std::string & s, PS ... ps, const E & e)
+// {
+// (foo<E, ES ...>(e))(s, ps ...);
+// // foo<std::function<void(const std::string &)>> f([](const std::string & s) { std::cerr << "lambda: " << s << std::endl; });
+// // f(s, ps ...);
+// }
+
+struct e {
+ int i;
+};
+
+void
+test(void)
+{
+ // d dd;
+ // (foo<d>(dd))("aaa 42 aaa", 5,6,7);
+ // ((foo<c, a, b>(c())))("ccc 42 ccc", 5,6,7);
+
+ foo_function("hhh 42 hhh", a("abcde"), 5,6,7);
+ foo_function("jjj 42 jjj", 5,6,7);
+ foo_function("kkk 42 kkk", [](const std::string & s){ std::cerr << "lambda: " << s << std::endl; }, 5,6,7);
+
+ foo_iface fi;
+ fi.foo_method(5,6,7);
+ fi.foo_method(b(), 5,6,7);
+ c cc;
+ fi.foo_method<a,b>(cc, 5,6,7);
+
+ std::cerr << std::boolalpha << std::endl
+ // << "has_member_operator(): " << has_member_operator<b>::value << std::endl
+ << "is_callable: " << is_callable<b>::value << std::endl
+ << "is_trivial: " << std::is_trivial<b>::value << std::endl
+ << "is_pod: " << std::is_pod<b>::value << std::endl
+ << "is_standard_layout: " << std::is_standard_layout<b>::value << std::endl
+ << "is_fundamental: " << std::is_fundamental<b>::value << std::endl
+ << "is_class: " << std::is_class<b>::value << std::endl
+ << "is_object: " << std::is_object<b>::value << std::endl
+ << std::endl;
+
+ std::cerr << std::boolalpha << std::endl
+ << "is_callable: " << is_callable<e>::value << std::endl
+ << "is_trivial: " << std::is_trivial<e>::value << std::endl
+ << "is_pod: " << std::is_pod<e>::value << std::endl
+ << "is_standard_layout: " << std::is_standard_layout<e>::value << std::endl
+ << "is_fundamental: " << std::is_fundamental<e>::value << std::endl
+ << "is_class: " << std::is_class<e>::value << std::endl
+ << "is_object: " << std::is_object<e>::value << std::endl
+ << std::endl;
+
+ std::cerr << std::boolalpha << std::endl
+ << "is_callable: " << is_callable<int>::value << std::endl
+ << std::endl;
+
+ // foo<b>(b(1))("bbb 42 bbb", 5,6,7);
+ // (foo<b>(b()))("bbb 42 bbb", 5,6,7);
+ // foo<a, b>(c())("ccc 42 ccc", 5,6,7);
+ // foo<>()("ddd 42 ddd", 5,6,7);
+ // foo<a>()(a("a2"), "eee 42 eee", 5,6,7);
+ // (foo<a>(a("a2")))("eee 42 eee", 5,6,7);
+}
+
+}; // test04
+
+namespace test05 {
+
+template<typename T>
+struct a {
+ struct member {
+ struct get {
+ void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+ };
+ };
+};
+
+struct b : a<int> {
+ void
+ fun(void)
+ {
+ using get = member::get;
+ get()();
+ }
+};
+
+void
+test(void)
+{
+ b bb;
+ bb.fun();
+}
+
+}; // test05
+
+namespace test06 {
+
+void foo(int i)
+{
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+}
+
+auto &bar = foo;
+
+struct a {
+ virtual void foo(void) = 0;
+};
+
+struct b : a {
+ using a::foo;
+ void bar(void) { foo(); }
+ // decltype(a::foo) & bar = a::foo;
+};
+
+void test(void)
+{
+ foo(1);
+ bar(2);
+}
+
+}; // test05
+
+namespace test07 {
+
+class a {
+ static const char * foo ;
+ static const std::string bar ;
+ static constexpr const char * baz = "baz";
+};
+const char * a::foo = "foo";
+const std::string a::bar = "bar";
+
+void
+test(void)
+{
+ // a aa;
+}
+
+}; // test05
+
+namespace test08 {
+
+template<typename T>
+struct print {
+ template<typename X>
+ std::ostream &
+ operator()(std::ostream & os, X && t)
+ {
+ return os << "forward: " << std::forward<T>(t);
+ }
+};
+
+template<typename T>
+struct print<T *> {
+ std::ostream &
+ operator()(std::ostream & os, T * t)
+ {
+ return os << "pointer: " << *t;
+ }
+};
+
+template<typename T>
+struct a {
+ explicit a(T && t)
+ : m_t(std::forward<T>(t))
+ {
+ std::ostream & os = std::cerr;
+ os << __PRETTY_FUNCTION__ << " m_t: ";
+ print<T>()(os, m_t);
+ os << std::endl;
+ }
+
+ explicit a(const T & t)
+ : m_t(t)
+ {
+ std::ostream & os = std::cerr;
+ os << __PRETTY_FUNCTION__ << " m_t: ";
+ print<T>()(os, m_t);
+ os << std::endl;
+ }
+
+ ~a(void)
+ {
+ std::ostream & os = std::cerr;
+ os << __PRETTY_FUNCTION__ << " m_t: ";
+ print<T>()(os, m_t);
+ os << std::endl;
+ }
+
+ T m_t;
+};
+
+void
+test(void)
+{
+ int i = 0;
+ double d = 0.0;
+ a<int> ai(i);
+ a<double> ad(d);
+ a<int> aim(std::move(i));
+ a<double> adm(std::move(d));
+ a<int *> aip(&i);
+ a<double *> adp(&d);
+}
+
+}; // test05
+
+namespace test09 {
+
+template<typename T, typename U>
+struct a {
+ explicit a(const T & t, const U & u)
+ : m_t(t)
+ , m_u(u)
+ {}
+ explicit a(const T & t)
+ : m_t(t)
+ {}
+ void operator=(const U & u) { m_u = u; }
+ void set(const U & u) { m_u = u; }
+ T m_t;
+ U m_u;
+};
+
+int t = 0;
+
+a<int, int>
+get_by_value(const int & u)
+{
+ return a<int, int>{ t, u };
+}
+
+a<int, int> reference_return_a(t);
+
+const a<int, int> &
+get_by_reference(const int & u)
+{
+ reference_return_a = u;
+ // reference_return_a.set(u);
+ return reference_return_a;
+}
+
+a<int, int> &&
+get_by_move(const int & u)
+{
+ return std::move(a<int, int>{t, u});
+ // return a<int, int>{t, u};
+}
+
+void
+test(void)
+{
+ int len = 1000000;
+ std::chrono::time_point<std::chrono::high_resolution_clock> start;
+ std::chrono::time_point<std::chrono::high_resolution_clock> stop;
+
+ std::cerr << "get_by_value:" << std::endl;
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < len; ++i) {
+ a<int, int> aa = get_by_value(i);
+ std::cerr << "\r" << aa.m_u;
+ }
+ stop = std::chrono::high_resolution_clock::now();
+ std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+ std::cerr << "get_by_value_rvalue:" << std::endl;
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < len; ++i) {
+ a<int, int> && aa = get_by_value(i);
+ std::cerr << "\r" << aa.m_u;
+ }
+ stop = std::chrono::high_resolution_clock::now();
+ std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+ std::cerr << "get_by_reference:" << std::endl;
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < len; ++i) {
+ const a<int, int> & aa = get_by_reference(i);
+ std::cerr << "\r" << aa.m_u;
+ }
+ stop = std::chrono::high_resolution_clock::now();
+ std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+ // invalid examples
+ /*
+ std::cerr << "get_by_move_value:" << std::endl;
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < len; ++i) {
+ a<int, int> aa = get_by_move(i);
+ std::cerr << "\r" << aa.m_u;
+ }
+ stop = std::chrono::high_resolution_clock::now();
+ std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+ std::cerr << "get_by_move_rvalue:" << std::endl;
+ start = std::chrono::high_resolution_clock::now();
+ for (int i = 0; i < len; ++i) {
+ a<int, int> && aa = get_by_move(i);
+ std::cerr << "\r" << aa.m_u;
+ }
+ stop = std::chrono::high_resolution_clock::now();
+ std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+ */
+}
+
+}; // test05
+
+namespace test10 {
+
+class a {
+ public:
+ a(const unsigned int & i) : m_int(std::make_shared<unsigned int>(i)) {}
+ operator unsigned int &(void)
+ {
+ return *m_int;
+ }
+ std::shared_ptr<unsigned int> m_int;
+};
+
+void
+test(void)
+{
+ a aa { 0 };
+ std::cerr << *aa.m_int << std::endl;
+ static_cast<unsigned int &>(aa) = 42;
+ std::cerr << *aa.m_int << std::endl;
+}
+
+}; // test05
+
+namespace test11 {
+
+template<typename T>
+class a {
+ public:
+ a(const T & i) {}
+ a(const T & i, const unsigned int &) {}
+};
+
+void
+test(void)
+{
+ std::cerr << std::boolalpha;
+ // std::cerr << std::is_constructible<a, const unsigned int &>::value << std::endl;
+ // std::cerr << std::is_constructible<a, unsigned int &>::value << std::endl;
+ // std::cerr << std::is_constructible<a, unsigned int>::value << std::endl;
+ std::cerr << std::is_constructible<a<unsigned int>, const unsigned int &>::value << std::endl;
+ std::cerr << std::is_constructible<a<unsigned int>, unsigned int &>::value << std::endl;
+ std::cerr << std::is_constructible<a<unsigned int>, unsigned int>::value << std::endl;
+}
+
+}; // test05
+
+namespace test12 {
+
+void
+test(void)
+{
+ char a[2*sizeof(int)];
+ uint * b = reinterpret_cast<uint *>(a);
+ b[0] = (uint)'a' + ((uint)'b' << 8) + ((uint)'c' << 16) + ((uint)'d' << 24);
+ b[1] = (uint)'e' + ((uint)'f' << 8) + ((uint)'g' << 16) + ((uint)'h' << 24);
+ std::cerr << b[0] << ": " << a[0] << " " << a[1] << " " << a[2] << " " << a[3] << std::endl;
+ std::cerr << b[1] << ": " << a[4] << " " << a[5] << " " << a[6] << " " << a[7] << std::endl;
+}
+
+}; // test05
+
+namespace test13 {
+
+template<typename ... Args>
+struct a {
+};
+
+template<typename ... Types> struct b;
+
+// template<template<typename ...> class T, typename ... Args>
+// template<template<typename ... Args> class T> // , typename ... Args>
+// template<typename ... Args>
+// template<template<typename T<typename ... Args>>>
+
+template<typename ... Args, template<typename ...> class T>
+struct b<T<Args ...>> {
+ b(const T<Args ...> & t)
+ : m_t(t)
+ {}
+ T<Args ...> m_t;
+};
+
+template<typename ... Args, template<typename ...> class T>
+struct b<T<Args ...> &>
+ : public b<T<Args ...>>
+{
+ using base = b<T<Args ...>>;
+ using base::base;
+};
+
+void
+test(void)
+{
+ a<int> aa_1;
+ b<a<int>> bb_1(aa_1);
+ b<a<int> &> bb_2(aa_1);
+}
+
+}; // test05
+
+namespace test15 {
+
+struct i {
+ virtual void fun(void) = 0;
+};
+
+struct a : i {
+ void fun(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b {
+};
+
+template<typename I>
+struct with_i : I
+{
+ virtual
+ void run(void)
+ {
+ std::cerr << "with_i" << std::endl;
+ static_cast<i *>(this)->fun();
+ }
+};
+
+struct without_i
+{
+ virtual
+ void run(void) { std::cerr << "without_i" << std::endl; }
+};
+
+template<typename T>
+struct i_test
+ : std::conditional<std::is_base_of<i, T>::value, with_i<T>, without_i>::type
+{};
+
+void
+test(void)
+{
+ i_test<a> a_test;
+ a_test.run();
+ i_test<b> b_test;
+ b_test.run();
+}
+
+}; // test05
+
+int main(int argc, char ** argv)
+{
+ // if (argc != 2) {
+ // std::cerr << "Need one string argument" << std::endl;
+ // return 1;
+ // }
+
+ // xid<xcb_window_t> xid_1;
+
+ // xid<xcb_window_t,
+ // allocator<xcb_window_t>::allocate<&foo::create>,
+ // allocator<xcb_window_t>::deallocate<&foo::destroy>>
+ // xid_2;
+
+ // xid<xcb_window_t,
+ // allocator<xcb_window_t, double>::allocate<&bar::create>,
+ // allocator<xcb_window_t>::deallocate<&bar::destroy>>
+ // xid_3(0.0);
+
+ // wrapper<decltype(foo::create), foo::create,
+ // decltype(foo::destroy), foo::destroy>::allocate(NULL, 0);
+
+ // wrapper<decltype(foo::create), foo::create,
+ // decltype(foo::destroy), foo::destroy>::deallocate(NULL, 0);
+
+ // w_1.allocate(NULL, 0);
+ // w_1.deallocate(NULL, 0);
+
+ // // xpp::resource class!
+ // // >>>>>>>>><<<<<<<<<<<
+ // wrapper<decltype(bar::create), bar::create,
+ // decltype(bar::destroy), bar::destroy>::allocate(NULL, 0, 0.0);
+
+ // wrapper<decltype(bar::create), bar::create,
+ // decltype(bar::destroy), bar::destroy>::deallocate(NULL, 0);
+
+ // wrapper<decltype(bar::create), bar::create,
+ // decltype(bar::destroy), bar::destroy> w_2;
+ // w_2.allocate(NULL, 0, 0.0);
+ // w_2.deallocate(NULL, 0);
+
+ // wrapper<decltype(bar::create), bar::create> w_2;
+
+ // xpp::connection<> connection("");
+
+ // std::map<int, char> char_map;
+ // char_map[0] = 'a';
+ // char_map[1] = 'b';
+ // char_map[2] = 'c';
+
+ // value_iterator<std::map<int, char>::iterator> begin =
+ // value_iterator<std::map<int, char>::iterator>(char_map.begin());
+ // value_iterator<std::map<int, char>::iterator> end =
+ // value_iterator<std::map<int, char>::iterator>(char_map.end());
+
+ // std::vector<char> chars_1 = std::vector<char>(begin, end); std::vector<char> chars_2 = { begin, end };
+ // const char * legacy_chars = "abc";
+ // std::vector<char> chars_3 = { legacy_chars, legacy_chars + 3 };
+
+ // // for (auto it = begin; it != end; ++it) {
+ // for (auto & c : chars_1) {
+ // std::cerr << "value: " << c << std::endl;
+ // }
+
+ // value_iterator<std::vector<char>::iterator> v_begin =
+ // value_iterator<std::vector<char>::iterator>(chars_1.begin());
+ // value_iterator<std::vector<char>::iterator> v_end =
+ // value_iterator<std::vector<char>::iterator>(chars_1.end());
+
+ // std::vector<char> vchar_1(v_begin, v_end);
+ // std::vector<char> vchar_2 = { v_begin, v_end };
+
+ // std::string my_string(argv[1]);
+ // std::cerr << "test 1" << std::endl;
+ // test(my_string.begin(), my_string.end());
+ // std::cerr << "test 2" << std::endl;
+ // test(my_string.c_str(), my_string.c_str() + my_string.length());
+
+ // auto atom = connection.intern_atom(false, "MY_STRING");
+
+ // xpp::request::x::change_property(connection, XCB_PROP_MODE_REPLACE,
+ // connection.root(), atom->atom, XCB_ATOM_STRING, 8, my_string.length(),
+ // my_string.c_str());
+
+ // connection.change_property(XCB_PROP_MODE_REPLACE, connection.root(),
+ // atom->atom, XCB_ATOM_STRING, 8,
+ // begin, end);
+
+ // connection.change_property(XCB_PROP_MODE_REPLACE, connection.root(),
+ // atom->atom, XCB_ATOM_STRING, 8,
+ // v_begin, v_end);
+
+ // connection.flush();
+
+ // caller()(callee(), 0);
+ // caller()(callee(), 1);
+ // caller()(callee(), 2);
+
+ // using mylib::request::checked;
+ // using mylib::request::unchecked;
+
+ // mylib::extension::request::void_request<>()();
+ // mylib::extension::request::void_request<checked>()();
+
+ // mylib::c c;
+ // c.void_request<>();
+ // c.reply_request<>();
+
+ // test00::test();
+ // test01::test();
+ // test02::test();
+ // test03::test();
+ // test04::test();
+ // test05::test();
+ // test06::test();
+ // test07::test();
+ // test08::test();
+ // test09::test();
+ // test10::test();
+ // test11::test();
+ // test12::test();
+ // test13::test();
+ // test14::test();
+ test15::test();
+
+ return 0;
+}
--- /dev/null
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+int main(int argc, char ** argv)
+{
+ Display * dpy = XOpenDisplay(NULL);
+ Window root = DefaultRootWindow(dpy);
+
+ XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+ std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+
+ for (int nsizes = 0; nsizes < 16; ++nsizes) {
+ int nrates = 0;
+ short * rates = XRRConfigRates(screen_cfg, nsizes, &nrates);
+ std::cerr << "nrates: " << nrates << std::endl;
+ for (int i = 0; i < nrates; ++i) {
+ std::cerr << "rate: " << rates[i] << std::endl;
+ }
+ }
+
+ int nhosts = 0;
+ int state = 0;
+ XHostAddress * host_addresses = XListHosts(dpy, &nhosts, &state);
+ for (int i = 0; i < nhosts; ++i) {
+ std::cerr << "address: " << host_addresses[i].address << std::endl;
+ }
+
+ return 0;
+}
--- /dev/null
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+int main(int argc, char ** argv)
+{
+ Display * dpy = XOpenDisplay(NULL);
+ Window root = DefaultRootWindow(dpy);
+
+ XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+ std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+
+ int nrates = 0;
+ short * rates = XRRConfigRates(screen_cfg, 10, &nrates);
+ std::cerr << "nrates: " << nrates << std::endl;
+ for (int i = 0; i < nrates; ++i) {
+ std::cerr << "rate: " << rates[i] << std::endl;
+ }
+
+ int nhosts = 0;
+ XHostAddress * host_addresses = XListHosts(dpy, &nhosts, True);
+ for (int i = 0; i < nhosts; ++i) {
+ std::cerr << "address: " << host_addresses[i].address << std::endl;
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/keysymdef.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/cursorfont.h> // XC_cross
+
+// #include <xcb/randr.h>
+// // #include "../gen/xproto_requests_with_accessors.hpp"
+// #include "../gen/randr_requests.hpp"
+// #include "../gen/xproto.hpp"
+// #include "../gen/randr.hpp"
+
+// #include "../xpp.hpp"
+
+#include "../event.hpp"
+#include "../core/connection.hpp"
+// #include "../gen/xproto.hpp"
+// #include "../gen/randr.hpp"
+
+bool g_quit = false;
+
+class key_printer
+ : public xpp::event::sink<xpp::x::event::key_press,
+ xpp::x::event::key_release,
+ xpp::x::event::button_press,
+ xpp::randr::event::notify,
+ xpp::damage::event::notify,
+ xpp::xfixes::event::selection_notify,
+ xpp::screensaver::event::notify
+ >
+{
+ public:
+ key_printer(const xpp::x::protocol & x)
+ : m_x(x)
+ {}
+
+ void handle(const xpp::x::event::key_press & e)
+ {
+ auto kbd_mapping = m_x.get_keyboard_mapping(e->detail, 1);
+ auto keysym = *kbd_mapping.keysyms().begin();
+
+ if (keysym == XK_Escape) {
+ std::cerr << "quitting" << std::endl;
+ m_x.ungrab_keyboard();
+ g_quit = true;
+ } else {
+ std::cerr << "key pressed: " << XKeysymToString(keysym) << std::endl;
+ }
+ }
+
+ void handle(const xpp::x::event::key_release & e)
+ {
+ auto kbd_mapping = m_x.get_keyboard_mapping(e->detail, 1);
+ auto keysym = *kbd_mapping.keysyms().begin();
+ std::cerr << "key released: " << XKeysymToString(keysym) << std::endl;
+ }
+
+ void handle(const xpp::x::event::button_press & e)
+ {
+ m_x.ungrab_pointer();
+
+ std::cerr << "root: 0x"
+ << std::hex << e->root << std::dec
+ << "; event: 0x"
+ << std::hex << e->event << std::dec
+ << "; child: 0x"
+ << std::hex << e->child << std::dec
+ << std::endl;
+
+ xpp::window grab_window = e.event<xpp::window>();
+ std::cerr << "grab_window: " << grab_window << std::endl;
+
+ if (e->event == e->root) {
+ grab_window = e.child();
+ std::cerr << "new grab_window: " << grab_window << std::endl;
+ auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+ grab_window = translate->child;
+ }
+
+ std::cerr << "grabbing "
+ << std::hex << grab_window << std::dec
+ << std::endl;
+
+ *m_x.grab_keyboard(true, grab_window,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+ }
+
+ void handle(const xpp::randr::event::notify & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ void handle(const xpp::damage::event::notify & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ void handle(const xpp::xfixes::event::selection_notify & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ void handle(const xpp::screensaver::event::notify & e)
+ {
+ std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ }
+
+ // does not work
+ // template<int OpCode, typename Event>
+ // void handle(const xpp::generic::event<OpCode, Event> &)
+ // {
+ // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+ // }
+
+ private:
+ const xpp::x::protocol & m_x;
+};
+
+class xevent : public xpp::x::event::dispatcher {
+ public:
+
+ template<typename Event>
+ void operator()(const Event &) const
+ {
+ }
+
+ // template<int OpCode, typename Event>
+ // void operator()(const xpp::generic::event<OpCode, Event> &) const
+ // {
+ // }
+
+ void doit(xcb_generic_event_t * const e) const
+ {
+#if not defined __clang__
+ dispatcher::operator()(*this, e);
+#endif
+ }
+
+ protected:
+ operator xcb_connection_t * const(void) { return nullptr; }
+};
+
+int main(int argc, char ** argv)
+{
+ xpp::connection<xpp::extension::randr,
+ xpp::extension::render,
+ xpp::extension::xv,
+ xpp::extension::damage,
+ xpp::extension::xinerama,
+ xpp::extension::xfixes,
+ xpp::extension::input,
+ xpp::extension::screensaver>
+ connection("");
+
+ // xpp::request::x::map_window(connection, 0);
+ // xpp::request::x::query_tree(connection, 0);
+
+ xpp::event::registry<xpp::extension::randr,
+ xpp::extension::render,
+ xpp::extension::xv,
+ xpp::extension::damage,
+ xpp::extension::xinerama,
+ xpp::extension::xfixes,
+ xpp::extension::input,
+ xpp::extension::screensaver>
+ registry(connection);
+
+ std::vector<key_printer *> printers(100, new key_printer(connection));
+
+ for (int i = 0; i < 100; ++i) {
+ registry.attach(0, printers[i]);
+ }
+
+ for (int i = 0; i < 99; ++i) {
+ registry.detach(0, printers[i]);
+ }
+
+ auto font_id = connection.generate_id();
+ connection.open_font(font_id, "cursor");
+ auto cursor_id = connection.generate_id();
+ connection.create_glyph_cursor(cursor_id, font_id, font_id,
+ XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+ connection.close_font(font_id);
+
+ *connection.grab_pointer(false, connection.root(),
+ XCB_EVENT_MASK_BUTTON_PRESS,
+ XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+ XCB_NONE, cursor_id);
+
+ connection.free_cursor(cursor_id);
+
+ std::cerr << "Please click on a window" << std::endl;
+
+// clang_complete does not like this
+// causes vim to segfault
+#if not defined __clang__
+
+ while (! g_quit) {
+ connection.flush();
+ registry.dispatch(connection.wait_for_event());
+ }
+
+#endif
+
+
+ // WATCHOUT FOR THE RETURN
+ // >>>>>>>>>>>>>>>>>>>>>>>
+ return 0;
+ // <<<<<<<<<<<<<<<<<<<<<<<
+
+
+
+
+
+ *((xpp::randr::protocol &)connection).query_version(
+ XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+ // auto & randr = (xpp::protocol::randr &)c;
+ // auto version = randr.query_version(
+ // XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+ // std::cerr << "randr_major: " << version->major_version
+ // << "; randr_minor: " << version->minor_version << std::endl;
+
+ // auto screens = connection.query_screens();
+
+ // for (auto & info : screens.screen_info()) {
+ // std::cerr << "screen @ "
+ // << info.x_org << "x" << info.x_org
+ // << "+"
+ // << info.width << "+" << info.height
+ // << std::endl;
+ // }
+
+ std::cerr << "connection.root().query_tree().children<xcb_window_t>():" << std::endl;
+ for (auto & window : connection.root().query_tree().children()) {
+ std::cerr << window << ", ";
+ }
+ std::cerr << std::endl;
+
+ std::cerr << "connection.root().query_tree().children<xpp::window>():" << std::endl;
+ for (auto & window : connection.root().query_tree().children<xpp::window>()) {
+ std::cerr << window << ": ";
+ for (auto & child : window.query_tree().children<xpp::window>()) {
+ std::cerr << child << ", ";
+ }
+ std::cerr << std::endl;
+ }
+ std::cerr << std::endl;
+
+ auto atom = xpp::request::x::intern_atom(
+ connection, false, "_NET_CLIENT_LIST_STACKING");
+ auto property = xpp::request::x::get_property(
+ connection, false, connection.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT_MAX);
+
+ std::cerr << "windows (xcb_window_t):" << std::hex << std::endl;
+ for (auto & w : property.value<xcb_window_t>()) {
+ std::cerr << "0x" << w << ", ";
+ }
+ std::cerr << std::dec << std::endl;
+
+ std::cerr << "windows (xpp::window)" << std::hex << std::endl;
+ for (auto & w : property.value<xpp::window>()) {
+ std::cerr << w << ": ";
+ for (auto & child : w.query_tree().children<xpp::window>()) {
+ std::cerr << child << ", ";
+ }
+ std::cerr << std::endl;
+ }
+ std::cerr << std::dec << std::endl;
+
+ std::cerr << "hosts:" << std::endl;
+ auto hosts = xpp::request::x::list_hosts(connection);
+ for (auto & host : hosts.hosts()) {
+ std::cerr << "host: " << xcb_host_address(&host) << std::endl;
+ }
+
+ std::cerr << "fonts:" << std::endl;
+ auto fonts = xpp::request::x::list_fonts(connection, 8, 1, "*");
+ for (auto & name : fonts.names()) {
+ std::cerr << "font (" << name.length() << "): " << name << std::endl;
+ }
+
+ auto font_paths = xpp::request::x::get_font_path(connection);
+ for (auto & path : font_paths.path()) {
+ std::cerr << "path (" << path.length() << "): " << path << std::endl;
+ }
+
+ auto screen_info = xpp::request::randr::get_screen_info(connection, connection.root());
+ std::cerr << "nsizes: " << (int)screen_info->nSizes << std::endl;
+ std::cerr << "SizeID: " << (int)screen_info->sizeID << std::endl;
+ int i = 0;
+ for (auto & rate : screen_info.rates()) {
+ if (++i > screen_info->nSizes) break;
+
+ uint16_t * rates = xcb_randr_refresh_rates_rates(&rate);
+ std::cerr << "rates (length: "
+ << xcb_randr_refresh_rates_rates_length(&rate)
+ << "): ";
+
+ for (int j = 0; j < xcb_randr_refresh_rates_rates_length(&rate); ++j) {
+ std::cerr << rates[j];
+ if (j < xcb_randr_refresh_rates_rates_length(&rate) - 1) {
+ std::cerr << ", ";
+ }
+ }
+ std::cerr << std::endl;
+
+ }
+
+/*
+ xcb_list_hosts_cookie_t hosts_cookie = xcb_list_hosts(c);
+ xcb_list_hosts_reply_t * hosts_reply = xcb_list_hosts_reply(connection, hosts_cookie, NULL);
+
+ std::cerr << "list hosts" << std::endl;
+ xcb_host_iterator_t host_iter = xcb_list_hosts_hosts_iterator(hosts_reply);
+ for ( ; host_iter.rem > 0; xcb_host_next(&host_iter) ) {
+ xcb_host_t * host = (xcb_host_t *)host_iter.data;
+ std::cerr << "host adress: " << xcb_host_address(host) << std::endl;
+ std::cerr << "host family: " << (int)host->family << std::endl;
+ std::cerr << "host address_len: " << (int)host->address_len << std::endl;
+ }
+
+ std::cerr << "Randr refresh rates: " << std::endl;
+ xcb_randr_get_screen_info_cookie_t screen_info_cookie =
+ xcb_randr_get_screen_info(connection, connection.root());
+ xcb_randr_get_screen_info_reply_t * screen_info_reply =
+ xcb_randr_get_screen_info_reply(connection, screen_info_cookie, NULL);
+ std::cerr << "Randr refresh rates length: "
+ << xcb_randr_get_screen_info_rates_length(screen_info_reply)
+ << std::endl;
+
+ xcb_randr_refresh_rates_iterator_t refresh_rates_iter =
+ xcb_randr_get_screen_info_rates_iterator(screen_info_reply);
+
+ for (int i = 0; i < screen_info->nSizes; ++i) {
+ xcb_randr_refresh_rates_next(&refresh_rates_iter);
+
+ xcb_randr_refresh_rates_t * rate =
+ (xcb_randr_refresh_rates_t *)refresh_rates_iter.data;
+ uint16_t * rates = xcb_randr_refresh_rates_rates(rate);
+ std::cerr << "rates (length: "
+ << xcb_randr_refresh_rates_rates_length(rate)
+ << "): ";
+ for (int j = 0; j < xcb_randr_refresh_rates_rates_length(rate); ++j) {
+ std::cerr << rates[j];
+ if (j < xcb_randr_refresh_rates_rates_length(rate) - 1) {
+ std::cerr << ", ";
+ }
+ }
+ std::cerr << std::endl;
+ }
+
+ auto pict_formats = xpp::request::render::query_pict_formats(c);
+ for (auto & format : pict_formats.formats()) {
+ std::cerr << "format.depth: " << (int)format.depth << std::endl;;
+ }
+ */
+
+ /*
+ {
+ Display * dpy = XOpenDisplay(NULL);
+ Window root = DefaultRootWindow(dpy);
+ XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+ std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+ int nrates = 0;
+ short * rates = XRRConfigRates(screen_cfg, 10, &nrates);
+ std::cerr << "nrates: " << nrates << std::endl;
+ for (int i = 0; i < nrates; ++i) {
+ std::cerr << "rate: " << rates[i] << std::endl;
+ }
+ }
+ */
+
+// auto iter = xpp::iterator<xcb_str_t, xcb_str_t, xcb_list_fonts_reply_t,
+// xcb_str_iterator_t,
+// xpp::callable<decltype(xcb_str_next), xcb_str_next>,
+// xpp::callable<decltype(xcb_str_sizeof), xcb_str_sizeof>,
+// xpp::callable<decltype(xpp::callable_test), xpp::callable_test>>();
+
+// auto iter2 = xpp::iterator<int, int, int, int,
+// xpp::callable<decltype(xpp::next), xpp::next>,
+// xpp::callable<decltype(xpp::size_of), xpp::size_of>,
+// xpp::callable<decltype(xpp::get_iterator), xpp::get_iterator>>();
+
+ // xcb_window_t root = 0;
+ // auto x = connection.x();
+ // auto windows = connection.x().query_tree(root);
+ // auto primary_output = connection.randr().get_output_primary(root);
+
+ // auto window = *windows.children().begin();
+ // auto geometry = window.get_geometry();
+
+
+
+ // primary_output->output;
+
+ /*
+ xpp::connection c("");
+ // xpp::xproto::connection cc(*c);
+ std::cerr << "c: " << *c << std::endl;
+ std::cerr << "connection.root() " << connection.root() << std::endl;
+
+ uint32_t mask = XCB_CW_BACK_PIXEL
+ | XCB_CW_BORDER_PIXEL
+ | XCB_CW_OVERRIDE_REDIRECT;
+ uint32_t values[] = { 0xff123456, 0xff654321, true };
+ // uint32_t values[] = { 0, 0, true };
+
+ xpp::window window_1(
+ connection, 24, *connection.root(), 25, 25, 50, 50, 2,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT,
+ mask, values);
+
+ std::cerr << window_1 << std::endl;
+ */
+
+ // {
+ // auto window_2 = window_1;
+ // std::cerr << window_2 << std::endl;
+ // {
+ // auto window_3 = window_2;
+ // std::cerr << window_3 << std::endl;
+ // window_3.map();
+ // connection.flush();
+ // }
+ // sleep(3);
+ // }
+
+ // window_1.map();
+ // connection.flush();
+ // sleep(1);
+
+ // connection.root().query_tree().children();
+
+
+ // intern_atom
+ /*
+ auto atom =
+ xpp::xproto::request::intern_atom(*connection, false, "_NET_CLIENT_LIST_STACKING");
+ auto atom_name = xpp::xproto::request::get_atom_name(*connection, atom->atom);
+ std::cerr << atom_name.name() << std::endl;
+
+ auto property = xpp::xproto::request::get_property(
+ *connection, false, *connection.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT_MAX);
+
+ std::cerr << "windows" << std::hex << std::endl;
+ for (auto & p : property.value<xcb_window_t>()) {
+ std::cerr << "0x" << p << ", ";
+ }
+ std::cerr << std::dec << std::endl;
+ */
+
+ /*
+ typedef xpp::generic::fixed_size::iterator<void,
+ xcb_window_t,
+ xcb_get_property_reply_t,
+ xcb_get_property_value,
+ xcb_get_property_value_length>
+ window_iterator;
+
+ window_iterator window_begin = property.value<xcb_window_t>().begin();
+ window_iterator window_end = property.value<xcb_window_t>().end();
+ */
+
+ // list_fonts_with_info && list_fonts
+ /*
+ auto fonts_with_info =
+ xpp::xproto::request::list_fonts_with_info(*connection, 8, "*");
+
+ for (int i = 0; i < 8; ++i) {
+ std::cerr << fonts_with_info.name() << std::endl;
+ fonts_with_info.reset();
+ }
+
+ auto fonts = xpp::xproto::request::list_fonts(*connection, 8, "*");
+ for (auto & font : fonts.names()) {
+ std::cerr << "(" << font.length() << "): " << font << std::endl;
+ }
+ */
+
+ // auto grab = xpp::xproto::request::grab_keyboard(
+ // *connection, false, connection.root(), XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+
+ // query_tree iterator
+ /*
+ typedef xpp::generic::fixed_size::iterator<xcb_window_t,
+ xcb_window_t,
+ xcb_query_tree_reply_t,
+ xcb_query_tree_children,
+ xcb_query_tree_children_length>
+ children_iterator;
+
+ children_iterator children_begin, children_end;
+ */
+
+ // {
+ // auto tree = xpp::xproto::request::query_tree(*connection, connection.root());
+ // children_begin = tree.children().begin();
+ // children_end = tree.children().end();
+ // }
+
+ // std::cerr << "windows" << std::endl;
+ // for (auto it = children_begin; it != children_end; ++it) {
+ // std::cerr << std::hex << "0x" << *it << ", ";
+ // }
+ // std::cerr << std::endl;
+
+ // std::cerr << "crtcs" << std::endl;
+ // auto primary = xpp::request::randr::get_output_primary(*connection, connection.root());
+ // for (auto & crtc : xpp::request::randr::get_output_info(
+ // *connection, primary->output, XCB_TIME_CURRENT_TIME).crtcs()) {
+ // std::cerr << "crtc: " << crtc << std::endl;
+ // }
+
+ // list_hosts
+ /*
+ std::cerr << "hosts" << std::endl;
+ for (auto & host : xpp::xproto::request::list_hosts(*c).hosts()) {
+ std::cerr << std::string((char *)xcb_host_address(&host),
+ xcb_host_address_length(&host))
+ << std::endl;
+ }
+ */
+
+ // std::cerr << "extensions" << std::endl;
+ // for (auto & name : xpp::xproto::request::list_extensions(*c).names()) {
+ // std::cerr << std::string((char *)xcb_str_name(&name), xcb_str_name_length(&name)) << std::endl;
+ // }
+
+ // get_font_path
+ /*
+ for (auto & path : xpp::xproto::request::get_font_path(*c).path()) {
+ std::cerr << "(" << path.length() << ") path: " << path << std::endl;
+ }
+ */
+
+ // list_fonts
+ /*
+ const size_t n_fonts = 16;
+
+ std::cerr << "fonts (xpp 2)" << std::endl;
+
+ typedef xpp::generic::variable_size::iterator<xcb_str_t,
+ xcb_str_t,
+ xcb_list_fonts_reply_t,
+ xcb_str_iterator_t,
+ xcb_str_next,
+ xcb_str_sizeof,
+ xcb_list_fonts_names_iterator>
+ names_iterator;
+
+ names_iterator names_begin, names_end;
+
+ {
+ auto names = xpp::xproto::request::list_fonts(*connection, n_fonts, 1, "*");
+
+ names_begin = names_iterator::begin(names.get());
+ names_end = names_iterator::end(names.get());
+ }
+
+ for (auto it = names_begin; it != names_end; ++it) {
+ std::cerr << it->length() << ": " << *it << std::endl;
+ }
+ */
+
+ return EXIT_SUCCESS;
+}
+
+
+ // auto it = names_begin;
+ // std::cerr << "it : " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // ++it;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // ++it;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it++;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it++;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it++;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it++;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // ++it;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it++;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // ++it;
+ // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // it--;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // --it;
+ // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+ // << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+ // << std::endl;
+
+ // {
+ // std::cerr << "fonts (native)" << std::endl;
+ // xcb_list_fonts_cookie_t cookie = xcb_list_fonts(*connection, n_fonts, 1, "*");
+ // xcb_list_fonts_reply_t * reply = xcb_list_fonts_reply(*connection, cookie, NULL);
+ // xcb_str_iterator_t iter = xcb_list_fonts_names_iterator(reply);
+ // xcb_str_t * name, * R, * prev;
+ // int len1, len2, len3, len4;
+
+ // // for ( ; iter.rem; xcb_str_next(&iter)) {
+ // // xcb_str_t * name = iter.data;
+ // // std::cerr << xcb_str_name_length(name) << ": " << std::string((char *)xcb_str_name(name), xcb_str_name_length(name)) << std::endl;
+ // // }
+
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // len1 = xcb_str_sizeof(iter.data);
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // len2 = xcb_str_sizeof(iter.data);
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // len3 = xcb_str_sizeof(iter.data);
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // len4 = xcb_str_sizeof(iter.data);
+ // xcb_str_next(&iter);
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+
+
+
+ // ++iter.rem;
+ // R = iter.data;
+ // // xcb_str_t * prev = R - xcb_str_sizeof(R);
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_sizeof(R));
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t) - 1;
+ // prev = (xcb_str_t *)((char *)R) - len4;
+ // iter.index = (char *) iter.data - (char *) prev;
+ // iter.data = prev;
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // ++iter.rem;
+ // R = iter.data;
+ // // xcb_str_t * prev = R - xcb_str_sizeof(R);
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t) - 2;
+ // prev = (xcb_str_t *)((char *)R) - len3;
+ // iter.index = (char *) iter.data - (char *) prev;
+ // iter.data = prev;
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // ++iter.rem;
+ // R = iter.data;
+ // // xcb_str_t * prev = R - xcb_str_sizeof(R);
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t);
+ // prev = (xcb_str_t *)((char *)R) - len2;
+ // iter.index = (char *) iter.data - (char *) prev;
+ // iter.data = prev;
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // ++iter.rem;
+ // R = iter.data;
+ // // xcb_str_t * prev = R - xcb_str_sizeof(R);
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+ // // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t);
+ // prev = (xcb_str_t *)((char *)R) - len1;
+ // iter.index = (char *) iter.data - (char *) prev;
+ // iter.data = prev;
+ // name = iter.data;
+ // std::cerr << std::endl;
+ // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // std::cerr << "index: 0x" << std::hex << iter.index << " (" << std::dec << iter.index << ")" << std::endl;
+ // std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+ // std::cerr << xcb_str_name_length(name) << ": "
+ // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // << std::endl;
+
+ // // xcb_str_next(&iter);
+ // // name = iter.data;
+ // // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+ // // std::cerr << "index: 0x" << std::hex << iter.index << std::dec << std::endl;
+ // // std::cerr << xcb_str_name_length(name) << ": "
+ // // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // // << std::endl;
+
+ // // ++iter.rem;
+ // // R = iter.data;
+ // // prev = R - xcb_str_sizeof(R);
+ // // iter.index = (char *) iter.data - (char *) prev;
+ // // iter.data = prev;
+ // // name = iter.data;
+ // // std::cerr << "data: 0x" << std::hex << iter.data << "(" << std::dec << iter.data << ")" << std::endl;
+ // // std::cerr << "index: 0x" << std::hex << iter.index << std::endl;
+ // // std::cerr << xcb_str_name_length(name) << ": "
+ // // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // // << std::endl;
+
+ // // xcb_str_next(&iter);
+ // // name = iter.data;
+ // // std::cerr << "data: 0x" << std::hex << iter.data << "(" << std::dec << iter.data << ")" << std::endl;
+ // // std::cerr << "index: 0x" << std::hex << iter.index << std::endl;
+ // // std::cerr << xcb_str_name_length(name) << ": "
+ // // << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+ // // << std::endl;
+
+ // std::cerr << "length: " << xcb_list_fonts_names_length(reply) << std::endl;
+ // }
+
--- /dev/null
+#include "xpp.hpp"
--- /dev/null
+#
+# Configure src
+#
+
+get_include_dirs(includes_dir)
+get_sources_dirs(src_dir)
+
+# Source tree {{{
+
+set(ALSA_SOURCES
+ ${src_dir}/adapters/alsa/control.cpp
+ ${src_dir}/adapters/alsa/mixer.cpp
+ ${src_dir}/modules/alsa.cpp
+ )
+
+set(GITHUB_SOURCES ${src_dir}/modules/github.cpp ${src_dir}/utils/http.cpp)
+
+set(I3_SOURCES
+ ${src_dir}/modules/i3.cpp
+ ${src_dir}/utils/i3.cpp
+ )
+
+set(MPD_SOURCES
+ ${src_dir}/adapters/mpd.cpp
+ ${src_dir}/modules/mpd.cpp
+ )
+
+set(NETWORK_SOURCES
+ ${src_dir}/adapters/net.cpp
+ ${src_dir}/modules/network.cpp
+ $<IF:$<BOOL:${WITH_LIBNL}>,${src_dir}/adapters/net_nl.cpp,${src_dir}/adapters/net_iw.cpp>
+ )
+
+set(PULSEAUDIO_SOURCES
+ ${src_dir}/adapters/pulseaudio.cpp
+ ${src_dir}/modules/pulseaudio.cpp
+ )
+
+set(XCURSOR_SOURCES ${src_dir}/x11/cursor.cpp)
+
+set(XKB_SOURCES
+ ${src_dir}/modules/xkeyboard.cpp
+ ${src_dir}/x11/extensions/xkb.cpp
+ )
+
+set(XRM_SOURCES ${src_dir}/x11/xresources.cpp)
+
+configure_file(
+ ${CMAKE_CURRENT_LIST_DIR}/settings.cpp.cmake
+ ${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
+ ESCAPE_QUOTES)
+
+set(POLY_SOURCES
+ ${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
+
+ ${src_dir}/adapters/script_runner.cpp
+
+ ${src_dir}/cairo/utils.cpp
+
+ ${src_dir}/components/bar.cpp
+ ${src_dir}/components/builder.cpp
+ ${src_dir}/components/command_line.cpp
+ ${src_dir}/components/config.cpp
+ ${src_dir}/components/config_parser.cpp
+ ${src_dir}/components/controller.cpp
+ ${src_dir}/components/logger.cpp
+ ${src_dir}/components/renderer.cpp
+ ${src_dir}/components/screen.cpp
+ ${src_dir}/components/eventloop.cpp
+
+ ${src_dir}/drawtypes/animation.cpp
+ ${src_dir}/drawtypes/iconset.cpp
+ ${src_dir}/drawtypes/layouticonset.cpp
+ ${src_dir}/drawtypes/label.cpp
+ ${src_dir}/drawtypes/progressbar.cpp
+ ${src_dir}/drawtypes/ramp.cpp
+
+ ${src_dir}/events/signal_emitter.cpp
+ ${src_dir}/events/signal_receiver.cpp
+
+ ${src_dir}/ipc/ipc.cpp
+ ${src_dir}/ipc/decoder.cpp
+ ${src_dir}/ipc/encoder.cpp
+ ${src_dir}/ipc/util.cpp
+
+ ${src_dir}/modules/backlight.cpp
+ ${src_dir}/modules/battery.cpp
+ ${src_dir}/modules/bspwm.cpp
+ ${src_dir}/modules/counter.cpp
+ ${src_dir}/modules/cpu.cpp
+ ${src_dir}/modules/date.cpp
+ ${src_dir}/modules/fs.cpp
+ ${src_dir}/modules/ipc.cpp
+ ${src_dir}/modules/memory.cpp
+ ${src_dir}/modules/menu.cpp
+ ${src_dir}/modules/meta/base.cpp
+ ${src_dir}/modules/meta/factory.cpp
+ ${src_dir}/modules/script.cpp
+ ${src_dir}/modules/temperature.cpp
+ ${src_dir}/modules/text.cpp
+ ${src_dir}/modules/xbacklight.cpp
+ ${src_dir}/modules/xwindow.cpp
+ ${src_dir}/modules/xworkspaces.cpp
+ ${src_dir}/modules/tray.cpp
+
+
+ ${src_dir}/tags/action_context.cpp
+ ${src_dir}/tags/context.cpp
+ ${src_dir}/tags/dispatch.cpp
+ ${src_dir}/tags/parser.cpp
+
+ ${src_dir}/utils/actions.cpp
+ ${src_dir}/utils/action_router.cpp
+ ${src_dir}/utils/bspwm.cpp
+ ${src_dir}/utils/color.cpp
+ ${src_dir}/utils/command.cpp
+ ${src_dir}/utils/concurrency.cpp
+ ${src_dir}/utils/env.cpp
+ ${src_dir}/utils/file.cpp
+ ${src_dir}/utils/inotify.cpp
+ ${src_dir}/utils/io.cpp
+ ${src_dir}/utils/process.cpp
+ ${src_dir}/utils/restack.cpp
+ ${src_dir}/utils/socket.cpp
+ ${src_dir}/utils/string.cpp
+ ${src_dir}/utils/units.cpp
+
+ ${src_dir}/x11/atoms.cpp
+ ${src_dir}/x11/background_manager.cpp
+ ${src_dir}/x11/connection.cpp
+ ${src_dir}/x11/ewmh.cpp
+ ${src_dir}/x11/extensions/composite.cpp
+ ${src_dir}/x11/extensions/randr.cpp
+ ${src_dir}/x11/icccm.cpp
+ ${src_dir}/x11/registry.cpp
+ ${src_dir}/x11/legacy_tray_manager.cpp
+ ${src_dir}/x11/tray_client.cpp
+ ${src_dir}/x11/tray_manager.cpp
+ ${src_dir}/x11/window.cpp
+ ${src_dir}/x11/winspec.cpp
+ ${src_dir}/x11/xembed.cpp
+
+ $<$<BOOL:${ENABLE_ALSA}>:${ALSA_SOURCES}>
+ $<$<BOOL:${ENABLE_CURL}>:${GITHUB_SOURCES}>
+ $<$<BOOL:${ENABLE_I3}>:${I3_SOURCES}>
+ $<$<BOOL:${ENABLE_MPD}>:${MPD_SOURCES}>
+ $<$<BOOL:${ENABLE_NETWORK}>:${NETWORK_SOURCES}>
+ $<$<BOOL:${ENABLE_PULSEAUDIO}>:${PULSEAUDIO_SOURCES}>
+ $<$<BOOL:${WITH_XCURSOR}>:${XCURSOR_SOURCES}>
+ $<$<BOOL:${WITH_XKB}>:${XKB_SOURCES}>
+ $<$<BOOL:${WITH_XRM}>:${XRM_SOURCES}>
+ )
+
+# }}}
+
+# Target poly {{{
+add_library(poly STATIC EXCLUDE_FROM_ALL ${POLY_SOURCES})
+target_include_directories(poly PUBLIC ${includes_dir})
+target_link_libraries(poly PUBLIC
+ Threads::Threads
+ Cairo::CairoFC
+ xpp
+ LibUV::LibUV
+ )
+
+if (TARGET i3ipc++)
+ target_link_libraries(poly PUBLIC i3ipc++)
+endif()
+
+if (TARGET ALSA::ALSA)
+ target_link_libraries(poly PUBLIC ALSA::ALSA)
+endif()
+
+if (TARGET CURL::libcurl)
+ target_link_libraries(poly PUBLIC CURL::libcurl)
+endif()
+
+if (TARGET LibMPDClient::LibMPDClient)
+ target_link_libraries(poly PUBLIC LibMPDClient::LibMPDClient)
+endif()
+
+if (TARGET LibNlGenl3::LibNlGenl3)
+ target_link_libraries(poly PUBLIC LibNlGenl3::LibNlGenl3)
+endif()
+
+if (TARGET Libiw::Libiw)
+ target_link_libraries(poly PUBLIC Libiw::Libiw)
+endif()
+
+if (TARGET LibPulse::LibPulse)
+ target_link_libraries(poly PUBLIC LibPulse::LibPulse)
+endif()
+
+if (TARGET Xcb::RANDR)
+ target_link_libraries(poly PUBLIC Xcb::RANDR)
+endif()
+
+if (TARGET Xcb::COMPOSITE)
+ target_link_libraries(poly PUBLIC Xcb::COMPOSITE)
+endif()
+
+if (TARGET Xcb::XKB)
+ target_link_libraries(poly PUBLIC Xcb::XKB)
+endif()
+
+if (TARGET Xcb::CURSOR)
+ target_link_libraries(poly PUBLIC Xcb::CURSOR)
+endif()
+
+if (TARGET Xcb::XRM)
+ target_link_libraries(poly PUBLIC Xcb::XRM)
+endif()
+
+if (TARGET LibInotify::LibInotify)
+ target_link_libraries(poly PUBLIC LibInotify::LibInotify)
+endif()
+
+target_compile_options(poly PUBLIC ${cxx_flags})
+set_target_properties(poly PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/libs)
+# }}}
+
+# Target: polybar {{{
+if(BUILD_POLYBAR)
+ add_executable(polybar main.cpp)
+ target_link_libraries(polybar poly)
+ target_compile_options(polybar PUBLIC ${cxx_flags})
+ set_target_properties(polybar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
+ install(TARGETS polybar
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT runtime)
+endif()
+# }}}
+
+# Target: polybar-msg {{{
+if(BUILD_POLYBAR_MSG)
+ add_executable(polybar-msg polybar-msg.cpp)
+ target_link_libraries(polybar-msg poly)
+ target_include_directories(polybar-msg PRIVATE ${includes_dir})
+ target_compile_options(polybar-msg PUBLIC ${cxx_flags})
+
+ install(TARGETS polybar-msg
+ DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT runtime)
+endif()
+# }}}
--- /dev/null
+#include "adapters/alsa/control.hpp"
+#include "adapters/alsa/generic.hpp"
+
+POLYBAR_NS
+
+namespace alsa {
+ /**
+ * Construct control object
+ */
+ control::control(int numid) : m_numid(numid) {
+ int err{0};
+
+ if ((err = snd_ctl_open(&m_ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) == -1) {
+ throw_exception<control_error>("Could not open control '" + string{ALSA_SOUNDCARD} + "'", err);
+ }
+
+ snd_config_update_free_global();
+
+ if ((err = snd_hctl_open_ctl(&m_hctl, m_ctl)) == -1) {
+ snd_ctl_close(m_ctl);
+ throw_exception<control_error>("Failed to open hctl", err);
+ }
+
+ snd_config_update_free_global();
+
+ if ((err = snd_hctl_load(m_hctl)) == -1) {
+ throw_exception<control_error>("Failed to load hctl", err);
+ }
+
+ snd_ctl_elem_id_t* m_id{nullptr};
+ snd_ctl_elem_id_alloca(&m_id);
+ snd_ctl_elem_id_set_numid(m_id, m_numid);
+
+ snd_ctl_elem_info_t* m_info{nullptr};
+ snd_ctl_elem_info_alloca(&m_info);
+ snd_ctl_elem_info_set_id(m_info, m_id);
+
+ if ((err = snd_ctl_elem_info(m_ctl, m_info)) == -1) {
+ throw_exception<control_error>("Could not get control data", err);
+ }
+
+ snd_ctl_elem_info_get_id(m_info, m_id);
+
+ if ((m_elem = snd_hctl_find_elem(m_hctl, m_id)) == nullptr) {
+ throw control_error("Could not find control with id " + to_string(snd_ctl_elem_id_get_numid(m_id)));
+ }
+
+ if ((err = snd_ctl_subscribe_events(m_ctl, 1)) == -1) {
+ throw control_error("Could not subscribe to events: " + to_string(snd_ctl_elem_id_get_numid(m_id)));
+ }
+ }
+
+ /**
+ * Deconstruct control object
+ */
+ control::~control() {
+ if (m_hctl != nullptr) {
+ snd_hctl_close(m_hctl);
+ }
+ }
+
+ /**
+ * Get the id number
+ */
+ int control::get_numid() {
+ return m_numid;
+ }
+
+ /**
+ * Wait for events
+ */
+ bool control::wait(int timeout) {
+ assert(m_ctl);
+
+ int err{0};
+
+ if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) {
+ throw_exception<control_error>("Failed to wait for events", err);
+ }
+
+ snd_ctl_event_t* event{nullptr};
+ snd_ctl_event_alloca(&event);
+
+ if ((err = snd_ctl_read(m_ctl, event)) == -1) {
+ return false;
+ }
+
+ if (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM) {
+ return snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the interface is in use
+ */
+ bool control::test_device_plugged() {
+ assert(m_elem);
+
+ snd_ctl_elem_value_t* m_value{nullptr};
+ snd_ctl_elem_value_alloca(&m_value);
+
+ int err{0};
+
+ if ((err = snd_hctl_elem_read(m_elem, m_value)) == -1) {
+ throw_exception<control_error>("Could not read control value", err);
+ }
+
+ return snd_ctl_elem_value_get_boolean(m_value, 0);
+ }
+
+ /**
+ * Process queued events
+ */
+ void control::process_events() {
+ wait(0);
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include <cmath>
+
+#include "adapters/alsa/generic.hpp"
+#include "adapters/alsa/mixer.hpp"
+#include "utils/math.hpp"
+
+#define MAX_LINEAR_DB_SCALE 24
+
+POLYBAR_NS
+
+namespace alsa {
+ /**
+ * Construct mixer object
+ */
+ mixer::mixer(string&& mixer_selem_name, string&& soundcard_name)
+ : m_name(forward<string>(mixer_selem_name)), s_name(soundcard_name) {
+ int err = 0;
+
+ if ((err = snd_mixer_open(&m_mixer, 1)) == -1) {
+ throw_exception<mixer_error>("Failed to open hardware mixer", err);
+ }
+
+ snd_config_update_free_global();
+
+ if ((err = snd_mixer_attach(m_mixer, s_name.c_str())) == -1) {
+ throw_exception<mixer_error>("Failed to attach hardware mixer control", err);
+ }
+ if ((err = snd_mixer_selem_register(m_mixer, nullptr, nullptr)) == -1) {
+ throw_exception<mixer_error>("Failed to register simple mixer element", err);
+ }
+ if ((err = snd_mixer_load(m_mixer)) == -1) {
+ throw_exception<mixer_error>("Failed to load mixer", err);
+ }
+
+ snd_mixer_selem_id_t* sid{nullptr};
+ snd_mixer_selem_id_alloca(&sid);
+ snd_mixer_selem_id_set_index(sid, 0);
+ snd_mixer_selem_id_set_name(sid, m_name.c_str());
+
+ if ((m_elem = snd_mixer_find_selem(m_mixer, sid)) == nullptr) {
+ throw mixer_error("Cannot find simple element");
+ }
+ }
+
+ /**
+ * Deconstruct mixer
+ */
+ mixer::~mixer() {
+ if (m_mixer != nullptr) {
+ snd_mixer_close(m_mixer);
+ }
+ }
+
+ /**
+ * Get mixer name
+ */
+ const string& mixer::get_name() {
+ return m_name;
+ }
+
+ /**
+ * Get the name of the soundcard that is associated with the mixer
+ */
+ const string& mixer::get_sound_card() {
+ return s_name;
+ }
+
+ /**
+ * Wait for events
+ */
+ bool mixer::wait(int timeout) {
+ assert(m_mixer);
+
+ int err = 0;
+
+ if ((err = snd_mixer_wait(m_mixer, timeout)) == -1) {
+ throw_exception<mixer_error>("Failed to wait for events", err);
+ }
+
+ return process_events() > 0;
+ }
+
+ /**
+ * Process queued mixer events
+ */
+ int mixer::process_events() {
+ int num_events{0};
+ if ((num_events = snd_mixer_handle_events(m_mixer)) == -1) {
+ throw_exception<mixer_error>("Failed to process pending events", num_events);
+ }
+
+ return num_events;
+ }
+
+ /**
+ * Get volume in percentage
+ */
+ int mixer::get_volume() {
+ assert(m_elem != nullptr);
+
+ long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
+
+ snd_mixer_selem_get_playback_volume_range(m_elem, &vol_min, &vol_max);
+
+ for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+ if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+ snd_mixer_selem_get_playback_volume(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &vol);
+ vol_total += vol;
+ chan_n++;
+ }
+ }
+
+ return math_util::unbounded_percentage(vol_total / chan_n, vol_min, vol_max);
+ }
+
+ /**
+ * Get normalized volume in percentage
+ */
+ int mixer::get_normalized_volume() {
+ assert(m_elem != nullptr);
+
+ long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
+ double normalized, min_norm;
+
+ snd_mixer_selem_get_playback_dB_range(m_elem, &vol_min, &vol_max);
+
+ for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+ if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+ snd_mixer_selem_get_playback_dB(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &vol);
+ vol_total += vol;
+ chan_n++;
+ }
+ }
+
+ if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) {
+ return math_util::percentage(vol_total / chan_n, vol_min, vol_max);
+ }
+
+ normalized = pow(10, (vol_total / chan_n - vol_max) / 6000.0);
+ if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) {
+ min_norm = pow(10, (vol_min - vol_max) / 6000.0);
+ normalized = (normalized - min_norm) / (1 - min_norm);
+ }
+
+ return 100.0f * normalized + 0.5f;
+ }
+
+ /**
+ * Set volume to given percentage
+ */
+ void mixer::set_volume(float percentage) {
+ assert(m_elem != nullptr);
+
+ if (is_muted()) {
+ return;
+ }
+
+ long vol_min, vol_max;
+ snd_mixer_selem_get_playback_volume_range(m_elem, &vol_min, &vol_max);
+ snd_mixer_selem_set_playback_volume_all(m_elem, math_util::percentage_to_value<int>(percentage, vol_min, vol_max));
+ }
+
+ /**
+ * Set normalized volume to given percentage
+ */
+ void mixer::set_normalized_volume(float percentage) {
+ assert(m_elem != nullptr);
+
+ if (is_muted()) {
+ return;
+ }
+
+ long vol_min, vol_max;
+ double min_norm;
+ percentage = percentage / 100.0f;
+
+ snd_mixer_selem_get_playback_dB_range(m_elem, &vol_min, &vol_max);
+
+ if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) {
+ snd_mixer_selem_set_playback_dB_all(m_elem, lrint(percentage * (vol_max - vol_min)) + vol_min, 0);
+ return;
+ }
+
+ if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) {
+ min_norm = pow(10, (vol_min - vol_max) / 6000.0);
+ percentage = percentage * (1 - min_norm) + min_norm;
+ }
+
+ snd_mixer_selem_set_playback_dB_all(m_elem, lrint(6000.0 * log10(percentage)) + vol_max, 0);
+ }
+
+ /**
+ * Set mute state
+ */
+ void mixer::set_mute(bool mode) {
+ assert(m_elem != nullptr);
+
+ snd_mixer_selem_set_playback_switch_all(m_elem, mode);
+ }
+
+ /**
+ * Toggle mute state
+ */
+ void mixer::toggle_mute() {
+ assert(m_elem != nullptr);
+
+ int state;
+
+ snd_mixer_selem_get_playback_switch(m_elem, SND_MIXER_SCHN_MONO, &state);
+ snd_mixer_selem_set_playback_switch_all(m_elem, !state);
+ }
+
+ /**
+ * Get current mute state
+ */
+ bool mixer::is_muted() {
+ assert(m_elem != nullptr);
+
+ int state = 0;
+
+ for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+ if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+ int state_ = 0;
+ snd_mixer_selem_get_playback_switch(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &state_);
+ state = state || state_;
+ }
+ }
+ return !state;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include <cassert>
+#include <csignal>
+#include <thread>
+#include <utility>
+
+#include "adapters/mpd.hpp"
+#include "components/logger.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+#define TRACE_BOOL(mode) m_log.trace("mpdconnection.%s: %s", __func__, mode ? "true" : "false");
+
+namespace mpd {
+ sig_atomic_t g_connection_closed = 0;
+ void g_mpd_signal_handler(int signum) {
+ if (signum == SIGPIPE) {
+ g_connection_closed = 1;
+ }
+ }
+
+ void check_connection(mpd_connection* conn) {
+ if (g_connection_closed) {
+ g_connection_closed = 0;
+ throw server_error("Connection closed (broken pipe)");
+ } else if (conn == nullptr) {
+ throw client_error("Not connected to server", MPD_ERROR_STATE);
+ }
+ }
+
+ void check_errors(mpd_connection* conn) {
+ check_connection(conn);
+
+ string err_msg;
+
+ switch (mpd_connection_get_error(conn)) {
+ case MPD_ERROR_SUCCESS:
+ return;
+ case MPD_ERROR_SERVER:
+ {
+ err_msg = mpd_connection_get_error_message(conn);
+ enum mpd_server_error server_err = mpd_connection_get_server_error(conn);
+ mpd_connection_clear_error(conn);
+ throw server_error(err_msg, server_err);
+ }
+ default:
+ {
+ err_msg = mpd_connection_get_error_message(conn);
+ enum mpd_error err = mpd_connection_get_error(conn);
+ mpd_connection_clear_error(conn);
+ throw client_error(err_msg, err);
+ }
+ }
+ }
+
+ // deleters {{{
+
+ namespace details {
+ void mpd_connection_deleter::operator()(mpd_connection* conn) {
+ if (conn != nullptr) {
+ mpd_connection_free(conn);
+ }
+ }
+
+ void mpd_status_deleter::operator()(mpd_status* status) {
+ mpd_status_free(status);
+ }
+
+ void mpd_song_deleter::operator()(mpd_song* song) {
+ mpd_song_free(song);
+ }
+ }
+
+ // }}}
+ // class: mpdsong {{{
+
+ mpdsong::operator bool() {
+ return m_song != nullptr;
+ }
+
+ string mpdsong::get_artist() {
+ assert(m_song);
+ auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ARTIST, 0);
+ return string{tag != nullptr ? tag : ""};
+ }
+
+ string mpdsong::get_album_artist() {
+ assert(m_song);
+ auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ALBUM_ARTIST, 0);
+ return string{tag != nullptr ? tag : ""};
+}
+
+ string mpdsong::get_album() {
+ assert(m_song);
+ auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ALBUM, 0);
+ return string{tag != nullptr ? tag : ""};
+ }
+
+ string mpdsong::get_date() {
+ assert(m_song);
+ auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_DATE, 0);
+ return string{tag != nullptr ? tag : ""};
+ }
+
+ string mpdsong::get_title() {
+ assert(m_song);
+ auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_TITLE, 0);
+ if (tag == nullptr) {
+ tag = mpd_song_get_tag(m_song.get(), MPD_TAG_NAME, 0);
+ if (tag == nullptr) {
+ auto uri = mpd_song_get_uri(m_song.get());
+ auto name = strrchr(uri, '/');
+ tag = name ? name + 1 : uri;
+ }
+ }
+ return string{tag};
+ }
+
+ unsigned mpdsong::get_duration() {
+ assert(m_song);
+ return mpd_song_get_duration(m_song.get());
+ }
+
+ // }}}
+ // class: mpdconnection {{{
+
+ mpdconnection::mpdconnection(
+ const logger& logger, string host, unsigned int port, string password, unsigned int timeout)
+ : m_log(logger), m_host(move(host)), m_port(port), m_password(move(password)), m_timeout(timeout) {
+ memset(&m_signal_action, 0, sizeof(m_signal_action));
+ m_signal_action.sa_handler = &g_mpd_signal_handler;
+ if (sigaction(SIGPIPE, &m_signal_action, nullptr) == -1) {
+ throw mpd_exception("Could not setup signal handler: "s + std::strerror(errno));
+ }
+ }
+
+ mpdconnection::~mpdconnection() {
+ m_signal_action.sa_handler = SIG_DFL;
+ sigaction(SIGPIPE, &m_signal_action, nullptr);
+ }
+
+ void mpdconnection::connect() {
+ try {
+ m_log.trace("mpdconnection.connect: %s, %i, \"%s\", timeout: %i", m_host, m_port, m_password, m_timeout);
+ m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
+ check_errors(m_connection.get());
+
+ if (!m_password.empty()) {
+ noidle();
+ assert(!m_listactive);
+ mpd_run_password(m_connection.get(), m_password.c_str());
+ check_errors(m_connection.get());
+ }
+
+ m_fd = mpd_connection_get_fd(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const client_error& e) {
+ disconnect();
+ throw e;
+ }
+ }
+
+ void mpdconnection::disconnect() {
+ m_connection.reset();
+ m_idle = false;
+ m_listactive = false;
+ }
+
+ bool mpdconnection::connected() {
+ return static_cast<bool>(m_connection);
+ }
+
+ bool mpdconnection::retry_connection(int interval) {
+ if (connected()) {
+ return true;
+ }
+
+ while (true) {
+ try {
+ connect();
+ return true;
+ } catch (const mpd_exception& e) {
+ std::this_thread::sleep_for(chrono::duration<double>(interval));
+ }
+ }
+
+ return false;
+ }
+
+ int mpdconnection::get_fd() {
+ return m_fd;
+ }
+
+ void mpdconnection::idle() {
+ check_connection(m_connection.get());
+ if (!m_idle) {
+ mpd_send_idle(m_connection.get());
+ check_errors(m_connection.get());
+ m_idle = true;
+ }
+ }
+
+ int mpdconnection::noidle() {
+ check_connection(m_connection.get());
+ int flags = 0;
+ if (m_idle && mpd_send_noidle(m_connection.get())) {
+ m_idle = false;
+ flags = mpd_recv_idle(m_connection.get(), true);
+ mpd_response_finish(m_connection.get());
+ check_errors(m_connection.get());
+ }
+ return flags;
+ }
+
+ unique_ptr<mpdstatus> mpdconnection::get_status() {
+ check_prerequisites();
+ auto status = make_unique<mpdstatus>(this);
+ check_errors(m_connection.get());
+ return status;
+ }
+
+ unique_ptr<mpdstatus> mpdconnection::get_status_safe() {
+ try {
+ return get_status();
+ } catch (const mpd_exception& e) {
+ return {};
+ }
+ }
+
+ unique_ptr<mpdsong> mpdconnection::get_song() {
+ check_prerequisites_commands_list();
+ mpd_send_current_song(m_connection.get());
+ mpd_song_t song{mpd_recv_song(m_connection.get()), mpd_song_t::deleter_type{}};
+ mpd_response_finish(m_connection.get());
+ check_errors(m_connection.get());
+ if (song != nullptr) {
+ return make_unique<mpdsong>(move(song));
+ }
+ return unique_ptr<mpdsong>{};
+ }
+
+ void mpdconnection::play() {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_play(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.play: %s", e.what());
+ }
+ }
+
+ void mpdconnection::pause(bool state) {
+ try {
+ TRACE_BOOL(state);
+ check_prerequisites_commands_list();
+ mpd_run_pause(m_connection.get(), state);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.pause: %s", e.what());
+ }
+ }
+
+ void mpdconnection::toggle() {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_toggle_pause(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.toggle: %s", e.what());
+ }
+ }
+
+ void mpdconnection::stop() {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_stop(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.stop: %s", e.what());
+ }
+ }
+
+ void mpdconnection::prev() {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_previous(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.prev: %s", e.what());
+ }
+ }
+
+ void mpdconnection::next() {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_next(m_connection.get());
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.next: %s", e.what());
+ }
+ }
+
+ void mpdconnection::seek(int songid, int pos) {
+ try {
+ check_prerequisites_commands_list();
+ mpd_run_seek_id(m_connection.get(), songid, pos);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.seek: %s", e.what());
+ }
+ }
+
+ void mpdconnection::set_repeat(bool mode) {
+ try {
+ TRACE_BOOL(mode);
+ check_prerequisites_commands_list();
+ mpd_run_repeat(m_connection.get(), mode);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.set_repeat: %s", e.what());
+ }
+ }
+
+ void mpdconnection::set_random(bool mode) {
+ try {
+ TRACE_BOOL(mode);
+ check_prerequisites_commands_list();
+ mpd_run_random(m_connection.get(), mode);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.set_random: %s", e.what());
+ }
+ }
+
+ void mpdconnection::set_single(bool mode) {
+ try {
+ TRACE_BOOL(mode);
+ check_prerequisites_commands_list();
+ mpd_run_single(m_connection.get(), mode);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.set_single: %s", e.what());
+ }
+ }
+
+ void mpdconnection::set_consume(bool mode) {
+ try {
+ TRACE_BOOL(mode);
+ check_prerequisites_commands_list();
+ mpd_run_consume(m_connection.get(), mode);
+ check_errors(m_connection.get());
+ } catch (const mpd_exception& e) {
+ m_log.err("mpdconnection.set_consume: %s", e.what());
+ }
+ }
+
+ mpdconnection::operator mpd_connection_t::element_type*() {
+ return m_connection.get();
+ }
+
+ void mpdconnection::check_prerequisites() {
+ check_connection(m_connection.get());
+ noidle();
+ }
+
+ void mpdconnection::check_prerequisites_commands_list() {
+ noidle();
+ assert(!m_listactive);
+ check_prerequisites();
+ }
+
+ // }}}
+ // class: mpdstatus {{{
+
+ mpdstatus::mpdstatus(mpdconnection* conn, bool autoupdate) {
+ fetch_data(conn);
+ if (autoupdate) {
+ update(-1, conn);
+ }
+ }
+
+ void mpdstatus::fetch_data(mpdconnection* conn) {
+ m_status.reset(mpd_run_status(*conn));
+ m_songid = mpd_status_get_song_id(m_status.get());
+ m_queuelen = mpd_status_get_queue_length(m_status.get());
+ m_random = mpd_status_get_random(m_status.get());
+ m_repeat = mpd_status_get_repeat(m_status.get());
+ m_single = mpd_status_get_single(m_status.get());
+ m_consume = mpd_status_get_consume(m_status.get());
+ m_elapsed_time = mpd_status_get_elapsed_time(m_status.get());
+ m_total_time = mpd_status_get_total_time(m_status.get());
+ }
+
+ void mpdstatus::update(int event, mpdconnection* connection) {
+ /*
+ * Only update if either the player state (play, stop, pause, seek, ...), the options (random, repeat, ...),
+ * or the playlist has been changed
+ */
+ if (connection == nullptr || !static_cast<bool>(event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
+ return;
+ }
+
+ fetch_data(connection);
+
+ m_elapsed_time_ms = m_elapsed_time * 1000;
+
+ auto state = mpd_status_get_state(m_status.get());
+
+ switch (state) {
+ case MPD_STATE_PAUSE:
+ m_state = mpdstate::PAUSED;
+ break;
+ case MPD_STATE_PLAY:
+ m_state = mpdstate::PLAYING;
+ break;
+ case MPD_STATE_STOP:
+ m_state = mpdstate::STOPPED;
+ break;
+ default:
+ m_state = mpdstate::UNKNOWN;
+ }
+ }
+
+ bool mpdstatus::random() const {
+ return m_random;
+ }
+
+ bool mpdstatus::repeat() const {
+ return m_repeat;
+ }
+
+ bool mpdstatus::single() const {
+ return m_single;
+ }
+
+ bool mpdstatus::consume() const {
+ return m_consume;
+ }
+
+ bool mpdstatus::match_state(mpdstate state) const {
+ return state == m_state;
+ }
+
+ int mpdstatus::get_songid() const {
+ return m_songid;
+ }
+
+ int mpdstatus::get_queuelen() const {
+ return m_queuelen;
+ }
+
+ unsigned mpdstatus::get_total_time() const {
+ return m_total_time;
+ }
+
+ unsigned mpdstatus::get_elapsed_time() const {
+ return m_elapsed_time;
+ }
+
+ unsigned mpdstatus::get_elapsed_percentage() {
+ if (m_total_time == 0) {
+ return 0;
+ }
+ return static_cast<int>(float(m_elapsed_time) / float(m_total_time) * 100.0 + 0.5f);
+ }
+
+ string mpdstatus::get_formatted_elapsed() {
+ char buffer[32];
+ snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_elapsed_time / 60, m_elapsed_time % 60);
+ return {buffer};
+ }
+
+ string mpdstatus::get_formatted_total() {
+ char buffer[32];
+ snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_total_time / 60, m_total_time % 60);
+ return {buffer};
+ }
+
+ int mpdstatus::get_seek_position(int percentage) {
+ if (m_total_time == 0) {
+ return 0;
+ }
+ math_util::cap<int>(0, 100, percentage);
+ return math_util::percentage_to_value<double>(percentage, m_total_time);
+ }
+
+ // }}}
+}
+
+#undef TRACE_BOOL
+
+POLYBAR_NS_END
--- /dev/null
+#include "adapters/net.hpp"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <linux/ethtool.h>
+#include <linux/if_link.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <cassert>
+#include <iomanip>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/command.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace net {
+ enum class NetType {
+ WIRELESS,
+ ETHERNET,
+ OTHER,
+ };
+
+ static const string NO_IP = string("N/A");
+ static const string NO_MAC = string("N/A");
+ static const string NET_PATH = "/sys/class/net/";
+ static const string VIRTUAL_PATH = "/sys/devices/virtual/";
+
+ static bool is_virtual(const std::string& ifname) {
+ char* target = realpath((NET_PATH + ifname).c_str(), nullptr);
+
+ if (!target) {
+ throw system_error("realpath");
+ }
+
+ const std::string real_path{target};
+ free(target);
+ return real_path.rfind(VIRTUAL_PATH, 0) == 0;
+ }
+
+ NetType iface_type(const std::string& ifname) {
+ if (file_util::exists(NET_PATH + ifname + "/wireless")) {
+ return NetType::WIRELESS;
+ }
+
+ if (is_virtual(ifname)) {
+ return NetType::OTHER;
+ }
+
+ return NetType::ETHERNET;
+ }
+
+ bool is_interface_valid(const string& ifname) {
+ return if_nametoindex(ifname.c_str()) != 0;
+ }
+
+ /**
+ * Returns the canonical name of the given interface and whether that differs
+ * from the given name.
+ *
+ * The canonical name is the name that has an entry in `/sys/class/net`.
+ *
+ * This resolves any altnames that were defined (see man ip-link).
+ */
+ std::pair<string, bool> get_canonical_interface(const string& ifname) {
+ int idx = if_nametoindex(ifname.c_str());
+
+ if (idx == 0) {
+ throw system_error("if_nameindex(" + ifname + ")");
+ }
+
+ char canonical[IF_NAMESIZE];
+ if (!if_indextoname(idx, canonical)) {
+ throw system_error("if_indextoname(" + to_string(idx) + ")");
+ }
+
+ string str{canonical};
+
+ return {str, str != ifname};
+ }
+
+ /**
+ * Test if interface with given name is a wireless device
+ */
+ bool is_wireless_interface(const string& ifname) {
+ return iface_type(ifname) == NetType::WIRELESS;
+ }
+
+ std::string find_interface(NetType type) {
+ struct ifaddrs* ifaddrs;
+ getifaddrs(&ifaddrs);
+
+ struct ifaddrs* candidate_if = nullptr;
+
+ for (struct ifaddrs* i = ifaddrs; i != nullptr; i = i->ifa_next) {
+ const std::string name{i->ifa_name};
+ const NetType iftype = iface_type(name);
+ if (iftype != type) {
+ continue;
+ }
+ if (candidate_if == nullptr) {
+ candidate_if = i;
+ } else if (((candidate_if->ifa_flags & IFF_RUNNING) == 0) && ((i->ifa_flags & IFF_RUNNING) > 0)) {
+ candidate_if = i;
+ }
+ }
+ if (candidate_if) {
+ const std::string name{candidate_if->ifa_name};
+ freeifaddrs(ifaddrs);
+ return name;
+ }
+ freeifaddrs(ifaddrs);
+ return "";
+ }
+
+ std::string find_wireless_interface() {
+ return find_interface(NetType::WIRELESS);
+ }
+
+ std::string find_wired_interface() {
+ return find_interface(NetType::ETHERNET);
+ }
+
+ // class : network {{{
+
+ /**
+ * Construct network interface
+ */
+ network::network(string interface) : m_log(logger::make()), m_interface(move(interface)) {
+ assert(is_interface_valid(m_interface));
+
+ m_socketfd = file_util::make_file_descriptor(socket(AF_INET, SOCK_DGRAM, 0));
+ if (!*m_socketfd) {
+ throw network_error("Failed to open socket");
+ }
+
+ check_tuntap_or_bridge();
+ }
+
+ /**
+ * Query device driver for information
+ */
+ bool network::query(bool accumulate) {
+ m_status.previous = m_status.current;
+ m_status.current.transmitted = 0;
+ m_status.current.received = 0;
+ m_status.current.time = std::chrono::steady_clock::now();
+ m_status.ip = NO_IP;
+ m_status.ip6 = NO_IP;
+
+ struct ifaddrs* ifaddr;
+ if (getifaddrs(&ifaddr) == -1 || ifaddr == nullptr) {
+ return false;
+ }
+
+ m_status.mac = string_util::trim(file_util::contents(NET_PATH + m_interface + "/address"), isspace);
+ if (m_status.mac == "") {
+ m_status.mac = NO_MAC;
+ }
+
+ for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == nullptr) {
+ continue;
+ }
+
+ if (m_interface.compare(0, m_interface.length(), ifa->ifa_name) != 0) {
+ if (!accumulate || (ifa->ifa_data == nullptr && ifa->ifa_addr->sa_family != AF_PACKET)) {
+ continue;
+ }
+ }
+
+ struct sockaddr_in6* sa6;
+
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_INET:
+ char ip_buffer[NI_MAXHOST];
+ getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), ip_buffer, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
+ m_status.ip = string{ip_buffer};
+ break;
+
+ case AF_INET6:
+ char ip6_buffer[INET6_ADDRSTRLEN];
+ sa6 = reinterpret_cast<decltype(sa6)>(ifa->ifa_addr);
+ if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) {
+ continue;
+ }
+ if (IN6_IS_ADDR_SITELOCAL(&sa6->sin6_addr)) {
+ continue;
+ }
+ if ((((unsigned char*)sa6->sin6_addr.s6_addr)[0] & 0xFE) == 0xFC) {
+ /* Skip Unique Local Addresses (fc00::/7) */
+ continue;
+ }
+ if (inet_ntop(AF_INET6, &sa6->sin6_addr, ip6_buffer, INET6_ADDRSTRLEN) == 0) {
+ m_log.warn("inet_ntop() " + string(strerror(errno)));
+ continue;
+ }
+ m_status.ip6 = string{ip6_buffer};
+ break;
+
+ case AF_PACKET:
+ if (ifa->ifa_data == nullptr) {
+ continue;
+ }
+ struct rtnl_link_stats* link_state = reinterpret_cast<decltype(link_state)>(ifa->ifa_data);
+ if (link_state == nullptr) {
+ continue;
+ }
+ m_status.current.transmitted += link_state->tx_bytes;
+ m_status.current.received += link_state->rx_bytes;
+ break;
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return true;
+ }
+
+ /**
+ * Run ping command to test internet connectivity
+ */
+ bool network::ping() const {
+ try {
+ auto exec = "ping -c 2 -W 2 -I " + m_interface + " " + string(CONNECTION_TEST_IP);
+ command<output_policy::IGNORED> ping(m_log, exec);
+ return ping.exec(true) == EXIT_SUCCESS;
+ } catch (const std::exception& err) {
+ return false;
+ }
+ }
+
+ /**
+ * Get interface ipv4 address
+ */
+ string network::ip() const {
+ return m_status.ip;
+ }
+ /**
+ * Get interface mac address
+ */
+ string network::mac() const {
+ return m_status.mac;
+ }
+
+ /**
+ * Get interface ipv6 address
+ */
+ string network::ip6() const {
+ return m_status.ip6;
+ }
+
+ /**
+ * Get download speed rate
+ */
+ string network::downspeed(int minwidth, const string& unit) const {
+ float bytes_diff = m_status.current.received - m_status.previous.received;
+ return format_speedrate(bytes_diff, minwidth, unit);
+ }
+
+ /**
+ * Get upload speed rate
+ */
+ string network::upspeed(int minwidth, const string& unit) const {
+ float bytes_diff = m_status.current.transmitted - m_status.previous.transmitted;
+ return format_speedrate(bytes_diff, minwidth, unit);
+ }
+
+ /**
+ * Get total net speed rate
+ */
+ string network::netspeed(int minwidth, const string& unit) const {
+ float bytes_diff = m_status.current.received - m_status.previous.received + m_status.current.transmitted -
+ m_status.previous.transmitted;
+ return format_speedrate(bytes_diff, minwidth, unit);
+ }
+
+ /**
+ * Set if unknown counts as up
+ */
+ void network::set_unknown_up(bool unknown) {
+ m_unknown_up = unknown;
+ }
+
+ /**
+ * Query driver info to check if the
+ * interface is a TUN/TAP device or BRIDGE
+ */
+ void network::check_tuntap_or_bridge() {
+ struct ethtool_drvinfo driver {};
+ struct ifreq request {};
+
+ driver.cmd = ETHTOOL_GDRVINFO;
+
+ memset(&request, 0, sizeof(request));
+
+ /*
+ * Only copy array size minus one bytes over to ensure there is a
+ * terminating NUL byte (which is guaranteed by memset)
+ */
+ strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+
+ request.ifr_data = reinterpret_cast<char*>(&driver);
+
+ if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+ return;
+ }
+
+ // Check if it's a TUN/TAP device
+ if (strncmp(driver.bus_info, "tun", 3) == 0) {
+ m_tuntap = true;
+ } else if (strncmp(driver.bus_info, "tap", 3) == 0) {
+ m_tuntap = true;
+ } else {
+ m_tuntap = false;
+ }
+
+ if (strncmp(driver.driver, "bridge", 6) == 0) {
+ m_bridge = true;
+ }
+ }
+
+ /**
+ * Test if the network interface is in a valid state
+ */
+ bool network::test_interface() const {
+ auto operstate = file_util::contents(NET_PATH + m_interface + "/operstate");
+ bool up = operstate.compare(0, 2, "up") == 0;
+ return m_unknown_up ? (up || operstate.compare(0, 7, "unknown") == 0) : up;
+ }
+
+ /**
+ * Format up- and download speed
+ */
+ string network::format_speedrate(float bytes_diff, int minwidth, const string& unit) const {
+ // Get time difference in seconds as a float
+ const std::chrono::duration<float> duration = m_status.current.time - m_status.previous.time;
+ float time_diff = duration.count();
+ float speedrate = bytes_diff / time_diff;
+
+ vector<pair<string, int>> units{make_pair("G", 2), make_pair("M", 1)};
+ string suffix{"K"};
+ int precision = 0;
+
+ while ((speedrate /= 1000) > 999) {
+ suffix = units.back().first;
+ precision = units.back().second;
+ units.pop_back();
+ }
+
+ return sstream() << std::setw(minwidth) << std::setfill(' ') << std::setprecision(precision) << std::fixed
+ << speedrate << " " << suffix << unit;
+ }
+
+ // }}}
+ // class : wired_network {{{
+
+ /**
+ * Query device driver for information
+ */
+ bool wired_network::query(bool accumulate) {
+ if (!network::query(accumulate)) {
+ return false;
+ }
+
+ if (m_tuntap) {
+ return true;
+ }
+
+ if (m_bridge) {
+ /* If bridge network then link speed cannot be computed
+ * TODO: Identify the physical network in bridge and compute the link speed
+ */
+ return true;
+ }
+
+ struct ifreq request {};
+ struct ethtool_cmd data {};
+
+ memset(&request, 0, sizeof(request));
+ strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+ data.cmd = ETHTOOL_GSET;
+ request.ifr_data = reinterpret_cast<char*>(&data);
+
+ if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+ m_linkspeed = -1;
+ } else {
+ m_linkspeed = data.speed;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check current connection state
+ */
+ bool wired_network::connected() const {
+ if (!m_tuntap && !network::test_interface()) {
+ return false;
+ }
+
+ struct ethtool_value data {};
+ struct ifreq request {};
+
+ memset(&request, 0, sizeof(request));
+ strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+ data.cmd = ETHTOOL_GLINK;
+ request.ifr_data = reinterpret_cast<char*>(&data);
+
+ if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+ return false;
+ }
+
+ return data.data != 0;
+ }
+
+ /**
+ *
+ * about the current connection
+ */
+ string wired_network::linkspeed() const {
+ return m_linkspeed == -1 ? "N/A"
+ : (m_linkspeed < 1000 ? (to_string(m_linkspeed) + " Mbit/s")
+ : (to_string(m_linkspeed / 1000) + " Gbit/s"));
+ }
+
+ // }}}
+
+} // namespace net
+
+POLYBAR_NS_END
--- /dev/null
+#include "adapters/net.hpp"
+
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+namespace net {
+ // class : wireless_network {{{
+
+ /**
+ * Query the wireless device for information
+ * about the current connection
+ */
+ bool wireless_network::query(bool accumulate) {
+ if (!network::query(accumulate)) {
+ return false;
+ }
+
+ auto socket_fd = file_util::make_file_descriptor(iw_sockets_open());
+ if (!*socket_fd) {
+ return false;
+ }
+
+ struct iwreq req {};
+
+ if (iw_get_ext(*socket_fd, m_interface.c_str(), SIOCGIWMODE, &req) == -1) {
+ return false;
+ }
+
+ // Ignore interfaces in ad-hoc mode
+ if (req.u.mode == IW_MODE_ADHOC) {
+ return false;
+ }
+
+ query_essid(*socket_fd);
+ query_quality(*socket_fd);
+
+ return true;
+ }
+
+ /**
+ * Check current connection state
+ */
+ bool wireless_network::connected() const {
+ if (!network::test_interface()) {
+ return false;
+ }
+ return !m_essid.empty();
+ }
+
+ /**
+ * ESSID reported by last query
+ */
+ string wireless_network::essid() const {
+ return m_essid;
+ }
+
+ /**
+ * Signal strength percentage reported by last query
+ */
+ int wireless_network::signal() const {
+ return m_signalstrength.percentage();
+ }
+
+ /**
+ * Link quality percentage reported by last query
+ */
+ int wireless_network::quality() const {
+ return m_linkquality.percentage();
+ }
+
+ /**
+ * Query for ESSID
+ */
+ void wireless_network::query_essid(const int& socket_fd) {
+ char essid[IW_ESSID_MAX_SIZE + 1];
+
+ struct iwreq req {};
+ req.u.essid.pointer = &essid;
+ req.u.essid.length = sizeof(essid);
+ req.u.essid.flags = 0;
+
+ if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWESSID, &req) != -1) {
+ m_essid = string{essid};
+ } else {
+ m_essid.clear();
+ }
+ }
+
+ /**
+ * Query for device driver quality values
+ */
+ void wireless_network::query_quality(const int& socket_fd) {
+ iwrange range{};
+ iwstats stats{};
+
+ // Fill range
+ if (iw_get_range_info(socket_fd, m_interface.c_str(), &range) == -1) {
+ return;
+ }
+ // Fill stats
+ if (iw_get_stats(socket_fd, m_interface.c_str(), &stats, &range, 1) == -1) {
+ return;
+ }
+
+ // Check if the driver supplies the quality value
+ if (stats.qual.updated & IW_QUAL_QUAL_INVALID) {
+ return;
+ }
+ // Check if the driver supplies the quality level value
+ if (stats.qual.updated & IW_QUAL_LEVEL_INVALID) {
+ return;
+ }
+
+ // Check if the link quality has been updated
+ if (stats.qual.updated & IW_QUAL_QUAL_UPDATED) {
+ m_linkquality.val = stats.qual.qual;
+ m_linkquality.max = range.max_qual.qual;
+ }
+
+ // Check if the signal strength has been updated
+ if (stats.qual.updated & IW_QUAL_LEVEL_UPDATED) {
+ m_signalstrength.val = stats.qual.level;
+ m_signalstrength.max = range.max_qual.level;
+
+ // Check if the values are defined in dBm
+ if (stats.qual.level > range.max_qual.level) {
+ m_signalstrength.val -= 0x100;
+ m_signalstrength.max = (stats.qual.level - range.max_qual.level) - 0x100;
+ }
+ }
+ }
+
+ // }}}
+} // namespace net
+
+POLYBAR_NS_END
--- /dev/null
+#include <linux/nl80211.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+
+#include <algorithm>
+
+#include "adapters/net.hpp"
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+namespace net {
+ // class : wireless_network {{{
+
+ /**
+ * Query the wireless device for information
+ * about the current connection
+ */
+ bool wireless_network::query(bool accumulate) {
+ if (!network::query(accumulate)) {
+ return false;
+ }
+
+ struct nl_sock* sk = nl_socket_alloc();
+ if (sk == nullptr) {
+ return false;
+ }
+
+ if (genl_connect(sk) < 0) {
+ return false;
+ }
+
+ int driver_id = genl_ctrl_resolve(sk, "nl80211");
+ if (driver_id < 0) {
+ nl_socket_free(sk);
+ return false;
+ }
+
+ if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, scan_cb, this) != 0) {
+ nl_socket_free(sk);
+ return false;
+ }
+
+ struct nl_msg* msg = nlmsg_alloc();
+ if (msg == nullptr) {
+ nl_socket_free(sk);
+ return false;
+ }
+
+ if ((genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) == nullptr) ||
+ nla_put_u32(msg, NL80211_ATTR_IFINDEX, m_ifid) < 0) {
+ nlmsg_free(msg);
+ nl_socket_free(sk);
+ return false;
+ }
+
+ // nl_send_sync always frees msg
+ if (nl_send_sync(sk, msg) < 0) {
+ nl_socket_free(sk);
+ return false;
+ }
+
+ nl_socket_free(sk);
+
+ return true;
+ }
+
+ /**
+ * Check current connection state
+ */
+ bool wireless_network::connected() const {
+ if (!network::test_interface()) {
+ return false;
+ }
+ return !m_essid.empty();
+ }
+
+ /**
+ * ESSID reported by last query
+ */
+ string wireless_network::essid() const {
+ return m_essid;
+ }
+
+ /**
+ * Signal strength percentage reported by last query
+ */
+ int wireless_network::signal() const {
+ return m_signalstrength.percentage();
+ }
+
+ /**
+ * Link quality percentage reported by last query
+ */
+ int wireless_network::quality() const {
+ return m_linkquality.percentage();
+ }
+
+ /**
+ * Callback to parse scan results
+ */
+ int wireless_network::scan_cb(struct nl_msg* msg, void* instance) {
+ auto wn = static_cast<wireless_network*>(instance);
+ auto gnlh = static_cast<genlmsghdr*>(nlmsg_data(nlmsg_hdr(msg)));
+ struct nlattr* tb[NL80211_ATTR_MAX + 1];
+ struct nlattr* bss[NL80211_BSS_MAX + 1];
+
+ struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};
+ bss_policy[NL80211_BSS_TSF].type = NLA_U64;
+ bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
+ bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC;
+ bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16;
+ bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16;
+ bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC;
+ bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32;
+ bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
+ bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
+
+ if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
+ return NL_SKIP;
+ }
+
+ if (tb[NL80211_ATTR_BSS] == nullptr) {
+ return NL_SKIP;
+ }
+
+ if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {
+ return NL_SKIP;
+ }
+
+ if (!wn->associated_or_joined(bss)) {
+ return NL_SKIP;
+ }
+
+ wn->parse_essid(bss);
+ wn->parse_frequency(bss);
+ wn->parse_signal(bss);
+ wn->parse_quality(bss);
+
+ return NL_SKIP;
+ }
+
+ /**
+ * Check for a connection to a AP
+ */
+ bool wireless_network::associated_or_joined(struct nlattr** bss) {
+ if (bss[NL80211_BSS_STATUS] == nullptr) {
+ return false;
+ }
+
+ auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);
+
+ switch (status) {
+ case NL80211_BSS_STATUS_ASSOCIATED:
+ case NL80211_BSS_STATUS_IBSS_JOINED:
+ case NL80211_BSS_STATUS_AUTHENTICATED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Set the ESSID
+ */
+ void wireless_network::parse_essid(struct nlattr** bss) {
+ m_essid.clear();
+
+ if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
+// Information Element ID from ieee80211.h
+#define WLAN_EID_SSID 0
+
+ auto ies = static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
+ auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+ const auto hdr_len = 2;
+
+ while (ies_len > hdr_len && ies[0] != WLAN_EID_SSID) {
+ ies_len -= ies[1] + hdr_len;
+ ies += ies[1] + hdr_len;
+ }
+
+ if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
+ auto essid_begin = ies + hdr_len;
+ auto essid_end = essid_begin + ies[1];
+
+ std::copy(essid_begin, essid_end, std::back_inserter(m_essid));
+ }
+ }
+ }
+
+ /**
+ * Set frequency
+ */
+ void wireless_network::parse_frequency(struct nlattr** bss) {
+ if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
+ // in MHz
+ m_frequency = static_cast<int>(nla_get_u32(bss[NL80211_BSS_FREQUENCY]));
+ }
+ }
+
+ /**
+ * Set device driver quality values
+ */
+ void wireless_network::parse_quality(struct nlattr** bss) {
+ if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
+ // Signal strength in unspecified units, scaled to 0..100 (u8)
+ m_linkquality.val = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
+ m_linkquality.max = 100;
+ }
+ }
+
+ /**
+ * Set the signalstrength
+ */
+ void wireless_network::parse_signal(struct nlattr** bss) {
+ if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
+ // signalstrength in dBm
+ int signalstrength = static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
+
+ // WiFi-hardware usually operates in the range -90 to -20dBm.
+ const int hardware_max = -20;
+ const int hardware_min = -90;
+ signalstrength = std::max(hardware_min, std::min(signalstrength, hardware_max));
+
+ // Shift for positive values
+ m_signalstrength.val = signalstrength - hardware_min;
+ m_signalstrength.max = hardware_max - hardware_min;
+ }
+ }
+} // namespace net
+
+POLYBAR_NS_END
--- /dev/null
+#include "adapters/pulseaudio.hpp"
+
+#include "components/logger.hpp"
+
+POLYBAR_NS
+
+/**
+ * Construct pulseaudio object
+ */
+pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume)
+ : m_log(logger), spec_s_name(sink_name) {
+ m_mainloop = pa_threaded_mainloop_new();
+ if (!m_mainloop) {
+ throw pulseaudio_error("Could not create pulseaudio threaded mainloop.");
+ }
+ pa_threaded_mainloop_lock(m_mainloop);
+
+ m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "polybar");
+ if (!m_context) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ pa_threaded_mainloop_free(m_mainloop);
+ throw pulseaudio_error("Could not create pulseaudio context.");
+ }
+
+ pa_context_set_state_callback(m_context, context_state_callback, this);
+
+ m_state_callback_signal = false;
+
+ if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
+ pa_context_disconnect(m_context);
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_unlock(m_mainloop);
+ pa_threaded_mainloop_free(m_mainloop);
+ throw pulseaudio_error("Could not connect pulseaudio context.");
+ }
+
+ if (pa_threaded_mainloop_start(m_mainloop) < 0) {
+ pa_context_disconnect(m_context);
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_unlock(m_mainloop);
+ pa_threaded_mainloop_free(m_mainloop);
+ throw pulseaudio_error("Could not start pulseaudio mainloop.");
+ }
+
+ m_log.trace("pulseaudio: started mainloop");
+
+ /*
+ * Only wait for signal from the context state callback, if it has not
+ * already signalled the mainloop since pa_context_connect was called,
+ * otherwise, we would wait forever.
+ *
+ * The while loop ensures spurious wakeups are handled.
+ */
+ while (!m_state_callback_signal) {
+ pa_threaded_mainloop_wait(m_mainloop);
+ }
+
+ if (pa_context_get_state(m_context) != PA_CONTEXT_READY) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ pa_threaded_mainloop_stop(m_mainloop);
+ pa_context_disconnect(m_context);
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_free(m_mainloop);
+ throw pulseaudio_error("Could not connect to pulseaudio server.");
+ }
+
+ pa_operation* op{nullptr};
+ if (!sink_name.empty()) {
+ op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this);
+ wait_loop(op, m_mainloop);
+ }
+ if (s_name.empty()) {
+ // get the sink index
+ op = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this);
+ wait_loop(op, m_mainloop);
+ m_log.notice("pulseaudio: using default sink %s", s_name);
+ } else {
+ m_log.trace("pulseaudio: using sink %s", s_name);
+ }
+
+ m_max_volume = max_volume ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM;
+
+ auto event_types = static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER);
+ op = pa_context_subscribe(m_context, event_types, simple_callback, this);
+ wait_loop(op, m_mainloop);
+ if (!success) {
+ throw pulseaudio_error("Failed to subscribe to sink.");
+ }
+ pa_context_set_subscribe_callback(m_context, subscribe_callback, this);
+
+ update_volume(op);
+
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Deconstruct pulseaudio
+ */
+pulseaudio::~pulseaudio() {
+ pa_threaded_mainloop_stop(m_mainloop);
+ pa_context_disconnect(m_context);
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_free(m_mainloop);
+}
+
+/**
+ * Get sink name
+ */
+const string& pulseaudio::get_name() {
+ return s_name;
+}
+
+/**
+ * Wait for events
+ */
+bool pulseaudio::wait() {
+ return m_events.size() > 0;
+}
+
+/**
+ * Process queued pulseaudio events
+ */
+int pulseaudio::process_events() {
+ int ret = m_events.size();
+ pa_threaded_mainloop_lock(m_mainloop);
+ pa_operation* o{nullptr};
+ // clear the queue
+ while (!m_events.empty()) {
+ switch (m_events.front()) {
+ // try to get specified sink
+ case evtype::NEW:
+ // redundant if already using specified sink
+ if (!spec_s_name.empty()) {
+ o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this);
+ wait_loop(o, m_mainloop);
+ break;
+ }
+ // FALLTHRU
+ case evtype::SERVER:
+ // don't fallthrough only if always using default sink
+ if (!spec_s_name.empty()) {
+ break;
+ }
+ // FALLTHRU
+ // get default sink
+ case evtype::REMOVE:
+ o = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this);
+ wait_loop(o, m_mainloop);
+ if (spec_s_name != s_name) {
+ m_log.notice("pulseaudio: using default sink %s", s_name);
+ }
+ break;
+ default:
+ break;
+ }
+ update_volume(o);
+ m_events.pop();
+ }
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return ret;
+}
+
+/**
+ * Get volume in percentage
+ */
+int pulseaudio::get_volume() {
+ // alternatively, user pa_cvolume_avg_mask() to average selected channels
+ return static_cast<int>(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f);
+}
+
+/**
+ * Get volume in decibels
+ */
+double pulseaudio::get_decibels() {
+ return pa_sw_volume_to_dB(pa_cvolume_max(&cv));
+}
+
+/**
+ * Set volume to given percentage
+ */
+void pulseaudio::set_volume(float percentage) {
+ pa_threaded_mainloop_lock(m_mainloop);
+ pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM);
+ pa_cvolume_scale(&cv, vol);
+ pa_operation* op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this);
+ wait_loop(op, m_mainloop);
+ if (!success)
+ throw pulseaudio_error("Failed to set sink volume.");
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume)
+ */
+void pulseaudio::inc_volume(int delta_perc) {
+ pa_threaded_mainloop_lock(m_mainloop);
+ pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(abs(delta_perc), PA_VOLUME_NORM);
+ if (delta_perc > 0) {
+ pa_volume_t current = pa_cvolume_max(&cv);
+ if (current + vol <= m_max_volume) {
+ pa_cvolume_inc(&cv, vol);
+ } else if (current < m_max_volume) {
+ // avoid rounding errors and set to m_max_volume directly
+ pa_cvolume_scale(&cv, m_max_volume);
+ } else {
+ m_log.notice("pulseaudio: maximum volume reached");
+ }
+ } else
+ pa_cvolume_dec(&cv, vol);
+ pa_operation* op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this);
+ wait_loop(op, m_mainloop);
+ if (!success)
+ throw pulseaudio_error("Failed to set sink volume.");
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Set mute state
+ */
+void pulseaudio::set_mute(bool mode) {
+ pa_threaded_mainloop_lock(m_mainloop);
+ pa_operation* op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this);
+ wait_loop(op, m_mainloop);
+ if (!success)
+ throw pulseaudio_error("Failed to mute sink.");
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Toggle mute state
+ */
+void pulseaudio::toggle_mute() {
+ set_mute(!is_muted());
+}
+
+/**
+ * Get current mute state
+ */
+bool pulseaudio::is_muted() {
+ return muted;
+}
+
+/**
+ * Update local volume cache
+ */
+void pulseaudio::update_volume(pa_operation* o) {
+ o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this);
+ wait_loop(o, m_mainloop);
+}
+
+/**
+ * Callback when getting volume
+ */
+void pulseaudio::get_sink_volume_callback(pa_context*, const pa_sink_info* info, int, void* userdata) {
+ pulseaudio* This = static_cast<pulseaudio*>(userdata);
+ if (info) {
+ This->cv = info->volume;
+ This->muted = info->mute;
+ }
+ pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Callback when subscribing to changes
+ */
+void pulseaudio::subscribe_callback(pa_context*, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
+ pulseaudio* This = static_cast<pulseaudio*>(userdata);
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SERVER:
+ switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+ case PA_SUBSCRIPTION_EVENT_CHANGE:
+ This->m_events.emplace(evtype::SERVER);
+ break;
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+ case PA_SUBSCRIPTION_EVENT_NEW:
+ This->m_events.emplace(evtype::NEW);
+ break;
+ case PA_SUBSCRIPTION_EVENT_CHANGE:
+ if (idx == This->m_index)
+ This->m_events.emplace(evtype::CHANGE);
+ break;
+ case PA_SUBSCRIPTION_EVENT_REMOVE:
+ if (idx == This->m_index)
+ This->m_events.emplace(evtype::REMOVE);
+ break;
+ }
+ break;
+ }
+ pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Simple callback to check for success
+ */
+void pulseaudio::simple_callback(pa_context*, int success, void* userdata) {
+ pulseaudio* This = static_cast<pulseaudio*>(userdata);
+ This->success = success;
+ pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Callback when getting sink info & existence
+ */
+void pulseaudio::sink_info_callback(pa_context*, const pa_sink_info* info, int eol, void* userdata) {
+ pulseaudio* This = static_cast<pulseaudio*>(userdata);
+ if (!eol && info) {
+ This->m_index = info->index;
+ This->s_name = info->name;
+ }
+ pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Callback when context state changes
+ */
+void pulseaudio::context_state_callback(pa_context* context, void* userdata) {
+ pulseaudio* This = static_cast<pulseaudio*>(userdata);
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ This->m_state_callback_signal = true;
+ pa_threaded_mainloop_signal(This->m_mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+inline void pulseaudio::wait_loop(pa_operation* op, pa_threaded_mainloop* loop) {
+ while (pa_operation_get_state(op) != PA_OPERATION_DONE) {
+ pa_threaded_mainloop_wait(loop);
+ }
+ pa_operation_unref(op);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "adapters/script_runner.hpp"
+
+#include <cassert>
+#include <functional>
+
+#include "modules/meta/base.hpp"
+#include "utils/command.hpp"
+#include "utils/io.hpp"
+#include "utils/scope.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+script_runner::script_runner(on_update on_update, const string& exec, const string& exec_if, bool tail,
+ interval interval_success, interval interval_fail, const vector<pair<string, string>>& env)
+ : m_log(logger::make())
+ , m_on_update(on_update)
+ , m_exec(exec)
+ , m_exec_if(exec_if)
+ , m_tail(tail)
+ , m_interval_success(interval_success)
+ , m_interval_fail(interval_fail)
+ , m_env(env) {}
+
+/**
+ * Check if defined condition is met
+ */
+bool script_runner::check_condition() const {
+ if (m_exec_if.empty()) {
+ return true;
+ }
+
+ command<output_policy::IGNORED> exec_if_cmd(m_log, m_exec_if);
+ return exec_if_cmd.exec(true) == 0;
+}
+
+/**
+ * Process mutex wrapped script handler
+ */
+script_runner::interval script_runner::process() {
+ if (m_tail) {
+ return run_tail();
+ } else {
+ return run();
+ }
+}
+
+void script_runner::clear_output() {
+ auto changed = set_output("");
+ if (changed) {
+ m_on_update(m_data);
+ }
+}
+
+void script_runner::stop() {
+ m_stopping = true;
+}
+
+bool script_runner::is_stopping() const {
+ return m_stopping;
+}
+
+/**
+ * Updates the current output.
+ *
+ * Returns true if the output changed.
+ */
+bool script_runner::set_output(string&& new_output) {
+ if (m_data.output != new_output) {
+ m_data.output = std::move(new_output);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Updates the current exit status
+ *
+ * Returns true if the exit status changed.
+ */
+bool script_runner::set_exit_status(int new_status) {
+ auto changed = (m_data.exit_status != new_status);
+ m_data.exit_status = new_status;
+
+ return changed;
+}
+
+script_runner::interval script_runner::run() {
+ auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_data.counter));
+ m_log.info("script_runner: Invoking shell command: \"%s\"", exec);
+ command<output_policy::REDIRECTED> cmd(m_log, exec);
+
+ try {
+ cmd.exec(false, m_env);
+ } catch (const exception& err) {
+ m_log.err("script_runner: %s", err.what());
+ throw modules::module_error("Failed to execute command, stopping module...");
+ }
+
+ int fd = cmd.get_stdout(PIPE_READ);
+ assert(fd != -1);
+
+ bool changed = false;
+
+ bool got_output = false;
+ while (!m_stopping && cmd.is_running() && !io_util::poll(fd, POLLHUP, 0)) {
+ /**
+ * For non-tailed scripts, we only use the first line. However, to ensure interruptability when the module shuts
+ * down, we still need to continue polling.
+ */
+ if (io_util::poll_read(fd, 250) && !got_output) {
+ changed = set_output(cmd.readline());
+ got_output = true;
+ }
+ }
+
+ if (m_stopping) {
+ cmd.terminate();
+ return 0s;
+ }
+
+ auto exit_status_changed = set_exit_status(cmd.wait());
+
+ if (changed || exit_status_changed) {
+ m_on_update(m_data);
+ }
+
+ if (m_data.exit_status == 0) {
+ return m_interval_success;
+ } else {
+ return std::max(m_interval_fail, interval{1s});
+ }
+}
+
+script_runner::interval script_runner::run_tail() {
+ auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_data.counter));
+ m_log.info("script_runner: Invoking shell command: \"%s\"", exec);
+ command<output_policy::REDIRECTED> cmd(m_log, exec);
+
+ try {
+ cmd.exec(false, m_env);
+ } catch (const exception& err) {
+ throw modules::module_error("Failed to execute command: " + string(err.what()));
+ }
+
+ scope_util::on_exit pid_guard([this]() {
+ m_data.pid = -1;
+ m_on_update(m_data);
+ });
+
+ m_data.pid = cmd.get_pid();
+
+ int fd = cmd.get_stdout(PIPE_READ);
+ assert(fd != -1);
+
+ while (!m_stopping && cmd.is_running() && !io_util::poll(fd, POLLHUP, 0)) {
+ if (io_util::poll_read(fd, 250)) {
+ auto changed = set_output(cmd.readline());
+
+ if (changed) {
+ m_on_update(m_data);
+ }
+ }
+ }
+
+ if (m_stopping) {
+ cmd.terminate();
+ return 0s;
+ }
+
+ auto exit_status = cmd.wait();
+
+ if (exit_status == 0) {
+ return m_interval_success;
+ } else {
+ return std::max(m_interval_fail, interval{1s});
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "cairo/utils.hpp"
+
+#include <map>
+
+POLYBAR_NS
+
+namespace cairo {
+namespace utils {
+
+ // implementation : device_lock {{{
+
+ device_lock::device_lock(cairo_device_t* device) {
+ auto status = cairo_device_acquire(device);
+ if (status == CAIRO_STATUS_SUCCESS) {
+ m_device = device;
+ }
+ }
+ device_lock::~device_lock() {
+ cairo_device_release(m_device);
+ }
+ device_lock::operator bool() const {
+ return m_device != nullptr;
+ }
+ device_lock::operator cairo_device_t*() const {
+ return m_device;
+ }
+
+ // }}}
+ // implementation : ft_face_lock {{{
+
+ ft_face_lock::ft_face_lock(cairo_scaled_font_t* font) : m_font(font) {
+ m_face = cairo_ft_scaled_font_lock_face(m_font);
+ }
+ ft_face_lock::~ft_face_lock() {
+ cairo_ft_scaled_font_unlock_face(m_font);
+ }
+ ft_face_lock::operator FT_Face() const {
+ return m_face;
+ }
+
+ // }}}
+
+ /**
+ * @see <cairo/cairo.h>
+ */
+ cairo_operator_t str2operator(const string& mode, cairo_operator_t fallback) {
+ if (mode.empty()) {
+ return fallback;
+ }
+ static std::map<string, cairo_operator_t> modes;
+ if (modes.empty()) {
+ modes["clear"s] = CAIRO_OPERATOR_CLEAR;
+ modes["source"s] = CAIRO_OPERATOR_SOURCE;
+ modes["over"s] = CAIRO_OPERATOR_OVER;
+ modes["in"s] = CAIRO_OPERATOR_IN;
+ modes["out"s] = CAIRO_OPERATOR_OUT;
+ modes["atop"s] = CAIRO_OPERATOR_ATOP;
+ modes["dest"s] = CAIRO_OPERATOR_DEST;
+ modes["dest-over"s] = CAIRO_OPERATOR_DEST_OVER;
+ modes["dest-in"s] = CAIRO_OPERATOR_DEST_IN;
+ modes["dest-out"s] = CAIRO_OPERATOR_DEST_OUT;
+ modes["dest-atop"s] = CAIRO_OPERATOR_DEST_ATOP;
+ modes["xor"s] = CAIRO_OPERATOR_XOR;
+ modes["add"s] = CAIRO_OPERATOR_ADD;
+ modes["saturate"s] = CAIRO_OPERATOR_SATURATE;
+ modes["multiply"s] = CAIRO_OPERATOR_MULTIPLY;
+ modes["screen"s] = CAIRO_OPERATOR_SCREEN;
+ modes["overlay"s] = CAIRO_OPERATOR_OVERLAY;
+ modes["darken"s] = CAIRO_OPERATOR_DARKEN;
+ modes["lighten"s] = CAIRO_OPERATOR_LIGHTEN;
+ modes["color-dodge"s] = CAIRO_OPERATOR_COLOR_DODGE;
+ modes["color-burn"s] = CAIRO_OPERATOR_COLOR_BURN;
+ modes["hard-light"s] = CAIRO_OPERATOR_HARD_LIGHT;
+ modes["soft-light"s] = CAIRO_OPERATOR_SOFT_LIGHT;
+ modes["difference"s] = CAIRO_OPERATOR_DIFFERENCE;
+ modes["exclusion"s] = CAIRO_OPERATOR_EXCLUSION;
+ modes["hsl-hue"s] = CAIRO_OPERATOR_HSL_HUE;
+ modes["hsl-saturation"s] = CAIRO_OPERATOR_HSL_SATURATION;
+ modes["hsl-color"s] = CAIRO_OPERATOR_HSL_COLOR;
+ modes["hsl-luminosity"s] = CAIRO_OPERATOR_HSL_LUMINOSITY;
+ }
+ auto it = modes.find(mode);
+ return it != modes.end() ? it->second : fallback;
+ }
+} // namespace utils
+} // namespace cairo
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/bar.hpp"
+
+#include <algorithm>
+
+#include "components/config.hpp"
+#include "components/renderer.hpp"
+#include "components/screen.hpp"
+#include "components/types.hpp"
+#include "drawtypes/label.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "tags/dispatch.hpp"
+#include "utils/bspwm.hpp"
+#include "utils/color.hpp"
+#include "utils/math.hpp"
+#include "utils/restack.hpp"
+#include "utils/string.hpp"
+#include "utils/units.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/icccm.hpp"
+#include "x11/legacy_tray_manager.hpp"
+
+#if WITH_XCURSOR
+#include "x11/cursor.hpp"
+#endif
+
+#if ENABLE_I3
+#include "utils/i3.hpp"
+#endif
+
+POLYBAR_NS
+
+using namespace signals::ui;
+using namespace eventloop;
+
+/**
+ * Create instance
+ */
+bar::make_type bar::make(loop& loop, const config& config, bool only_initialize_values) {
+ auto action_ctxt = make_unique<tags::action_context>();
+
+ // clang-format off
+ return std::make_unique<bar>(
+ connection::make(),
+ signal_emitter::make(),
+ config,
+ logger::make(),
+ loop,
+ screen::make(config),
+ tags::dispatch::make(*action_ctxt),
+ std::move(action_ctxt),
+ only_initialize_values);
+ // clang-format on
+}
+
+/**
+ * Construct bar instance
+ */
+bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger, loop& loop,
+ unique_ptr<screen>&& screen, unique_ptr<tags::dispatch>&& dispatch, unique_ptr<tags::action_context>&& action_ctxt,
+ bool only_initialize_values)
+ : m_connection(conn)
+ , m_sig(emitter)
+ , m_conf(config)
+ , m_log(logger)
+ , m_loop(loop)
+ , m_screen(forward<decltype(screen)>(screen))
+ , m_dispatch(forward<decltype(dispatch)>(dispatch))
+ , m_action_ctxt(forward<decltype(action_ctxt)>(action_ctxt)) {
+ string bs{m_conf.section()};
+
+ m_tray = legacy_tray::tray_manager::make(m_opts);
+
+ // Get available RandR outputs
+ auto monitor_name = m_conf.get(bs, "monitor", ""s);
+ auto monitor_name_fallback = m_conf.get(bs, "monitor-fallback", ""s);
+ m_opts.monitor_strict = m_conf.get(bs, "monitor-strict", m_opts.monitor_strict);
+ m_opts.monitor_exact = m_conf.get(bs, "monitor-exact", m_opts.monitor_exact);
+ auto monitors = randr_util::get_monitors(m_connection, m_opts.monitor_strict, false);
+
+ if (monitors.empty()) {
+ throw application_error("No monitors found");
+ }
+
+ // if monitor_name is not defined, first check for primary monitor
+ if (monitor_name.empty()) {
+ for (auto&& mon : monitors) {
+ if (mon->primary) {
+ monitor_name = mon->name;
+ break;
+ }
+ }
+ }
+
+ // if still not found (and not strict matching), get first connected monitor
+ if (monitor_name.empty() && !m_opts.monitor_strict) {
+ auto connected_monitors = randr_util::get_monitors(m_connection, true, false);
+ if (!connected_monitors.empty()) {
+ monitor_name = connected_monitors[0]->name;
+ m_log.warn("No monitor specified, using \"%s\"", monitor_name);
+ }
+ }
+
+ // if still not found, get first monitor
+ if (monitor_name.empty()) {
+ monitor_name = monitors[0]->name;
+ m_log.warn("No monitor specified, using \"%s\"", monitor_name);
+ }
+
+ // get the monitor data based on the name
+ m_opts.monitor = randr_util::match_monitor(monitors, monitor_name, m_opts.monitor_exact);
+ monitor_t fallback{};
+
+ if (!monitor_name_fallback.empty()) {
+ fallback = randr_util::match_monitor(monitors, monitor_name_fallback, m_opts.monitor_exact);
+ }
+
+ if (!m_opts.monitor) {
+ if (fallback) {
+ m_opts.monitor = move(fallback);
+ m_log.warn("Monitor \"%s\" not found, reverting to fallback \"%s\"", monitor_name, m_opts.monitor->name);
+ } else {
+ throw application_error("Monitor \"" + monitor_name + "\" not found or disconnected");
+ }
+ }
+
+ m_log.info("Loaded monitor %s (%ix%i+%i+%i)", m_opts.monitor->name, m_opts.monitor->w, m_opts.monitor->h,
+ m_opts.monitor->x, m_opts.monitor->y);
+
+ m_opts.override_redirect = m_conf.deprecated(bs, "dock", "override-redirect", m_opts.override_redirect);
+
+ m_opts.dimvalue = m_conf.get(bs, "dim-value", 1.0);
+ m_opts.dimvalue = math_util::cap(m_opts.dimvalue, 0.0, 1.0);
+
+#if WITH_XCURSOR
+ m_opts.cursor_click = m_conf.get(bs, "cursor-click", ""s);
+ if (!m_opts.cursor_click.empty() && !cursor_util::valid(m_opts.cursor_click)) {
+ m_log.warn("Ignoring unsupported cursor-click option '%s'", m_opts.cursor_click);
+ m_opts.cursor_click.clear();
+ }
+
+ m_opts.cursor_scroll = m_conf.get(bs, "cursor-scroll", ""s);
+ if (!m_opts.cursor_scroll.empty() && !cursor_util::valid(m_opts.cursor_scroll)) {
+ m_log.warn("Ignoring unsupported cursor-scroll option '%s'", m_opts.cursor_scroll);
+ m_opts.cursor_scroll.clear();
+ }
+#else
+ if (m_conf.has(bs, "cursor-click")) {
+ m_log.warn("Polybar was not compiled with xcursor support, ignoring cursor-click option");
+ }
+
+ if (m_conf.has(bs, "cursor-scroll")) {
+ m_log.warn("Polybar was not compiled with xcursor support, ignoring cursor-scroll option");
+ }
+#endif
+
+ // Build WM_NAME
+ m_opts.wmname = m_conf.get(bs, "wm-name", "polybar-" + bs.substr(4) + "_" + m_opts.monitor->name);
+ m_opts.wmname = string_util::replace(m_opts.wmname, " ", "-");
+
+ // Configure DPI
+ {
+ double dpi_x = 96, dpi_y = 96;
+ if (m_conf.has(m_conf.section(), "dpi")) {
+ dpi_x = dpi_y = m_conf.get<double>("dpi");
+ } else {
+ if (m_conf.has(m_conf.section(), "dpi-x")) {
+ dpi_x = m_conf.get<double>("dpi-x");
+ }
+ if (m_conf.has(m_conf.section(), "dpi-y")) {
+ dpi_y = m_conf.get<double>("dpi-y");
+ }
+ }
+
+ // dpi to be computed
+ if (dpi_x <= 0 || dpi_y <= 0) {
+ auto screen = m_connection.screen();
+ if (dpi_x <= 0) {
+ dpi_x = screen->width_in_pixels * 25.4 / screen->width_in_millimeters;
+ }
+ if (dpi_y <= 0) {
+ dpi_y = screen->height_in_pixels * 25.4 / screen->height_in_millimeters;
+ }
+ }
+
+ m_opts.dpi_x = dpi_x;
+ m_opts.dpi_y = dpi_y;
+
+ m_log.info("Configured DPI = %gx%g", dpi_x, dpi_y);
+ }
+
+ // Load configuration values
+
+ m_opts.bottom = m_conf.get(bs, "bottom", m_opts.bottom);
+ m_opts.spacing = m_conf.get(bs, "spacing", m_opts.spacing);
+ m_opts.separator = drawtypes::load_optional_label(m_conf, bs, "separator", "");
+ m_opts.locale = m_conf.get(bs, "locale", ""s);
+
+ auto radius = m_conf.get<double>(bs, "radius", 0.0);
+ auto top = m_conf.get(bs, "radius-top", radius);
+ m_opts.radius.top_left = m_conf.get(bs, "radius-top-left", top);
+ m_opts.radius.top_right = m_conf.get(bs, "radius-top-right", top);
+ auto bottom = m_conf.get(bs, "radius-bottom", radius);
+ m_opts.radius.bottom_left = m_conf.get(bs, "radius-bottom-left", bottom);
+ m_opts.radius.bottom_right = m_conf.get(bs, "radius-bottom-right", bottom);
+
+ auto padding = m_conf.get(bs, "padding", ZERO_SPACE);
+ m_opts.padding.left = m_conf.get(bs, "padding-left", padding);
+ m_opts.padding.right = m_conf.get(bs, "padding-right", padding);
+
+ auto margin = m_conf.get(bs, "module-margin", ZERO_SPACE);
+ m_opts.module_margin.left = m_conf.get(bs, "module-margin-left", margin);
+ m_opts.module_margin.right = m_conf.get(bs, "module-margin-right", margin);
+
+ m_opts.double_click_interval = m_conf.get(bs, "double-click-interval", m_opts.double_click_interval);
+
+ m_opts.struts = m_conf.get(bs, "enable-struts", m_opts.struts);
+
+ if (only_initialize_values) {
+ return;
+ }
+
+ // Load values used to adjust the struts atom
+
+ if (!m_opts.struts) {
+ if (m_conf.has("global/wm", "margin-bottom")) {
+ m_log.warn("Struts are disabled, ignoring margin-bottom");
+ }
+ if (m_conf.has("global/wm", "margin-top")) {
+ m_log.warn("Struts are disabled, ignoring margin-top");
+ }
+ }
+ auto margin_top = m_conf.get("global/wm", "margin-top", percentage_with_offset{});
+ auto margin_bottom = m_conf.get("global/wm", "margin-bottom", percentage_with_offset{});
+ m_opts.strut.top = units_utils::percentage_with_offset_to_pixel(margin_top, m_opts.monitor->h, m_opts.dpi_y);
+ m_opts.strut.bottom = units_utils::percentage_with_offset_to_pixel(margin_bottom, m_opts.monitor->h, m_opts.dpi_y);
+
+ // Load commands used for fallback click handlers
+ vector<action> actions;
+ actions.emplace_back(action{mousebtn::LEFT, m_conf.get(bs, "click-left", ""s)});
+ actions.emplace_back(action{mousebtn::MIDDLE, m_conf.get(bs, "click-middle", ""s)});
+ actions.emplace_back(action{mousebtn::RIGHT, m_conf.get(bs, "click-right", ""s)});
+ actions.emplace_back(action{mousebtn::SCROLL_UP, m_conf.get(bs, "scroll-up", ""s)});
+ actions.emplace_back(action{mousebtn::SCROLL_DOWN, m_conf.get(bs, "scroll-down", ""s)});
+ actions.emplace_back(action{mousebtn::DOUBLE_LEFT, m_conf.get(bs, "double-click-left", ""s)});
+ actions.emplace_back(action{mousebtn::DOUBLE_MIDDLE, m_conf.get(bs, "double-click-middle", ""s)});
+ actions.emplace_back(action{mousebtn::DOUBLE_RIGHT, m_conf.get(bs, "double-click-right", ""s)});
+
+ for (auto&& act : actions) {
+ if (!act.command.empty()) {
+ m_opts.actions.emplace_back(action{act.button, act.command});
+ }
+ }
+
+ const auto parse_or_throw_color = [&](string key, rgba def) -> rgba {
+ try {
+ rgba color = m_conf.get(bs, key, def);
+
+ /*
+ * These are the base colors of the bar and cannot be alpha only
+ * In that case, we just use the alpha channel on the default value.
+ */
+ return color.try_apply_alpha_to(def);
+ } catch (const exception& err) {
+ throw application_error(sstream() << "Failed to set " << key << " (reason: " << err.what() << ")");
+ }
+ };
+
+ // Load background
+ for (auto&& step : m_conf.get_list<rgba>(bs, "background", {})) {
+ m_opts.background_steps.emplace_back(step);
+ }
+
+ if (!m_opts.background_steps.empty()) {
+ m_opts.background = m_opts.background_steps[0];
+
+ if (m_conf.has(bs, "background")) {
+ m_log.warn("Ignoring `%s.background` (overridden by gradient background)", bs);
+ }
+ } else {
+ m_opts.background = parse_or_throw_color("background", m_opts.background);
+ }
+
+ // Load foreground
+ m_opts.foreground = parse_or_throw_color("foreground", m_opts.foreground);
+
+ // Load over-/underline
+ auto line_color = m_conf.get(bs, "line-color", rgba{0xFFFF0000});
+ auto line_size = m_conf.get(bs, "line-size", ZERO_PX_EXTENT);
+
+ auto overline_size = m_conf.get(bs, "overline-size", line_size);
+ auto underline_size = m_conf.get(bs, "underline-size", line_size);
+
+ m_opts.overline.size = units_utils::extent_to_pixel_nonnegative(overline_size, m_opts.dpi_y);
+ m_opts.overline.color = parse_or_throw_color("overline-color", line_color);
+ m_opts.underline.size = units_utils::extent_to_pixel_nonnegative(underline_size, m_opts.dpi_y);
+ m_opts.underline.color = parse_or_throw_color("underline-color", line_color);
+
+ // Load border settings
+ auto border_color = m_conf.get(bs, "border-color", rgba{0x00000000});
+ auto border_size = m_conf.get(bs, "border-size", percentage_with_offset{});
+ auto border_top = m_conf.deprecated(bs, "border-top", "border-top-size", border_size);
+ auto border_bottom = m_conf.deprecated(bs, "border-bottom", "border-bottom-size", border_size);
+ auto border_left = m_conf.deprecated(bs, "border-left", "border-left-size", border_size);
+ auto border_right = m_conf.deprecated(bs, "border-right", "border-right-size", border_size);
+
+ m_opts.borders.emplace(edge::TOP, border_settings{});
+ m_opts.borders[edge::TOP].size =
+ units_utils::percentage_with_offset_to_pixel_nonnegative(border_top, m_opts.monitor->h, m_opts.dpi_y);
+ m_opts.borders[edge::TOP].color = parse_or_throw_color("border-top-color", border_color);
+ m_opts.borders.emplace(edge::BOTTOM, border_settings{});
+ m_opts.borders[edge::BOTTOM].size =
+ units_utils::percentage_with_offset_to_pixel_nonnegative(border_bottom, m_opts.monitor->h, m_opts.dpi_y);
+ m_opts.borders[edge::BOTTOM].color = parse_or_throw_color("border-bottom-color", border_color);
+ m_opts.borders.emplace(edge::LEFT, border_settings{});
+ m_opts.borders[edge::LEFT].size =
+ units_utils::percentage_with_offset_to_pixel_nonnegative(border_left, m_opts.monitor->w, m_opts.dpi_x);
+ m_opts.borders[edge::LEFT].color = parse_or_throw_color("border-left-color", border_color);
+ m_opts.borders.emplace(edge::RIGHT, border_settings{});
+ m_opts.borders[edge::RIGHT].size =
+ units_utils::percentage_with_offset_to_pixel_nonnegative(border_right, m_opts.monitor->w, m_opts.dpi_x);
+ m_opts.borders[edge::RIGHT].color = parse_or_throw_color("border-right-color", border_color);
+
+ // Load geometry values
+ auto w = m_conf.get(m_conf.section(), "width", percentage_with_offset{100.});
+ auto h = m_conf.get(m_conf.section(), "height", percentage_with_offset{0., {extent_type::PIXEL, 24}});
+ auto offsetx = m_conf.get(m_conf.section(), "offset-x", percentage_with_offset{});
+ auto offsety = m_conf.get(m_conf.section(), "offset-y", percentage_with_offset{});
+
+ m_opts.size.w = units_utils::percentage_with_offset_to_pixel_nonnegative(w, m_opts.monitor->w, m_opts.dpi_x);
+ m_opts.size.h = units_utils::percentage_with_offset_to_pixel_nonnegative(h, m_opts.monitor->h, m_opts.dpi_y);
+ m_opts.offset.x = units_utils::percentage_with_offset_to_pixel(offsetx, m_opts.monitor->w, m_opts.dpi_x);
+ m_opts.offset.y = units_utils::percentage_with_offset_to_pixel(offsety, m_opts.monitor->h, m_opts.dpi_y);
+
+ // Apply offsets
+ m_opts.pos.x = m_opts.offset.x + m_opts.monitor->x;
+ m_opts.pos.y = m_opts.offset.y + m_opts.monitor->y;
+ m_opts.size.h += m_opts.borders[edge::TOP].size;
+ m_opts.size.h += m_opts.borders[edge::BOTTOM].size;
+
+ if (m_opts.bottom) {
+ m_opts.pos.y = m_opts.monitor->y + m_opts.monitor->h - m_opts.size.h - m_opts.offset.y;
+ }
+
+ if (m_opts.size.w <= 0 || m_opts.size.w > m_opts.monitor->w) {
+ throw application_error("Resulting bar width is out of bounds (" + to_string(m_opts.size.w) + ")");
+ } else if (m_opts.size.h <= 0 || m_opts.size.h > m_opts.monitor->h) {
+ throw application_error("Resulting bar height is out of bounds (" + to_string(m_opts.size.h) + ")");
+ }
+
+ m_log.info("Bar geometry: %ix%i+%i+%i; Borders: %d,%d,%d,%d", m_opts.size.w, m_opts.size.h, m_opts.pos.x,
+ m_opts.pos.y, m_opts.borders[edge::TOP].size, m_opts.borders[edge::RIGHT].size, m_opts.borders[edge::BOTTOM].size,
+ m_opts.borders[edge::LEFT].size);
+
+ m_log.trace("bar: Attach X event sink");
+ m_connection.attach_sink(this, SINK_PRIORITY_BAR);
+
+ m_log.trace("bar: Attach signal receiver");
+ m_sig.attach(this);
+}
+
+/**
+ * Cleanup signal handlers and destroy the bar window
+ */
+bar::~bar() {
+ m_connection.detach_sink(this, SINK_PRIORITY_BAR);
+ m_sig.detach(this);
+}
+
+/**
+ * Get the bar settings container
+ */
+const bar_settings& bar::settings() const {
+ return m_opts;
+}
+
+/**
+ * Parse input string and redraw the bar window
+ *
+ * @param data Input string
+ * @param force Unless true, do not parse unchanged data
+ */
+void bar::parse(string&& data, bool force) {
+ bool unchanged = data == m_lastinput;
+
+ m_lastinput = data;
+
+ if (force) {
+ m_log.trace("bar: Force update");
+ } else if (!m_visible) {
+ return m_log.trace("bar: Ignoring update (invisible)");
+ } else if (unchanged) {
+ return m_log.trace("bar: Ignoring update (unchanged)");
+ }
+
+ auto rect = m_opts.inner_area();
+
+ if (m_tray && !m_tray->settings().detached && m_tray->settings().configured_slots) {
+ auto tray_pos = m_tray->settings().tray_position;
+ auto traywidth = m_tray->settings().configured_w;
+ if (tray_pos == legacy_tray::tray_postition::LEFT) {
+ rect.x += traywidth;
+ rect.width -= traywidth;
+ } else if (tray_pos == legacy_tray::tray_postition::RIGHT) {
+ rect.width -= traywidth;
+ }
+ }
+
+ m_log.info("Redrawing bar window");
+ m_renderer->begin(rect);
+
+ try {
+ m_dispatch->parse(settings(), *m_renderer, std::move(data));
+ } catch (const exception& err) {
+ m_log.err("Failed to parse contents (reason: %s)", err.what());
+ }
+
+ m_renderer->end();
+
+ m_dblclicks.clear();
+ for (auto&& action : m_opts.actions) {
+ if (static_cast<int>(action.button) >= static_cast<int>(mousebtn::DOUBLE_LEFT)) {
+ m_dblclicks.insert(action.button);
+ }
+ }
+}
+
+/**
+ * Hide the bar by unmapping its X window
+ */
+void bar::hide() {
+ if (!m_visible) {
+ return;
+ }
+
+ try {
+ m_log.info("Hiding bar window");
+ m_visible = false;
+ reconfigure_struts();
+ m_sig.emit(visibility_change{false});
+ m_connection.unmap_window_checked(m_opts.x_data.window);
+ m_connection.flush();
+ } catch (const exception& err) {
+ m_log.err("Failed to unmap bar window (err=%s", err.what());
+ }
+}
+
+/**
+ * Show the bar by mapping its X window and
+ * trigger a redraw of previous content
+ */
+void bar::show() {
+ if (m_visible) {
+ return;
+ }
+
+ try {
+ m_log.info("Showing bar window");
+ m_sig.emit(visibility_change{true});
+ map_window();
+ m_connection.flush();
+ parse(string{m_lastinput}, true);
+ } catch (const exception& err) {
+ m_log.err("Failed to map bar window (err=%s", err.what());
+ }
+}
+
+/**
+ * Toggle the bar's visibility state
+ */
+void bar::toggle() {
+ if (m_visible) {
+ hide();
+ } else {
+ show();
+ }
+}
+
+/**
+ * Move the bar window above/below defined sibling in the X window stack.
+ *
+ * How the sibling is determined depends on the wm-restack setting.
+ *
+ * This is meant to resolve polybar appearing above other windows (especially in fullscreen).
+ */
+void bar::restack_window() {
+ string wm_restack;
+
+ try {
+ wm_restack = m_conf.get(m_conf.section(), "wm-restack");
+ } catch (const key_error& err) {
+ return;
+ }
+
+ xcb_window_t bar_window = m_opts.x_data.window;
+
+ restack_util::params restack_params;
+
+ if (wm_restack == "generic") {
+ restack_params = restack_util::get_generic_params(m_connection, bar_window);
+ } else if (wm_restack == "ewmh") {
+ restack_params = restack_util::get_ewmh_params(m_connection);
+ } else if (wm_restack == "bottom") {
+ restack_params = restack_util::get_bottom_params(m_connection, bar_window);
+ } else if (wm_restack == "bspwm") {
+ restack_params = bspwm_util::get_restack_params(m_connection);
+#if ENABLE_I3
+ } else if (wm_restack == "i3" && m_opts.override_redirect) {
+ restack_params = i3_util::get_restack_params(m_connection);
+ } else if (wm_restack == "i3" && !m_opts.override_redirect) {
+ m_log.warn("Ignoring restack of i3 window (not needed when `override-redirect = false`)");
+ wm_restack.clear();
+#endif
+ } else {
+ m_log.warn("Ignoring unsupported wm-restack option '%s'", wm_restack);
+ wm_restack.clear();
+ }
+
+ auto [restack_sibling, stack_mode] = restack_params;
+
+ if (restack_sibling != XCB_NONE) {
+ try {
+ m_log.info("bar: Restacking bar window relative to %s with stacking mode %s", m_connection.id(restack_sibling),
+ restack_util::stack_mode_to_string(stack_mode));
+ restack_util::restack_relative(m_connection, bar_window, restack_sibling, stack_mode);
+ m_log.info("Successfully restacked bar window");
+ } catch (const exception& err) {
+ m_log.err("Failed to restack bar window (err=%s)", err.what());
+ }
+ } else if (!wm_restack.empty()) {
+ m_log.err("Failed to restack bar window: No suitable sibling window for restacking was found");
+ }
+}
+
+void bar::reconfigure_window() {
+ m_log.trace("bar: Reconfigure window");
+ restack_window();
+ reconfigure_geom();
+ reconfigure_struts();
+ reconfigure_wm_hints();
+}
+
+/**
+ * Reconfigure window geometry
+ */
+void bar::reconfigure_geom() {
+ window win{m_connection, m_opts.x_data.window};
+ win.reconfigure_geom(m_opts.size.w, m_opts.size.h, m_opts.pos.x, m_opts.pos.y);
+}
+
+/**
+ * Reconfigure window position
+ */
+void bar::reconfigure_pos() {
+ window win{m_connection, m_opts.x_data.window};
+ win.reconfigure_pos(m_opts.pos.x, m_opts.pos.y);
+}
+
+/**
+ * Reconfigure window strut values
+ */
+void bar::reconfigure_struts() {
+ if (!m_opts.struts) {
+ return;
+ }
+ window win{m_connection, m_opts.x_data.window};
+ if (m_visible) {
+ auto geom = m_connection.get_geometry(m_connection.root());
+ int h = m_opts.size.h + m_opts.offset.y;
+
+ // Apply user-defined margins
+ if (m_opts.bottom) {
+ h += m_opts.strut.top;
+ } else {
+ h += m_opts.strut.bottom;
+ }
+
+ h = std::max(h, 0);
+
+ int correction = 0;
+
+ // Only apply correction if any space is requested
+ if (h > 0) {
+ /*
+ * Strut coordinates have to be relative to root window and not any monitor.
+ * If any monitor is not aligned at the top or bottom
+ */
+ if (m_opts.bottom) {
+ /*
+ * For bottom-algined bars, the correction is the number of pixels between
+ * the root window's bottom edge and the monitor's bottom edge
+ */
+ correction = geom->height - (m_opts.monitor->y + m_opts.monitor->h);
+ } else {
+ // For top-aligned bars, we simply add the monitor's y-position
+ correction = m_opts.monitor->y;
+ }
+
+ correction = std::max(correction, 0);
+ }
+ win.reconfigure_struts(m_opts.size.w, h + correction, m_opts.pos.x, m_opts.bottom);
+ } else {
+ // Set struts to 0 for invisible bars
+ win.reconfigure_struts(0, 0, 0, m_opts.bottom);
+ }
+}
+
+/**
+ * Reconfigure window wm hint values
+ */
+void bar::reconfigure_wm_hints() {
+ const auto& win = m_opts.x_data.window;
+
+ m_log.trace("bar: Set window WM_NAME");
+ icccm_util::set_wm_name(m_connection, win, m_opts.wmname.c_str(), m_opts.wmname.size(), "polybar\0Polybar", 15_z);
+
+ m_log.trace("bar: Set window _NET_WM_WINDOW_TYPE");
+ ewmh_util::set_wm_window_type(win, {_NET_WM_WINDOW_TYPE_DOCK});
+
+ m_log.trace("bar: Set window _NET_WM_STATE");
+ ewmh_util::set_wm_state(win, {_NET_WM_STATE_STICKY, _NET_WM_STATE_ABOVE});
+
+ m_log.trace("bar: Set window _NET_WM_DESKTOP");
+ ewmh_util::set_wm_desktop(win, 0xFFFFFFFF);
+
+ m_log.trace("bar: Set window _NET_WM_PID");
+ ewmh_util::set_wm_pid(win);
+
+ icccm_util::set_wm_size_hints(m_connection, win, m_opts.pos.x, m_opts.pos.y, m_opts.size.w, m_opts.size.h);
+}
+
+/**
+ * Broadcast current map state
+ */
+void bar::broadcast_visibility() {
+ auto attr = m_connection.get_window_attributes(m_opts.x_data.window);
+
+ if (attr->map_state == XCB_MAP_STATE_UNVIEWABLE) {
+ m_sig.emit(visibility_change{false});
+ } else if (attr->map_state == XCB_MAP_STATE_UNMAPPED) {
+ m_sig.emit(visibility_change{false});
+ } else {
+ m_sig.emit(visibility_change{true});
+ }
+}
+
+void bar::map_window() {
+ m_visible = true;
+
+ /**
+ * First reconfigures the window so that WMs that discard some information
+ * when unmapping have the correct window properties (geometry etc).
+ */
+ reconfigure_window();
+
+ m_log.trace("bar: Map window");
+ m_connection.map_window_checked(m_opts.x_data.window);
+
+ /*
+ * Required by AwesomeWM. AwesomeWM does not seem to respect polybar's position if WM_NORMAL_HINTS are set before
+ * mapping. Additionally updating the window position after mapping seems to fix that.
+ */
+ reconfigure_pos();
+}
+
+void bar::trigger_click(mousebtn btn, int pos) {
+ tags::action_t action = m_action_ctxt->has_action(btn, pos);
+
+ if (action != tags::NO_ACTION) {
+ m_log.trace("Found matching input area");
+ m_sig.emit(button_press{m_action_ctxt->get_action(action)});
+ return;
+ }
+
+ for (auto&& action : m_opts.actions) {
+ if (action.button == btn && !action.command.empty()) {
+ m_log.trace("Found matching fallback handler");
+ m_sig.emit(button_press{string{action.command}});
+ return;
+ }
+ }
+ m_log.info("No matching input area found (btn=%i)", static_cast<int>(btn));
+}
+
+/**
+ * Event handler for XCB_DESTROY_NOTIFY events
+ */
+void bar::handle(const evt::client_message& evt) {
+ if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_opts.x_data.window) {
+ m_log.err("Bar window has been destroyed, shutting down...");
+ m_connection.disconnect();
+ }
+}
+
+/**
+ * Event handler for XCB_DESTROY_NOTIFY events
+ */
+void bar::handle(const evt::destroy_notify& evt) {
+ if (evt->window == m_opts.x_data.window) {
+ m_connection.disconnect();
+ }
+}
+
+/**
+ * Event handler for XCB_ENTER_NOTIFY events
+ *
+ * Used to brighten the window by setting the
+ * _NET_WM_WINDOW_OPACITY atom value
+ */
+void bar::handle(const evt::enter_notify&) {
+ if (m_opts.dimmed) {
+ m_dim_timer->start(25, 0, [this]() {
+ m_opts.dimmed = false;
+ m_sig.emit(dim_window{1.0});
+ });
+ } else if (m_dim_timer->is_active()) {
+ m_dim_timer->stop();
+ }
+}
+
+/**
+ * Event handler for XCB_LEAVE_NOTIFY events
+ *
+ * Used to dim the window by setting the
+ * _NET_WM_WINDOW_OPACITY atom value
+ */
+void bar::handle(const evt::leave_notify&) {
+ // Only trigger dimming, if the dim-value is not fully opaque.
+ if (m_opts.dimvalue < 1.0) {
+ if (!m_opts.dimmed) {
+ m_dim_timer->start(3000, 0, [this]() {
+ m_opts.dimmed = true;
+ m_sig.emit(dim_window{double(m_opts.dimvalue)});
+ });
+ }
+ }
+}
+
+/**
+ * Event handler for XCB_MOTION_NOTIFY events
+ *
+ * Used to change the cursor depending on the module
+ */
+void bar::handle(const evt::motion_notify& evt) {
+ m_log.trace("bar: Detected motion: %i at pos(%i, %i)", evt->detail, evt->event_x, evt->event_y);
+#if WITH_XCURSOR
+ int motion_pos = evt->event_x;
+ // scroll cursor is less important than click cursor, so we shouldn't return until we are sure there is no click
+ // action
+ bool found_scroll = false;
+ const auto has_action = [&](const vector<mousebtn>& buttons) -> bool {
+ for (auto btn : buttons) {
+ if (m_action_ctxt->has_action(btn, motion_pos) != tags::NO_ACTION) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ if (!m_opts.cursor_click.empty() && has_action({mousebtn::LEFT, mousebtn::MIDDLE, mousebtn::RIGHT,
+ mousebtn::DOUBLE_LEFT, mousebtn::DOUBLE_MIDDLE, mousebtn::DOUBLE_RIGHT})) {
+ change_cursor(m_opts.cursor_click);
+ return;
+ }
+
+ if (!m_opts.cursor_scroll.empty() && has_action({mousebtn::SCROLL_DOWN, mousebtn::SCROLL_UP})) {
+ change_cursor(m_opts.cursor_scroll);
+ return;
+ }
+
+ const auto find_click_area = [&](const action& action) {
+ if (!m_opts.cursor_click.empty() &&
+ !(action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN ||
+ action.button == mousebtn::NONE)) {
+ change_cursor(m_opts.cursor_click);
+ return true;
+ } else if (!m_opts.cursor_scroll.empty() &&
+ (action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN)) {
+ if (!found_scroll) {
+ found_scroll = true;
+ }
+ }
+ return false;
+ };
+
+ for (auto&& action : m_opts.actions) {
+ if (!action.command.empty()) {
+ m_log.trace("Found matching fallback handler");
+ if (find_click_area(action)) {
+ return;
+ }
+ }
+ }
+
+ if (found_scroll) {
+ change_cursor(m_opts.cursor_scroll);
+ return;
+ }
+
+ m_log.trace("No matching cursor area found");
+ change_cursor("default");
+ return;
+#endif
+}
+
+/**
+ * Event handler for XCB_BUTTON_PRESS events
+ *
+ * Used to map mouse clicks to bar actions
+ */
+void bar::handle(const evt::button_press& evt) {
+ m_log.trace("bar: Received button press: %i at pos(%i, %i)", evt->detail, evt->event_x, evt->event_y);
+
+ mousebtn btn = static_cast<mousebtn>(evt->detail);
+ int pos = evt->event_x;
+
+ /*
+ * For possible double-clicks we need to delay the triggering of the click by
+ * the configured interval and if in that time another click arrives, we
+ * need to trigger a double click.
+ */
+ const auto check_double = [this](TimerHandle& handle, mousebtn btn, int pos) {
+ if (!handle.is_active()) {
+ handle.start(m_opts.double_click_interval, 0, [=]() { trigger_click(btn, pos); });
+ } else {
+ handle.stop();
+ trigger_click(mousebtn_get_double(btn), pos);
+ }
+ };
+
+ mousebtn double_btn = mousebtn_get_double(btn);
+ bool has_dblclick = m_dblclicks.count(double_btn) || m_action_ctxt->has_action(double_btn, pos) != tags::NO_ACTION;
+
+ // If there are no double click handlers defined we can
+ // just by-pass the click timer handling
+ if (!has_dblclick) {
+ trigger_click(btn, pos);
+ } else if (btn == mousebtn::LEFT) {
+ check_double(*m_leftclick_timer, btn, pos);
+ } else if (btn == mousebtn::MIDDLE) {
+ check_double(*m_middleclick_timer, btn, pos);
+ } else if (btn == mousebtn::RIGHT) {
+ check_double(*m_rightclick_timer, btn, pos);
+ } else {
+ trigger_click(btn, pos);
+ }
+}
+
+/**
+ * Event handler for XCB_EXPOSE events
+ *
+ * Used to redraw the bar
+ */
+void bar::handle(const evt::expose& evt) {
+ if (evt->window == m_opts.x_data.window && evt->count == 0) {
+ if (m_tray && m_tray->settings().running) {
+ broadcast_visibility();
+ }
+
+ m_log.trace("bar: Received expose event");
+ m_renderer->flush();
+ }
+}
+
+/**
+ * Event handler for XCB_PROPERTY_NOTIFY events
+ *
+ * - Emit events whenever the bar window's
+ * visibility gets changed. This allows us to toggle the
+ * state of the tray container even though the tray
+ * window restacking failed. Used as a fallback for
+ * tedious WM's, like i3.
+ */
+void bar::handle(const evt::property_notify& evt) {
+#ifdef DEBUG_LOGGER_VERBOSE
+ string atom_name = m_connection.get_atom_name(evt->atom).name();
+ m_log.trace_x("bar: property_notify(%s)", atom_name);
+#endif
+
+ if (evt->window == m_opts.x_data.window && evt->atom == WM_STATE) {
+ broadcast_visibility();
+ }
+}
+
+void bar::handle(const evt::configure_notify& evt) {
+ if (evt->window != m_opts.x_data.window) {
+ return;
+ }
+ // The absolute position of the window in the root may be different after configuration is done
+ // (for example, because the parent is not positioned at 0/0 in the root window).
+ // Notify components that the geometry may have changed (used by the background manager for example).
+ m_sig.emit(signals::ui::update_geometry{});
+}
+
+void bar::start(const string& tray_module_name) {
+ m_log.trace("bar: Create renderer");
+ m_renderer = renderer::make(m_opts, *m_action_ctxt, m_conf);
+
+ m_opts.x_data.window = m_renderer->window();
+ m_opts.x_data.visual = m_renderer->visual();
+ m_opts.x_data.depth = m_renderer->depth();
+
+ // Subscribe to window enter and leave events
+ // if we should dim the window
+ if (m_opts.dimvalue != 1.0) {
+ m_connection.ensure_event_mask(m_opts.x_data.window, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW);
+ }
+ if (!m_opts.cursor_click.empty() || !m_opts.cursor_scroll.empty()) {
+ m_connection.ensure_event_mask(m_opts.x_data.window, XCB_EVENT_MASK_POINTER_MOTION);
+ }
+ m_connection.ensure_event_mask(m_opts.x_data.window, XCB_EVENT_MASK_STRUCTURE_NOTIFY);
+
+ m_log.info("Bar window: %s", m_connection.id(m_opts.x_data.window));
+
+ map_window();
+
+ // With the mapping, the absolute position of our window may have changed (due to re-parenting for example).
+ // Notify all components that depend on the absolute bar position (such as the background manager).
+ m_sig.emit(signals::ui::update_geometry{});
+
+ m_log.trace("bar: Draw empty bar");
+ m_renderer->begin(m_opts.inner_area());
+ m_renderer->end();
+
+ m_log.trace("bar: Setup tray manager");
+ m_tray->setup(m_conf, tray_module_name);
+
+ if (m_tray->settings().tray_position == legacy_tray::tray_postition::MODULE ||
+ m_tray->settings().tray_position == legacy_tray::tray_postition::NONE) {
+ m_tray.reset();
+ }
+
+ broadcast_visibility();
+}
+
+bool bar::on(const signals::ui::dim_window& sig) {
+ m_opts.dimmed = sig.cast() != 1.0;
+ ewmh_util::set_wm_window_opacity(m_opts.x_data.window, sig.cast() * 0xFFFFFFFF);
+ return false;
+}
+
+#if WITH_XCURSOR
+void bar::change_cursor(const string& name) {
+ // This is already the same cursor, no need to update
+ if (m_cursor == name) {
+ return;
+ }
+ m_cursor = name;
+
+ if (!cursor_util::set_cursor(m_connection, m_connection.screen(), m_opts.x_data.window, name)) {
+ m_log.warn("Failed to create cursor context for cursor name '%s'", name);
+ }
+ m_connection.flush();
+}
+#endif
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/builder.hpp"
+
+#include <utility>
+
+#include "drawtypes/label.hpp"
+#include "utils/actions.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+#include "utils/time.hpp"
+#include "utils/units.hpp"
+
+POLYBAR_NS
+
+using namespace tags;
+
+builder::builder(const bar_settings& bar) : m_bar(bar) {
+ reset();
+}
+
+void builder::reset() {
+ /* Add all values as keys so that we never have to check if a key exists in
+ * the map
+ */
+ m_tags.clear();
+ m_tags[syntaxtag::A] = 0;
+ m_tags[syntaxtag::B] = 0;
+ m_tags[syntaxtag::F] = 0;
+ m_tags[syntaxtag::T] = 0;
+ m_tags[syntaxtag::R] = 0;
+ m_tags[syntaxtag::o] = 0;
+ m_tags[syntaxtag::u] = 0;
+ m_tags[syntaxtag::P] = 0;
+
+ m_attrs.clear();
+ m_output.clear();
+}
+
+/**
+ * Flush contents of the builder and return built string
+ *
+ * This will also close any unclosed tags
+ */
+string builder::flush() {
+ background_close();
+ foreground_close();
+ font_close();
+ overline_color_close();
+ underline_color_close();
+ underline_close();
+ overline_close();
+
+ while (m_tags[syntaxtag::A]) {
+ action_close();
+ }
+
+ string output{};
+ std::swap(m_output, output);
+
+ reset();
+
+ return output;
+}
+
+/**
+ * Insert raw text string
+ */
+void builder::append(const string& text) {
+ m_output.reserve(text.size());
+ m_output += text;
+}
+
+/**
+ * Insert text node
+ *
+ * This will also parse raw syntax tags
+ */
+void builder::node(const string& str) {
+ if (str.empty()) {
+ return;
+ }
+
+ append(str);
+}
+
+/**
+ * Insert text node with specific font index
+ *
+ * @see builder::node
+ */
+void builder::node(const string& str, int font_index) {
+ font(font_index);
+ node(str);
+ font_close();
+}
+
+/**
+ * Insert tags for given label
+ */
+void builder::node(const label_t& label) {
+ if (!label || !*label) {
+ return;
+ }
+
+ auto text = label->get();
+
+ if (label->m_margin.left) {
+ spacing(label->m_margin.left);
+ }
+
+ if (label->m_overline.has_color()) {
+ overline(label->m_overline);
+ }
+ if (label->m_underline.has_color()) {
+ underline(label->m_underline);
+ }
+
+ if (label->m_background.has_color()) {
+ background(label->m_background);
+ }
+ if (label->m_foreground.has_color()) {
+ foreground(label->m_foreground);
+ }
+
+ if (label->m_padding.left) {
+ spacing(label->m_padding.left);
+ }
+
+ node(text, label->m_font);
+
+ if (label->m_padding.right) {
+ spacing(label->m_padding.right);
+ }
+
+ if (label->m_background.has_color()) {
+ background_close();
+ }
+ if (label->m_foreground.has_color()) {
+ foreground_close();
+ }
+
+ if (label->m_underline.has_color()) {
+ underline_close();
+ }
+ if (label->m_overline.has_color()) {
+ overline_close();
+ }
+
+ if (label->m_margin.right) {
+ spacing(label->m_margin.right);
+ }
+}
+
+/**
+ * Repeat label contents n times
+ */
+void builder::node_repeat(const label_t& label, size_t n) {
+ string text;
+ string label_text{label->get()};
+ text.reserve(label_text.size() * n);
+ while (n--) {
+ text += label_text;
+ }
+
+ label_t tmp{new label_t::element_type{text}};
+ tmp->replace_defined_values(label);
+ node(tmp);
+}
+
+/**
+ * Insert tag that will offset the contents by the given extent
+ */
+void builder::offset(extent_val extent) {
+ if (!extent) {
+ return;
+ }
+ tag_open(syntaxtag::O, units_utils::extent_to_string(extent));
+}
+
+/**
+ * Insert spacing
+ */
+void builder::spacing(spacing_val size) {
+ if (!size && m_bar.spacing) {
+ // TODO remove once the deprecated spacing key in the bar section is removed
+ // The spacing in the bar section acts as a fallback for all spacing value
+ size = m_bar.spacing;
+ }
+
+ if (size) {
+ m_output += get_spacing_format_string(size);
+ }
+}
+
+/**
+ * Insert tag to alter the current font index
+ */
+void builder::font(int index) {
+ if (index == 0) {
+ return;
+ }
+ tag_open(syntaxtag::T, to_string(index));
+}
+
+/**
+ * Insert tag to reset the font index
+ */
+void builder::font_close() {
+ tag_close(syntaxtag::T);
+}
+
+/**
+ * Insert tag to alter the current background color
+ */
+void builder::background(rgba color) {
+ color = color.try_apply_alpha_to(m_bar.background);
+
+ auto hex = color_util::simplify_hex(color);
+ tag_open(syntaxtag::B, hex);
+}
+
+/**
+ * Insert tag to reset the background color
+ */
+void builder::background_close() {
+ tag_close(syntaxtag::B);
+}
+
+/**
+ * Insert tag to alter the current foreground color
+ */
+void builder::foreground(rgba color) {
+ color = color.try_apply_alpha_to(m_bar.foreground);
+
+ auto hex = color_util::simplify_hex(color);
+ tag_open(syntaxtag::F, hex);
+}
+
+/**
+ * Insert tag to reset the foreground color
+ */
+void builder::foreground_close() {
+ tag_close(syntaxtag::F);
+}
+
+/**
+ * Close underline color tag
+ */
+void builder::overline_color_close() {
+ tag_close(syntaxtag::o);
+}
+
+/**
+ * Close underline color tag
+ */
+void builder::underline_color_close() {
+ tag_close(syntaxtag::u);
+}
+
+/**
+ * Insert tag to enable the overline attribute with the given color
+ */
+void builder::overline(const rgba& color) {
+ if (color.has_color()) {
+ auto hex = color_util::simplify_hex(color);
+ tag_open(syntaxtag::o, hex);
+ tag_open(attribute::OVERLINE);
+ }
+}
+
+/**
+ * Close overline attribute tag
+ */
+void builder::overline_close() {
+ tag_close(attribute::OVERLINE);
+}
+
+/**
+ * Insert tag to enable the underline attribute with the given color
+ */
+void builder::underline(const rgba& color) {
+ if (color.has_color()) {
+ auto hex = color_util::simplify_hex(color);
+ tag_open(syntaxtag::u, hex);
+ tag_open(attribute::UNDERLINE);
+ }
+}
+
+/**
+ * Close underline attribute tag
+ */
+void builder::underline_close() {
+ tag_close(attribute::UNDERLINE);
+}
+
+/**
+ * Add a polybar control tag
+ */
+void builder::control(controltag tag) {
+ string str;
+ switch (tag) {
+ case controltag::R:
+ str = "R";
+ break;
+ case controltag::t:
+ str = "t";
+ break;
+ default:
+ throw runtime_error("Invalid controltag: " + to_string(to_integral(tag)));
+ }
+
+ if (!str.empty()) {
+ tag_open(syntaxtag::P, str);
+ }
+}
+
+/**
+ * Open action tag with the given action string
+ *
+ * The action string is escaped, if needed.
+ */
+void builder::action(mousebtn index, string action) {
+ if (!action.empty()) {
+ action = string_util::replace_all(action, ":", "\\:");
+ tag_open(syntaxtag::A, to_string(to_integral(index)) + ":" + action + ":");
+ }
+}
+
+/**
+ * Open action tag for the action of the given module
+ */
+void builder::action(mousebtn btn, const modules::module_interface& module, string action_name, string data) {
+ action(btn, actions_util::get_action_string(module, action_name, data));
+}
+
+/**
+ * Wrap label in action tag
+ */
+void builder::action(mousebtn index, string action_name, const label_t& label) {
+ if (label && *label) {
+ action(index, action_name);
+ node(label);
+ action_close();
+ }
+}
+
+/**
+ * Wrap label in module action tag
+ */
+void builder::action(
+ mousebtn btn, const modules::module_interface& module, string action_name, string data, const label_t& label) {
+ action(btn, actions_util::get_action_string(module, action_name, data), label);
+}
+
+/**
+ * Close command tag
+ */
+void builder::action_close() {
+ tag_close(syntaxtag::A);
+}
+
+/**
+ * Insert directive to change value of given tag
+ */
+void builder::tag_open(syntaxtag tag, const string& value) {
+ m_tags[tag]++;
+
+ switch (tag) {
+ case syntaxtag::A:
+ append("%{A" + value + "}");
+ break;
+ case syntaxtag::F:
+ append("%{F" + value + "}");
+ break;
+ case syntaxtag::B:
+ append("%{B" + value + "}");
+ break;
+ case syntaxtag::T:
+ append("%{T" + value + "}");
+ break;
+ case syntaxtag::u:
+ append("%{u" + value + "}");
+ break;
+ case syntaxtag::o:
+ append("%{o" + value + "}");
+ break;
+ case syntaxtag::R:
+ append("%{R}");
+ break;
+ case syntaxtag::O:
+ append("%{O" + value + "}");
+ break;
+ case syntaxtag::P:
+ append("%{P" + value + "}");
+ break;
+ case syntaxtag::l:
+ append("%{l}");
+ break;
+ case syntaxtag::c:
+ append("%{c}");
+ break;
+ case syntaxtag::r:
+ append("%{r}");
+ break;
+ default:
+ throw runtime_error("Invalid tag: " + to_string(to_integral(tag)));
+ }
+}
+
+/**
+ * Insert directive to use given attribute unless already set
+ */
+void builder::tag_open(attribute attr) {
+ // Don't emit activation tag if the attribute is already activated
+ if (m_attrs.count(attr) != 0) {
+ return;
+ }
+
+ m_attrs.insert(attr);
+
+ switch (attr) {
+ case attribute::UNDERLINE:
+ append("%{+u}");
+ break;
+ case attribute::OVERLINE:
+ append("%{+o}");
+ break;
+ default:
+ throw runtime_error("Invalid attribute: " + to_string(to_integral(attr)));
+ }
+}
+
+/**
+ * Insert directive to reset given tag if it's open and closable
+ */
+void builder::tag_close(syntaxtag tag) {
+ if (!m_tags[tag]) {
+ return;
+ }
+
+ m_tags[tag]--;
+
+ switch (tag) {
+ case syntaxtag::A:
+ append("%{A}");
+ break;
+ case syntaxtag::F:
+ append("%{F-}");
+ break;
+ case syntaxtag::B:
+ append("%{B-}");
+ break;
+ case syntaxtag::T:
+ append("%{T-}");
+ break;
+ case syntaxtag::u:
+ append("%{u-}");
+ break;
+ case syntaxtag::o:
+ append("%{o-}");
+ break;
+ default:
+ throw runtime_error("Cannot close syntaxtag: " + to_string(to_integral(tag)));
+ }
+}
+
+/**
+ * Insert directive to remove given attribute if set
+ */
+void builder::tag_close(attribute attr) {
+ // Don't close activation tag if it wasn't activated
+ if (m_attrs.erase(attr) == 0) {
+ return;
+ }
+
+ switch (attr) {
+ case attribute::UNDERLINE:
+ append("%{-u}");
+ break;
+ case attribute::OVERLINE:
+ append("%{-o}");
+ break;
+ default:
+ throw runtime_error("Invalid attribute: " + to_string(to_integral(attr)));
+ }
+}
+
+string builder::get_spacing_format_string(spacing_val space) {
+ float value = space.value;
+ if (value == 0) {
+ return "";
+ }
+
+ if (space.type == spacing_type::SPACE) {
+ return string(value, ' ');
+ } else {
+ return "%{O" + units_utils::extent_to_string(units_utils::spacing_to_extent(space)) + "}";
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/command_line.hpp"
+
+#include <algorithm>
+
+POLYBAR_NS
+
+namespace command_line {
+ /**
+ * Create instance
+ */
+ parser::make_type parser::make(string&& scriptname, const options&& opts) {
+ return std::make_unique<parser>("Usage: " + scriptname + " [OPTION]... [BAR]", forward<decltype(opts)>(opts));
+ }
+
+ /**
+ * Construct parser
+ */
+ parser::parser(string&& synopsis, const options&& opts)
+ : m_synopsis(forward<decltype(synopsis)>(synopsis)), m_opts(forward<decltype(opts)>(opts)) {}
+
+ /**
+ * Print application usage message
+ */
+ void parser::usage() const {
+ printf("%s\n\n", m_synopsis.c_str());
+
+ // get the length of the longest string in the flag column
+ // which is used to align the description fields
+ size_t maxlen{0};
+
+ for (const auto& m_opt : m_opts) {
+ size_t len{m_opt.flag_long.length() + m_opt.flag.length() + 4};
+ maxlen = len > maxlen ? len : maxlen;
+ }
+
+ for (auto& opt : m_opts) {
+ size_t pad = maxlen - opt.flag_long.length() - opt.token.length();
+
+ printf(" %s, %s", opt.flag.c_str(), opt.flag_long.c_str());
+
+ if (!opt.token.empty()) {
+ printf("=%s", opt.token.c_str());
+ pad--;
+ }
+
+ // output the list with accepted values
+ if (!opt.values.empty()) {
+ printf("%*s\n", static_cast<int>(pad + opt.desc.length()), opt.desc.c_str());
+
+ pad = pad + opt.flag_long.length() + opt.token.length() + 7;
+
+ printf("%*c%s is one of: ", static_cast<int>(pad), ' ', opt.token.c_str());
+
+ for (auto& v : opt.values) {
+ printf("%s%s", v.c_str(), v != opt.values.back() ? ", " : "");
+ }
+ } else {
+ printf("%*s", static_cast<int>(pad + opt.desc.length()), opt.desc.c_str());
+ }
+
+ printf("\n");
+ }
+
+ printf("\n");
+ }
+
+ /**
+ * Process input values
+ */
+ void parser::process_input(const vector<string>& values) {
+ for (size_t i = 0; i < values.size(); i++) {
+ parse(values[i], values.size() > i + 1 ? values[i + 1] : "");
+ }
+ }
+
+ /**
+ * Test if the passed option was provided
+ */
+ bool parser::has(const string& option) const {
+ return m_optvalues.find(option) != m_optvalues.end();
+ }
+
+ /**
+ * Test if a positional argument is defined at given index
+ */
+ bool parser::has(size_t index) const {
+ return m_posargs.size() > index;
+ }
+
+ /**
+ * Get the value defined for given option
+ */
+ string parser::get(string opt) const {
+ if (has(forward<string>(opt))) {
+ return m_optvalues.find(opt)->second;
+ }
+ return "";
+ }
+
+ /**
+ * Get the positional argument at given index
+ */
+ string parser::get(size_t index) const {
+ return has(index) ? m_posargs[index] : "";
+ }
+
+ /**
+ * Compare option value with given string
+ */
+ bool parser::compare(string opt, const string& val) const {
+ return get(move(opt)) == val;
+ }
+
+ /**
+ * Compare positional argument at given index with given string
+ */
+ bool parser::compare(size_t index, const string& val) const {
+ return get(index) == val;
+ }
+
+ /**
+ * Compare option with its short version
+ */
+ auto parser::is_short(const string& option, const string& opt_short) const {
+ return option.compare(0, opt_short.length(), opt_short) == 0;
+ }
+
+ /**
+ * Compare option with its long version
+ */
+ auto parser::is_long(const string& option, const string& opt_long) const {
+ return option.compare(0, opt_long.length(), opt_long) == 0;
+ }
+
+ /**
+ * Compare option with both versions
+ */
+ auto parser::is(const string& option, string opt_short, string opt_long) const {
+ return is_short(option, move(opt_short)) || is_long(option, move(opt_long));
+ }
+
+ /**
+ * Parse option value
+ */
+ auto parser::parse_value(string input, const string& input_next, choices values) const {
+ string opt = move(input);
+ size_t pos;
+ string value;
+
+ if (input_next.empty() && opt.compare(0, 2, "--") != 0) {
+ throw value_error("Missing argument for option " + opt);
+ } else if ((pos = opt.find('=')) == string::npos && opt.compare(0, 2, "--") == 0) {
+ throw value_error("Missing argument for option " + opt);
+ } else if (pos == string::npos && !input_next.empty()) {
+ value = input_next;
+ } else {
+ value = opt.substr(pos + 1);
+ opt = opt.substr(0, pos);
+ }
+
+ if (!values.empty() && std::find(values.begin(), values.end(), value) == values.end()) {
+ throw value_error("Invalid argument for option " + opt);
+ }
+
+ return value;
+ }
+
+ /**
+ * Parse and validate passed arguments and flags
+ */
+ void parser::parse(const string& input, const string& input_next) {
+ auto skipped = m_skipnext;
+ if (m_skipnext) {
+ m_skipnext = false;
+ if (!input_next.empty()) {
+ return;
+ }
+ }
+
+ for (auto&& opt : m_opts) {
+ if (is(input, opt.flag, opt.flag_long)) {
+ if (opt.token.empty()) {
+ m_optvalues.insert(make_pair(opt.flag_long.substr(2), ""));
+ } else {
+ auto value = parse_value(input, input_next, opt.values);
+ m_skipnext = (value == input_next);
+ m_optvalues.insert(make_pair(opt.flag_long.substr(2), value));
+ }
+ return;
+ }
+ }
+
+ if (skipped) {
+ return;
+ } else if (input[0] != '-') {
+ m_posargs.emplace_back(input);
+ } else {
+ throw argument_error("Unrecognized option " + input);
+ }
+ }
+} // namespace command_line
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/config.hpp"
+
+#include <climits>
+#include <cmath>
+#include <fstream>
+
+#include "cairo/utils.hpp"
+#include "components/types.hpp"
+#include "utils/color.hpp"
+#include "utils/env.hpp"
+#include "utils/factory.hpp"
+#include "utils/string.hpp"
+#include "utils/units.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+/**
+ * Get path of loaded file
+ */
+const string& config::filepath() const {
+ return m_file;
+}
+
+/**
+ * Get the section name of the bar in use
+ */
+string config::section() const {
+ return BAR_PREFIX + m_barname;
+}
+
+void config::use_xrm() {
+#if WITH_XRM
+ /*
+ * Initialize the xresource manager if there are any xrdb refs
+ * present in the configuration
+ */
+ if (!m_xrm) {
+ m_log.info("Enabling xresource manager");
+ m_xrm.reset(new xresource_manager{connection::make()});
+ }
+#endif
+}
+
+void config::set_sections(sectionmap_t sections) {
+ m_sections = move(sections);
+ copy_inherited();
+}
+
+void config::set_included(file_list included) {
+ m_included = move(included);
+}
+
+file_list config::get_included_files() const {
+ return m_included;
+}
+
+/**
+ * Print a deprecation warning if the given parameter is set
+ */
+void config::warn_deprecated(const string& section, const string& key, string replacement) const {
+ if (has(section, key)) {
+ if (replacement.empty()) {
+ m_log.warn(
+ "The config parameter '%s.%s' is deprecated, it will be removed in the future. Please remove it from your "
+ "config",
+ section, key);
+ } else {
+ m_log.warn(
+ "The config parameter `%s.%s` is deprecated, use `%s.%s` instead.", section, key, section, move(replacement));
+ }
+ }
+}
+
+/**
+ * Returns true if a given parameter exists
+ */
+bool config::has(const string& section, const string& key) const {
+ auto it = m_sections.find(section);
+ return it != m_sections.end() && it->second.find(key) != it->second.end();
+}
+
+/**
+ * Set parameter value
+ */
+void config::set(const string& section, const string& key, string&& value) {
+ auto it = m_sections.find(section);
+ if (it == m_sections.end()) {
+ valuemap_t values;
+ values[key] = value;
+ m_sections[section] = move(values);
+ }
+ auto it2 = it->second.find(key);
+ if ((it2 = it->second.find(key)) == it->second.end()) {
+ it2 = it->second.emplace_hint(it2, key, value);
+ } else {
+ it2->second = value;
+ }
+}
+
+/**
+ * Look for sections set up to inherit from a base section
+ * and copy the missing parameters
+ *
+ * Multiple sections can be specified, separated by a space.
+ *
+ * [sub/section]
+ * inherit = section1 section2
+ */
+void config::copy_inherited() {
+ for (auto&& section : m_sections) {
+ std::vector<string> inherit_sections;
+
+ // Collect all sections to be inherited
+ for (auto&& param : section.second) {
+ string key_name = param.first;
+ if (key_name == "inherit") {
+ auto inherit = param.second;
+ inherit = dereference(section.first, key_name, inherit);
+
+ std::vector<string> sections = string_util::split(std::move(inherit), ' ');
+
+ inherit_sections.insert(inherit_sections.end(), sections.begin(), sections.end());
+
+ } else if (key_name.find("inherit") == 0) {
+ // Legacy support for keys that just start with 'inherit'
+ m_log.warn(
+ "\"%s.%s\": Using anything other than 'inherit' for inheriting section keys is deprecated. "
+ "The 'inherit' key supports multiple section names separated by a space.",
+ section.first, key_name);
+
+ auto inherit = param.second;
+ inherit = dereference(section.first, key_name, inherit);
+ if (inherit.empty() || m_sections.find(inherit) == m_sections.end()) {
+ throw value_error(
+ "Invalid section \"" + inherit + "\" defined for \"" + section.first + "." + key_name + "\"");
+ }
+
+ inherit_sections.push_back(std::move(inherit));
+ }
+ }
+
+ for (const auto& base_name : inherit_sections) {
+ const auto base_section = m_sections.find(base_name);
+ if (base_section == m_sections.end()) {
+ throw value_error("Invalid section \"" + base_name + "\" defined for \"" + section.first + ".inherit\"");
+ }
+
+ m_log.trace("config: Inheriting keys from \"%s\" in \"%s\"", base_name, section.first);
+
+ /*
+ * Iterate the base and copy the parameters that haven't been defined
+ * yet.
+ */
+ for (auto&& base_param : base_section->second) {
+ section.second.emplace(base_param.first, base_param.second);
+ }
+ }
+ }
+}
+
+/**
+ * Dereference value reference
+ */
+string config::dereference(const string& section, const string& key, const string& var) const {
+ if (var.substr(0, 2) != "${" || var.substr(var.length() - 1) != "}") {
+ return var;
+ }
+
+ auto path = var.substr(2, var.length() - 3);
+ size_t pos;
+
+ if (path.compare(0, 4, "env:") == 0) {
+ return dereference_env(path.substr(4));
+ } else if (path.compare(0, 5, "xrdb:") == 0) {
+ return dereference_xrdb(path.substr(5));
+ } else if (path.compare(0, 5, "file:") == 0) {
+ return dereference_file(path.substr(5));
+ } else if ((pos = path.find(".")) != string::npos) {
+ return dereference_local(path.substr(0, pos), path.substr(pos + 1), section);
+ } else {
+ throw value_error("Invalid reference defined at \"" + section + "." + key + "\"");
+ }
+}
+
+/**
+ * Dereference local value reference defined using:
+ * ${root.key}
+ * ${root.key:fallback}
+ * ${self.key}
+ * ${self.key:fallback}
+ * ${section.key}
+ * ${section.key:fallback}
+ */
+string config::dereference_local(string section, const string& key, const string& current_section) const {
+ if (section == "BAR") {
+ m_log.warn("${BAR.key} is deprecated. Use ${root.key} instead");
+ }
+
+ section = string_util::replace(section, "BAR", this->section(), 0, 3);
+ section = string_util::replace(section, "root", this->section(), 0, 4);
+ section = string_util::replace(section, "self", current_section, 0, 4);
+
+ try {
+ string string_value{get<string>(section, key)};
+ return dereference(string(section), move(key), move(string_value));
+ } catch (const key_error& err) {
+ size_t pos;
+ if ((pos = key.find(':')) != string::npos) {
+ string fallback = key.substr(pos + 1);
+ m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section,
+ key.substr(0, pos), fallback);
+ return fallback;
+ }
+ throw value_error("The reference ${" + section + "." + key + "} does not exist (no fallback set)");
+ }
+}
+
+/**
+ * Dereference environment variable reference defined using:
+ * ${env:key}
+ * ${env:key:fallback value}
+ */
+string config::dereference_env(string var) const {
+ size_t pos;
+ string env_default;
+ /*
+ * This is needed because with only the string we cannot distinguish
+ * between an empty string as default and not default
+ */
+ bool has_default = false;
+
+ if ((pos = var.find(':')) != string::npos) {
+ env_default = var.substr(pos + 1);
+ has_default = true;
+ var.erase(pos);
+ }
+
+ if (env_util::has(var)) {
+ string env_value{env_util::get(var)};
+ m_log.info("Environment var reference ${%s} found (value=%s)", var, env_value);
+ return env_value;
+ } else if (has_default) {
+ m_log.info("Environment var ${%s} is undefined, using defined fallback value \"%s\"", var, env_default);
+ return env_default;
+ } else {
+ throw value_error(sstream() << "Environment var ${" << var << "} does not exist (no fallback set)");
+ }
+}
+
+/**
+ * Dereference X resource db value defined using:
+ * ${xrdb:key}
+ * ${xrdb:key:fallback value}
+ */
+string config::dereference_xrdb(string var) const {
+ size_t pos;
+#if not WITH_XRM
+ m_log.warn("No built-in support to dereference ${xrdb:%s} references (requires `xcb-util-xrm`)", var);
+ if ((pos = var.find(':')) != string::npos) {
+ return var.substr(pos + 1);
+ }
+ return "";
+#else
+ if (!m_xrm) {
+ throw application_error("xrm is not initialized");
+ }
+
+ string fallback;
+ bool has_fallback = false;
+ if ((pos = var.find(':')) != string::npos) {
+ fallback = var.substr(pos + 1);
+ has_fallback = true;
+ var.erase(pos);
+ }
+
+ try {
+ auto value = m_xrm->require<string>(var.c_str());
+ m_log.info("Found matching X resource \"%s\" (value=%s)", var, value);
+ return value;
+ } catch (const xresource_error& err) {
+ if (has_fallback) {
+ m_log.info("%s, using defined fallback value \"%s\"", err.what(), fallback);
+ return fallback;
+ }
+ throw value_error(sstream() << err.what() << " (no fallback set)");
+ }
+#endif
+}
+
+/**
+ * Dereference file reference by reading its contents
+ * ${file:/absolute/file/path}
+ * ${file:/absolute/file/path:fallback value}
+ */
+string config::dereference_file(string var) const {
+ size_t pos;
+ string fallback;
+ bool has_fallback = false;
+ if ((pos = var.find(':')) != string::npos) {
+ fallback = var.substr(pos + 1);
+ has_fallback = true;
+ var.erase(pos);
+ }
+ var = file_util::expand(var);
+
+ if (file_util::exists(var)) {
+ m_log.info("File reference \"%s\" found", var);
+ return string_util::trim(file_util::contents(var), '\n');
+ } else if (has_fallback) {
+ m_log.info("File reference \"%s\" not found, using defined fallback value \"%s\"", var, fallback);
+ return fallback;
+ } else {
+ throw value_error(sstream() << "The file \"" << var << "\" does not exist (no fallback set)");
+ }
+}
+
+
+template <>
+string config::convert(string&& value) const {
+ return forward<string>(value);
+}
+
+template <>
+const char* config::convert(string&& value) const {
+ return value.c_str();
+}
+
+template <>
+char config::convert(string&& value) const {
+ return value.c_str()[0];
+}
+
+template <>
+int config::convert(string&& value) const {
+ return std::strtol(value.c_str(), nullptr, 10);
+}
+
+template <>
+short config::convert(string&& value) const {
+ return static_cast<short>(std::strtol(value.c_str(), nullptr, 10));
+}
+
+template <>
+bool config::convert(string&& value) const {
+ string lower{string_util::lower(forward<string>(value))};
+
+ return (lower == "true" || lower == "yes" || lower == "on" || lower == "1");
+}
+
+template <>
+float config::convert(string&& value) const {
+ return std::strtof(value.c_str(), nullptr);
+}
+
+template <>
+double config::convert(string&& value) const {
+ return std::strtod(value.c_str(), nullptr);
+}
+
+template <>
+long config::convert(string&& value) const {
+ return std::strtol(value.c_str(), nullptr, 10);
+}
+
+template <>
+long long config::convert(string&& value) const {
+ return std::strtoll(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned char config::convert(string&& value) const {
+ return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned short config::convert(string&& value) const {
+ return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned int config::convert(string&& value) const {
+ return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned long config::convert(string&& value) const {
+ unsigned long v{std::strtoul(value.c_str(), nullptr, 10)};
+ return v < ULONG_MAX ? v : 0UL;
+}
+
+template <>
+unsigned long long config::convert(string&& value) const {
+ unsigned long long v{std::strtoull(value.c_str(), nullptr, 10)};
+ return v < ULLONG_MAX ? v : 0ULL;
+}
+
+template <>
+spacing_val config::convert(string&& value) const {
+ return units_utils::parse_spacing(value);
+}
+
+template <>
+extent_val config::convert(std::string&& value) const {
+ return units_utils::parse_extent(value);
+}
+
+/**
+ * Allows a new format for pixel sizes (like width in the bar section)
+ *
+ * The new format is X%:Z, where X is in [0, 100], and Z is any real value
+ * describing a pixel offset. The actual value is calculated by X% * max + Z
+ */
+template <>
+percentage_with_offset config::convert(string&& value) const {
+ size_t i = value.find(':');
+
+ if (i == std::string::npos) {
+ if (value.find('%') != std::string::npos) {
+ return {std::stod(value), {}};
+ } else {
+ return {0., convert<extent_val>(move(value))};
+ }
+ } else {
+ std::string percentage = value.substr(0, i - 1);
+ return {std::stod(percentage), convert<extent_val>(value.substr(i + 1))};
+ }
+}
+
+template <>
+chrono::seconds config::convert(string&& value) const {
+ return chrono::seconds{convert<chrono::seconds::rep>(forward<string>(value))};
+}
+
+template <>
+chrono::milliseconds config::convert(string&& value) const {
+ return chrono::milliseconds{convert<chrono::milliseconds::rep>(forward<string>(value))};
+}
+
+template <>
+chrono::duration<double> config::convert(string&& value) const {
+ return chrono::duration<double>{convert<double>(forward<string>(value))};
+}
+
+template <>
+rgba config::convert(string&& value) const {
+ if (value.empty()) {
+ return rgba{};
+ }
+
+ rgba ret{value};
+
+ if (!ret.has_color()) {
+ throw value_error("\"" + value + "\" is an invalid color value.");
+ }
+
+ return ret;
+}
+
+template <>
+cairo_operator_t config::convert(string&& value) const {
+ return cairo::utils::str2operator(forward<string>(value), CAIRO_OPERATOR_OVER);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/config_parser.hpp"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+config_parser::config_parser(const logger& logger, string&& file)
+ : m_log(logger), m_config_file(file_util::expand(file)) {}
+
+config config_parser::parse(string barname) {
+ m_log.notice("Parsing config file: %s", m_config_file);
+
+ parse_file(m_config_file, {});
+
+ sectionmap_t sections = create_sectionmap();
+
+ vector<string> bars = get_bars(sections);
+ if (barname.empty()) {
+ if (bars.size() == 1) {
+ barname = bars[0];
+ } else if (bars.empty()) {
+ throw application_error("The config file contains no bar.");
+ } else {
+ throw application_error("The config file contains multiple bars, but no bar name was given. Available bars: " +
+ string_util::join(bars, ", "));
+ }
+ } else if (sections.find("bar/" + barname) == sections.end()) {
+ if (bars.empty()) {
+ throw application_error("Undefined bar: " + barname + ". The config file contains no bar.");
+ } else {
+ throw application_error(
+ "Undefined bar: " + barname + ". Available bars: " + string_util::join(get_bars(sections), ", "));
+ }
+ }
+
+ /*
+ * The first element in the files vector is always the main config file and
+ * because it has unique filenames, we can use all the elements from the
+ * second element onwards for the included list
+ */
+ file_list included(m_files.begin() + 1, m_files.end());
+ config conf(m_log, move(m_config_file), move(barname));
+
+ conf.set_sections(move(sections));
+ conf.set_included(move(included));
+ if (use_xrm) {
+ conf.use_xrm();
+ }
+
+ return conf;
+}
+
+sectionmap_t config_parser::create_sectionmap() {
+ sectionmap_t sections{};
+
+ string current_section{};
+
+ for (const line_t& line : m_lines) {
+ if (!line.useful) {
+ continue;
+ }
+
+ if (line.is_header) {
+ current_section = line.header;
+ } else {
+ // The first valid line in the config is not a section definition
+ if (current_section.empty()) {
+ throw syntax_error("First valid line in config must be section header", m_files[line.file_index], line.line_no);
+ }
+
+ const string& key = line.key;
+ const string& value = line.value;
+
+ valuemap_t& valuemap = sections[current_section];
+
+ if (valuemap.find(key) == valuemap.end()) {
+ valuemap.emplace(key, value);
+ } else {
+ // Key already exists in this section
+ throw syntax_error("Duplicate key name \"" + key + "\" defined in section \"" + current_section + "\"",
+ m_files[line.file_index], line.line_no);
+ }
+ }
+ }
+
+ return sections;
+}
+
+/**
+ * Get the bars declared
+ */
+vector<string> config_parser::get_bars(const sectionmap_t& sections) const {
+ vector<string> bars;
+ for (const auto& it : sections) {
+ if (it.first.find(config::BAR_PREFIX) == 0) {
+ bars.push_back(it.first.substr(strlen(config::BAR_PREFIX)));
+ }
+ }
+ return bars;
+}
+
+void config_parser::parse_file(const string& file, file_list path) {
+ if (std::find(path.begin(), path.end(), file) != path.end()) {
+ string path_str{};
+
+ for (const auto& p : path) {
+ path_str += ">\t" + p + "\n";
+ }
+
+ path_str += ">\t" + file;
+
+ // We have already parsed this file in this path, so there are cyclic dependencies
+ throw application_error("include-file: Dependency cycle detected:\n" + path_str);
+ }
+
+ if (!file_util::exists(file)) {
+ throw application_error("Failed to open config file " + file + ": " + strerror(errno));
+ }
+
+ if (file_util::is_dir(file)) {
+ throw application_error("Config file " + file + " is a directory");
+ }
+
+ m_log.trace("config_parser: Parsing %s", file);
+
+ int file_index;
+
+ auto found = std::find(m_files.begin(), m_files.end(), file);
+
+ if (found == m_files.end()) {
+ file_index = m_files.size();
+ m_files.push_back(file);
+ } else {
+ /*
+ * `file` is already in the `files` vector so we calculate its index.
+ *
+ * This means that the file was already parsed, this can happen without
+ * cyclic dependencies, if the file is included twice
+ */
+ file_index = found - m_files.begin();
+ }
+
+ path.push_back(file);
+
+ int line_no = 0;
+
+ string line_str{};
+
+ std::ifstream in(file);
+
+ if (!in) {
+ throw application_error("Failed to open config file " + file + ": " + strerror(errno));
+ }
+
+ auto dirname = file_util::dirname(file);
+
+ while (std::getline(in, line_str)) {
+ line_no++;
+ line_t line;
+ line.file_index = file_index;
+ line.line_no = line_no;
+ parse_line(line, line_str);
+
+ // Skip useless lines (comments, empty lines)
+ if (!line.useful) {
+ continue;
+ }
+
+ if (!line.is_header && line.key == "include-file") {
+ parse_file(file_util::expand(line.value, dirname), path);
+ } else if (!line.is_header && line.key == "include-directory") {
+ const string expanded_path = file_util::expand(line.value, dirname);
+ vector<string> file_list = file_util::list_files(expanded_path);
+ sort(file_list.begin(), file_list.end());
+ for (const auto& filename : file_list) {
+ parse_file(expanded_path + "/" + filename, path);
+ }
+ } else {
+ m_lines.push_back(line);
+ }
+ }
+}
+
+void config_parser::parse_line(line_t& line, const string& line_str) {
+ if (string_util::contains(line_str, "\ufeff")) {
+ throw syntax_error(
+ "This config file uses UTF-8 with BOM, which is not supported. Please use plain UTF-8 without BOM.",
+ m_files[line.file_index], line.line_no);
+ }
+
+ string line_trimmed = string_util::trim(line_str, isspace);
+ line_type type = get_line_type(line_trimmed);
+
+ if (type == line_type::EMPTY || type == line_type::COMMENT) {
+ line.useful = false;
+ return;
+ }
+
+ if (type == line_type::UNKNOWN) {
+ throw syntax_error("Unknown line type: " + line_trimmed, m_files[line.file_index], line.line_no);
+ }
+
+ line.useful = true;
+
+ if (type == line_type::HEADER) {
+ line.is_header = true;
+ line.header = parse_header(line, line_trimmed);
+ } else if (type == line_type::KEY) {
+ line.is_header = false;
+ auto key_value = parse_key(line, line_trimmed);
+ line.key = key_value.first;
+ line.value = key_value.second;
+ }
+}
+
+line_type config_parser::get_line_type(const string& line) {
+ if (line.empty()) {
+ return line_type::EMPTY;
+ }
+
+ switch (line[0]) {
+ case '[':
+ return line_type::HEADER;
+
+ case ';':
+ case '#':
+ return line_type::COMMENT;
+
+ default: {
+ if (string_util::contains(line, "=")) {
+ return line_type::KEY;
+ } else {
+ return line_type::UNKNOWN;
+ }
+ }
+ }
+}
+
+string config_parser::parse_header(const line_t& line, const string& line_str) {
+ if (line_str.back() != ']') {
+ throw syntax_error("Missing ']' in header '" + line_str + "'", m_files[line.file_index], line.line_no);
+ }
+
+ // Stripping square brackets
+ string header = line_str.substr(1, line_str.size() - 2);
+
+ if (!is_valid_name(header)) {
+ throw invalid_name_error("Section", header, m_files[line.file_index], line.line_no);
+ }
+
+ if (m_reserved_section_names.find(header) != m_reserved_section_names.end()) {
+ throw syntax_error(
+ "'" + header + "' is reserved and cannot be used as a section name", m_files[line.file_index], line.line_no);
+ }
+
+ return header;
+}
+
+std::pair<string, string> config_parser::parse_key(const line_t& line, const string& line_str) {
+ size_t pos = line_str.find_first_of('=');
+
+ string key = string_util::trim(line_str.substr(0, pos), isspace);
+ string value = string_util::trim(line_str.substr(pos + 1), isspace);
+
+ if (!is_valid_name(key)) {
+ throw invalid_name_error("Key", key, m_files[line.file_index], line.line_no);
+ }
+
+ value = parse_escaped_value(line, move(value), key);
+
+ /*
+ * Only if the string is surrounded with double quotes, do we treat them
+ * not as part of the value and remove them.
+ */
+ if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
+ value = value.substr(1, value.size() - 2);
+ }
+
+ // TODO check value for references
+
+#if WITH_XRM
+ // Use xrm, if at least one value is an xrdb reference
+ if (!use_xrm && value.find("${xrdb") == 0) {
+ use_xrm = true;
+ }
+#endif
+
+ return {move(key), move(value)};
+}
+
+bool config_parser::is_valid_name(const string& name) {
+ if (name.empty()) {
+ return false;
+ }
+
+ for (const char c : name) {
+ // Names with forbidden chars or spaces are not valid
+ if (isspace(c) || m_forbidden_chars.find_first_of(c) != string::npos) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+string config_parser::parse_escaped_value(const line_t& line, string&& value, const string& key) {
+ string cfg_value = value;
+ bool log = false;
+ auto backslash_pos = value.find('\\');
+ while (backslash_pos != string::npos) {
+ if (backslash_pos == value.size() - 1 || value[backslash_pos + 1] != '\\') {
+ log = true;
+ } else {
+ value = value.replace(backslash_pos, 2, "\\");
+ }
+ backslash_pos = value.find('\\', backslash_pos + 1);
+ }
+ if (log) {
+ m_log.err(
+ "%s:%d: Value '%s' of key '%s' contains one or more unescaped backslashes, please prepend them with the "
+ "backslash "
+ "escape character.",
+ m_files[line.file_index], line.line_no, cfg_value, key);
+ }
+ return move(value);
+}
+POLYBAR_NS_END
--- /dev/null
+#include "components/controller.hpp"
+
+#include <csignal>
+#include <cassert>
+#include <utility>
+
+#include "components/bar.hpp"
+#include "components/builder.hpp"
+#include "components/config.hpp"
+#include "components/eventloop.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "modules/meta/all.hpp"
+#include "modules/meta/base.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/factory.hpp"
+#include "utils/actions.hpp"
+#include "utils/inotify.hpp"
+#include "utils/process.hpp"
+#include "utils/string.hpp"
+#include "utils/time.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+
+POLYBAR_NS
+
+using namespace eventloop;
+using namespace modules;
+
+/**
+ * Build controller instance
+ */
+controller::make_type controller::make(bool has_ipc, loop& loop, const config& config) {
+ return std::make_unique<controller>(
+ connection::make(), signal_emitter::make(), logger::make(), config, has_ipc, loop);
+}
+
+/**
+ * Construct controller
+ */
+controller::controller(
+ connection& conn, signal_emitter& emitter, const logger& logger, const config& config, bool has_ipc, loop& loop)
+ : m_connection(conn)
+ , m_sig(emitter)
+ , m_log(logger)
+ , m_conf(config)
+ , m_loop(loop)
+ , m_bar(bar::make(m_loop, config))
+ , m_has_ipc(has_ipc) {
+ m_conf.warn_deprecated("settings", "throttle-input-for");
+ m_conf.warn_deprecated("settings", "throttle-output");
+ m_conf.warn_deprecated("settings", "throttle-output-for");
+ m_conf.warn_deprecated("settings", "eventqueue-swallow");
+ m_conf.warn_deprecated("settings", "eventqueue-swallow-time");
+
+ m_log.trace("controller: Setup user-defined modules");
+ size_t created_modules{0};
+ created_modules += setup_modules(alignment::LEFT);
+ created_modules += setup_modules(alignment::CENTER);
+ created_modules += setup_modules(alignment::RIGHT);
+
+ if (!created_modules) {
+ throw application_error("No modules created");
+ }
+
+ m_log.notice("Loaded %zd modules", created_modules);
+}
+
+/**
+ * Deconstruct controller
+ */
+controller::~controller() {
+ m_log.trace("controller: Detach signal receiver");
+ m_sig.detach(this);
+
+ m_log.trace("controller: Stop modules");
+ for (auto&& module : m_modules) {
+ auto module_name = module->name();
+ auto cleanup_ms = time_util::measure([&module] {
+ module->stop();
+ module->join();
+ module.reset();
+ });
+ m_log.info("Deconstruction of %s took %lu ms.", module_name, cleanup_ms);
+ }
+}
+
+/**
+ * Run the main loop
+ */
+bool controller::run(bool writeback, string snapshot_dst, bool confwatch) {
+ m_log.info("Starting application");
+ m_log.trace("controller: Main thread id = %i", concurrency_util::thread_id(this_thread::get_id()));
+
+ assert(!m_connection.connection_has_error());
+
+ m_writeback = writeback;
+ m_snapshot_dst = move(snapshot_dst);
+
+ m_sig.attach(this);
+
+ m_connection.flush();
+
+ read_events(confwatch);
+
+ m_log.notice("Termination signal received, shutting down...");
+
+ return !m_reload;
+}
+
+/**
+ * Enqueue input data
+ */
+void controller::trigger_action(string&& input_data) {
+ std::unique_lock<std::mutex> guard(m_notification_mutex);
+ m_log.trace("controller: Queueing input event '%s'", input_data);
+ m_notifications.inputdata.push(std::move(input_data));
+ trigger_notification();
+}
+
+void controller::trigger_quit(bool reload) {
+ std::unique_lock<std::mutex> guard(m_notification_mutex);
+ m_notifications.quit = true;
+ m_notifications.reload = m_notifications.reload || reload;
+ trigger_notification();
+}
+
+void controller::trigger_update(bool force) {
+ std::unique_lock<std::mutex> guard(m_notification_mutex);
+ m_notifications.update = true;
+ m_notifications.force_update = m_notifications.force_update || force;
+
+ trigger_notification();
+}
+
+void controller::trigger_notification() {
+ m_notifier->send();
+}
+
+void controller::stop(bool reload) {
+ update_reload(reload);
+ m_loop.stop();
+}
+
+void controller::conn_cb() {
+ int xcb_error = m_connection.connection_has_error();
+ if ((xcb_error = m_connection.connection_has_error()) != 0) {
+ m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(xcb_error));
+ stop(false);
+ return;
+ }
+
+ shared_ptr<xcb_generic_event_t> evt{};
+ while ((evt = m_connection.poll_for_event()) != nullptr) {
+ try {
+ m_connection.dispatch_event(evt);
+ } catch (xpp::connection_error& err) {
+ m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code()));
+ stop(false);
+ } catch (const exception& err) {
+ // IDs for events are defined in xproto.h
+ m_log.err("Error in X event loop while handling event %d: %s", evt->response_type, err.what());
+ }
+ }
+
+ if ((xcb_error = m_connection.connection_has_error()) != 0) {
+ m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(xcb_error));
+ stop(false);
+ return;
+ }
+}
+
+void controller::signal_handler(int signum) {
+ m_log.notice("Received signal(%d): %s", signum, strsignal(signum));
+ stop(signum == SIGUSR1);
+}
+
+void controller::create_config_watcher(const string& filename) {
+ auto fs_event_handle = m_loop.handle<FSEventHandle>();
+ fs_event_handle->start(
+ filename, 0, [this](const auto& e) { confwatch_handler(e.path); },
+ [this, &handle = *fs_event_handle](const auto& e) {
+ m_log.err("libuv error while watching included file for changes: %s", uv_strerror(e.status));
+ handle.close();
+ });
+}
+
+void controller::confwatch_handler(const char* filename) {
+ m_log.notice("Watched config file changed %s", filename);
+ stop(true);
+}
+
+void controller::notifier_handler() {
+ notifications_t data{};
+
+ {
+ std::unique_lock<std::mutex> guard(m_notification_mutex);
+ std::swap(m_notifications, data);
+ }
+
+ if (data.quit) {
+ stop(data.reload);
+ return;
+ }
+
+ while (!data.inputdata.empty()) {
+ auto inputdata = data.inputdata.front();
+ data.inputdata.pop();
+ m_log.trace("controller: Dequeueing inputdata: '%s'", inputdata);
+ process_inputdata(std::move(inputdata));
+ }
+
+ if (data.update) {
+ process_update(data.force_update);
+ }
+}
+
+void controller::screenshot_handler() {
+ m_sig.emit(signals::ui::request_snapshot{move(m_snapshot_dst)});
+ trigger_update(true);
+}
+
+void controller::start_modules() {
+ size_t started_modules{0};
+ for (const auto& module : m_modules) {
+ auto evt_handler = dynamic_cast<event_handler_interface*>(&*module);
+
+ if (evt_handler != nullptr) {
+ evt_handler->connect(m_connection);
+ }
+
+ try {
+ m_log.info("Starting %s", module->name());
+ module->start();
+ started_modules++;
+ } catch (const application_error& err) {
+ m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what());
+ }
+ }
+
+ if (!started_modules) {
+ throw application_error("No modules started");
+ }
+}
+
+/**
+ * Read events from configured file descriptors
+ */
+void controller::read_events(bool confwatch) {
+ m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id());
+
+ if (!m_writeback) {
+ m_bar->start(m_tray_module_name);
+ }
+
+ start_modules();
+
+ auto x_poll_handle = m_loop.handle<PollHandle>(m_connection.get_file_descriptor());
+ x_poll_handle->start(
+ UV_READABLE, [this](const auto&) { conn_cb(); },
+ [this](const auto& e) {
+ m_log.err("libuv error while polling X connection: "s + uv_strerror(e.status));
+ stop(false);
+ });
+
+ auto x_prepare_handle = m_loop.handle<PrepareHandle>();
+ x_prepare_handle->start([this]() {
+ /*
+ * We have to also handle events in the prepare handle (which runs right
+ * before polling for IO) to process any already queued X events which
+ * wouldn't trigger the uv_poll handle.
+ */
+ conn_cb();
+ m_connection.flush();
+ });
+
+ for (auto s : {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGALRM}) {
+ auto signal_handle = m_loop.handle<SignalHandle>();
+ signal_handle->start(s, [this](const auto& e) { signal_handler(e.signum); });
+ }
+
+ if (confwatch) {
+ create_config_watcher(m_conf.filepath());
+ // also watch the include-files for changes
+ for (auto& module_path : m_conf.get_included_files()) {
+ create_config_watcher(module_path);
+ }
+ }
+
+ if (!m_snapshot_dst.empty()) {
+ // Trigger a single screenshot after 3 seconds
+ auto timer_handle = m_loop.handle<TimerHandle>();
+ timer_handle->start(3000, 0, [this]() { screenshot_handler(); });
+ }
+
+ /*
+ * Immediately trigger and update so that the bar displays something.
+ */
+ trigger_update(true);
+
+ try {
+ m_loop.run();
+ } catch (const exception& err) {
+ m_log.err("Fatal Error in eventloop: %s", err.what());
+ stop(false);
+ }
+
+ m_log.info("Eventloop finished");
+}
+
+/**
+ * Tries to match the given command to a legacy action string and sends the
+ * appropriate new action (and data) to the right module if possible.
+ *
+ * @returns true iff the given command matches a legacy action string and was
+ * successfully forwarded to a module
+ */
+bool controller::try_forward_legacy_action(const string& cmd) {
+ /*
+ * Maps legacy action names to a module type and the new action name in that module.
+ *
+ * We try to match the old action name as a prefix, and everything after it will also be added to the end of the new
+ * action string (for example "mpdseek+5" will be redirected to "seek.+5" in the first mpd module).
+ *
+ * The action will be delivered to the first module of that type so that it is consistent with existing behavior.
+ * If the module does not support the action or no matching module is found, the command is forwarded to the shell.
+ *
+ * TODO Remove when deprecated action names are removed
+ */
+// clang-format off
+#define A_MAP(old, module_name, event) {old, {string(module_name::TYPE), string(module_name::event)}}
+
+ static const std::unordered_map<string, std::pair<string, const string>> legacy_actions{
+ A_MAP("datetoggle", date_module, EVENT_TOGGLE),
+#if ENABLE_ALSA
+ A_MAP("volup", alsa_module, EVENT_INC),
+ A_MAP("voldown", alsa_module, EVENT_DEC),
+ A_MAP("volmute", alsa_module, EVENT_TOGGLE),
+#endif
+#if ENABLE_PULSEAUDIO
+ A_MAP("pa_volup", pulseaudio_module, EVENT_INC),
+ A_MAP("pa_voldown", pulseaudio_module, EVENT_DEC),
+ A_MAP("pa_volmute", pulseaudio_module, EVENT_TOGGLE),
+#endif
+ A_MAP("xbacklight+", xbacklight_module, EVENT_INC),
+ A_MAP("xbacklight-", xbacklight_module, EVENT_DEC),
+ A_MAP("backlight+", backlight_module, EVENT_INC),
+ A_MAP("backlight-", backlight_module, EVENT_DEC),
+#if ENABLE_XKEYBOARD
+ A_MAP("xkeyboard/switch", xkeyboard_module, EVENT_SWITCH),
+#endif
+#if ENABLE_MPD
+ A_MAP("mpdplay", mpd_module, EVENT_PLAY),
+ A_MAP("mpdpause", mpd_module, EVENT_PAUSE),
+ A_MAP("mpdstop", mpd_module, EVENT_STOP),
+ A_MAP("mpdprev", mpd_module, EVENT_PREV),
+ A_MAP("mpdnext", mpd_module, EVENT_NEXT),
+ A_MAP("mpdrepeat", mpd_module, EVENT_REPEAT),
+ A_MAP("mpdsingle", mpd_module, EVENT_SINGLE),
+ A_MAP("mpdrandom", mpd_module, EVENT_RANDOM),
+ A_MAP("mpdconsume", mpd_module, EVENT_CONSUME),
+ // Has data
+ A_MAP("mpdseek", mpd_module, EVENT_SEEK),
+#endif
+ // Has data
+ A_MAP("xworkspaces-focus=", xworkspaces_module, EVENT_FOCUS),
+ A_MAP("xworkspaces-next", xworkspaces_module, EVENT_NEXT),
+ A_MAP("xworkspaces-prev", xworkspaces_module, EVENT_PREV),
+ // Has data
+ A_MAP("bspwm-deskfocus", bspwm_module, EVENT_FOCUS),
+ A_MAP("bspwm-desknext", bspwm_module, EVENT_NEXT),
+ A_MAP("bspwm-deskprev", bspwm_module, EVENT_PREV),
+#if ENABLE_I3
+ // Has data
+ A_MAP("i3wm-wsfocus-", i3_module, EVENT_FOCUS),
+ A_MAP("i3wm-wsnext", i3_module, EVENT_NEXT),
+ A_MAP("i3wm-wsprev", i3_module, EVENT_PREV),
+#endif
+ // Has data
+ A_MAP("menu-open-", menu_module, EVENT_OPEN),
+ A_MAP("menu-close", menu_module, EVENT_CLOSE),
+ };
+#undef A_MAP
+ // clang-format on
+
+ // Check if any key in the map is a prefix for the `cmd`
+ for (const auto& entry : legacy_actions) {
+ const auto& key = entry.first;
+ if (cmd.compare(0, key.length(), key) == 0) {
+ string type = entry.second.first;
+ auto data = cmd.substr(key.length());
+ string action = entry.second.second;
+
+ // Search for the first module that matches the type for this legacy action
+ for (auto&& module : m_modules) {
+ if (module->type() == type) {
+ auto module_name = module->name_raw();
+ if (data.empty()) {
+ m_log.warn("The action '%s' is deprecated, use '#%s.%s' instead!", cmd, module_name, action);
+ } else {
+ m_log.warn("The action '%s' is deprecated, use '#%s.%s.%s' instead!", cmd, module_name, action, data);
+ }
+ m_log.warn("Consult the 'Actions' page in the polybar documentation for more information.");
+ m_log.info(
+ "Forwarding legacy action '%s' to module '%s' as '%s' with data '%s'", cmd, module_name, action, data);
+ if (!module->input(action, data)) {
+ m_log.err("Failed to forward deprecated action to %s module", type);
+ // Forward to shell if the module cannot accept the action to not break existing behavior.
+ return false;
+ }
+ // Only deliver to the first matching module.
+ return true;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we couldn't find any matching legacy action, we return false and let
+ * the command be forwarded to the shell
+ */
+ return false;
+}
+
+bool controller::forward_action(const actions_util::action& action_triple) {
+ string module_name = std::get<0>(action_triple);
+ string action = std::get<1>(action_triple);
+ string data = std::get<2>(action_triple);
+
+ m_log.info("Forwarding action to modules (module: '%s', action: '%s', data: '%s')", module_name, action, data);
+
+ int num_delivered = 0;
+
+ // Forwards the action to all modules that match the name
+ for (auto&& module : m_modules) {
+ if (module->name_raw() == module_name) {
+ if (!module->input(action, data)) {
+ m_log.err("The '%s' module does not support the '%s' action.", module_name, action);
+ }
+
+ num_delivered++;
+ }
+ }
+
+ if (num_delivered == 0) {
+ m_log.err("Could not forward action to module: No module named '%s' (action: '%s', data: '%s')", module_name,
+ action, data);
+ } else {
+ m_log.info("Delivered action to %d module%s", num_delivered, num_delivered > 1 ? "s" : "");
+ }
+ return true;
+}
+
+/**
+ * Process stored input data
+ */
+void controller::process_inputdata(string&& cmd) {
+ m_log.trace("controller: Processing inputdata: %s", cmd);
+
+ // Every command that starts with '#' is considered an action string.
+ if (cmd.front() == '#') {
+ try {
+ this->forward_action(actions_util::parse_action_string(cmd));
+ } catch (runtime_error& e) {
+ m_log.err("Invalid action string (action: %s, reason: %s)", cmd, e.what());
+ }
+
+ return;
+ }
+
+ if (this->try_forward_legacy_action(cmd)) {
+ return;
+ }
+
+ try {
+ // Run input as command if it's not an input for a module
+ m_log.info("Forwarding command to shell... (input: %s)", cmd);
+ m_log.info("Executing shell command: %s", cmd);
+ process_util::fork_detached([cmd] { process_util::exec_sh(cmd.c_str()); });
+ process_update(true);
+ } catch (const application_error& err) {
+ m_log.err("controller: Error while forwarding input to shell -> %s", err.what());
+ }
+}
+
+/**
+ * Process eventqueue update event
+ */
+bool controller::process_update(bool force) {
+ const bar_settings& bar{m_bar->settings()};
+ string contents;
+ string padding_left = builder::get_spacing_format_string(bar.padding.left);
+ string padding_right = builder::get_spacing_format_string(bar.padding.right);
+ string margin_left = builder::get_spacing_format_string(bar.module_margin.left);
+ string margin_right = builder::get_spacing_format_string(bar.module_margin.right);
+
+ builder build{bar};
+ build.node(bar.separator);
+ string separator{build.flush()};
+
+ for (const auto& block : m_blocks) {
+ string block_contents;
+ bool is_left = false;
+ bool is_center = false;
+ bool is_right = false;
+ bool is_first = true;
+
+ if (block.first == alignment::LEFT) {
+ is_left = true;
+ } else if (block.first == alignment::CENTER) {
+ is_center = true;
+ } else if (block.first == alignment::RIGHT) {
+ is_right = true;
+ }
+
+ for (const auto& module : block.second) {
+ if (!module->running() || !module->visible()) {
+ continue;
+ }
+
+ string module_contents;
+
+ try {
+ module_contents = module->contents();
+ } catch (const exception& err) {
+ m_log.err("Failed to get contents for \"%s\" (err: %s)", module->name(), err.what());
+ }
+
+ if (module_contents.empty()) {
+ continue;
+ }
+
+ if (!block_contents.empty() && !margin_right.empty()) {
+ block_contents += margin_right;
+ }
+
+ if (!block_contents.empty() && !separator.empty()) {
+ block_contents += separator;
+ }
+
+ if (!block_contents.empty() && !margin_left.empty() && !(is_left && is_first)) {
+ block_contents += margin_left;
+ }
+
+ block_contents.reserve(module_contents.size());
+ block_contents += module_contents;
+
+ is_first = false;
+ }
+
+ if (block_contents.empty()) {
+ continue;
+ } else if (is_left) {
+ contents += "%{l}";
+ contents += padding_left;
+ } else if (is_center) {
+ contents += "%{c}";
+ } else if (is_right) {
+ contents += "%{r}";
+ block_contents += padding_right;
+ }
+
+ contents += block_contents;
+ }
+
+ try {
+ if (!m_writeback) {
+ m_bar->parse(move(contents), force);
+ } else {
+ std::cout << contents << std::endl;
+ }
+ } catch (const exception& err) {
+ m_log.err("Failed to update bar contents (reason: %s)", err.what());
+ }
+
+ return true;
+}
+
+void controller::update_reload(bool reload) {
+ m_reload = m_reload || reload;
+}
+
+/**
+ * Creates module instances for all the modules in the given alignment block
+ */
+size_t controller::setup_modules(alignment align) {
+ string key;
+
+ switch (align) {
+ case alignment::LEFT:
+ key = "modules-left";
+ break;
+
+ case alignment::CENTER:
+ key = "modules-center";
+ break;
+
+ case alignment::RIGHT:
+ key = "modules-right";
+ break;
+
+ case alignment::NONE:
+ m_log.err("controller: Tried to setup modules for alignment NONE");
+ break;
+ }
+
+ string configured_modules;
+ if (!key.empty()) {
+ configured_modules = m_conf.get(m_conf.section(), key, ""s);
+ }
+
+ for (auto& module_name : string_util::split(configured_modules, ' ')) {
+ if (module_name.empty()) {
+ continue;
+ }
+
+ try {
+ auto type = m_conf.get("module/" + module_name, "type");
+
+ if (type == tray_module::TYPE) {
+ if (!m_tray_module_name.empty()) {
+ throw module_error("Multiple trays defined. Using tray `" + m_tray_module_name + "`");
+ }
+ m_tray_module_name = module_name;
+ }
+
+ if (type == ipc_module::TYPE && !m_has_ipc) {
+ throw application_error("Inter-process messaging needs to be enabled");
+ }
+
+ m_log.notice("Loading module '%s' of type '%s'", module_name, type);
+ module_t module = modules::make_module(move(type), m_bar->settings(), module_name, m_log, m_conf);
+
+ m_modules.push_back(module);
+ m_blocks[align].push_back(module);
+ } catch (const std::exception& err) {
+ m_log.err("Disabling module \"%s\" (reason: %s)", module_name, err.what());
+ }
+ }
+
+ return m_blocks[align].size();
+}
+
+/**
+ * Process broadcast events
+ */
+bool controller::on(const signals::eventqueue::notify_change&) {
+ trigger_update(false);
+ return true;
+}
+
+/**
+ * Process forced broadcast events
+ */
+bool controller::on(const signals::eventqueue::notify_forcechange&) {
+ trigger_update(true);
+ return true;
+}
+
+/**
+ * Process eventqueue reload event
+ */
+bool controller::on(const signals::eventqueue::exit_reload&) {
+ trigger_quit(true);
+ return true;
+}
+
+/**
+ * Process eventqueue check event
+ */
+bool controller::on(const signals::eventqueue::check_state&) {
+ for (const auto& module : m_modules) {
+ if (module->running()) {
+ return true;
+ }
+ }
+ m_log.warn("No running modules...");
+ trigger_quit(false);
+ return true;
+}
+
+/**
+ * Process ui button press event
+ */
+bool controller::on(const signals::ui::button_press& evt) {
+ string input{evt.cast()};
+
+ if (input.empty()) {
+ m_log.err("Cannot enqueue empty input");
+ return false;
+ }
+
+ trigger_action(move(input));
+ return true;
+}
+
+/**
+ * Process ipc action messages
+ */
+bool controller::on(const signals::ipc::action& evt) {
+ string action{evt.cast()};
+
+ if (action.empty()) {
+ m_log.err("Cannot enqueue empty ipc action");
+ return false;
+ }
+
+ m_log.info("Enqueuing ipc action: %s", action);
+ trigger_action(move(action));
+ return true;
+}
+
+/**
+ * Process ipc command messages
+ */
+bool controller::on(const signals::ipc::command& evt) {
+ string command{evt.cast()};
+
+ if (command.empty()) {
+ return false;
+ }
+
+ if (command == "quit") {
+ trigger_quit(false);
+ } else if (command == "restart") {
+ trigger_quit(true);
+ } else if (command == "hide") {
+ m_bar->hide();
+ } else if (command == "show") {
+ m_bar->show();
+ } else if (command == "toggle") {
+ m_bar->toggle();
+ } else {
+ m_log.warn("\"%s\" is not a valid ipc command", command);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Process ipc hook messages
+ */
+bool controller::on(const signals::ipc::hook& evt) {
+ string hook{evt.cast()};
+
+ for (const auto& module : m_modules) {
+ if (!module->running()) {
+ continue;
+ }
+ auto ipc = std::dynamic_pointer_cast<ipc_module>(module);
+ if (ipc != nullptr) {
+ ipc->on_message(hook);
+ }
+ }
+
+ return true;
+}
+
+bool controller::on(const signals::ui::update_background&) {
+ trigger_update(true);
+ return false;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/eventloop.hpp"
+
+#include <cassert>
+#include <utility>
+
+#include "errors.hpp"
+
+#if !(UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR >= 3)
+#error "Polybar requires libuv 1.x and at least version 1.3"
+#endif
+
+POLYBAR_NS
+
+namespace eventloop {
+
+ /**
+ * Closes the given wrapper.
+ *
+ * We have to have distinct cases for all types because we can't just cast to `Handle` without template
+ * arguments.
+ */
+ void close_handle(uv_handle_t* handle) {
+ switch (handle->type) {
+ case UV_ASYNC:
+ static_cast<AsyncHandle*>(handle->data)->close();
+ break;
+ case UV_FS_EVENT:
+ static_cast<FSEventHandle*>(handle->data)->close();
+ break;
+ case UV_POLL:
+ static_cast<PollHandle*>(handle->data)->close();
+ break;
+ case UV_TIMER:
+ static_cast<TimerHandle*>(handle->data)->close();
+ break;
+ case UV_SIGNAL:
+ static_cast<SignalHandle*>(handle->data)->close();
+ break;
+ case UV_NAMED_PIPE:
+ static_cast<PipeHandle*>(handle->data)->close();
+ break;
+ case UV_PREPARE:
+ static_cast<PrepareHandle*>(handle->data)->close();
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ // WriteRequest {{{
+ WriteRequest::WriteRequest(cb_write&& user_cb, cb_error&& err_cb)
+ : write_callback(std::move(user_cb)), write_err_cb(std::move(err_cb)) {
+ get()->data = this;
+ }
+
+ WriteRequest& WriteRequest::create(cb_write&& user_cb, cb_error&& err_cb) {
+ auto r = std::make_unique<WriteRequest>(std::move(user_cb), std::move(err_cb));
+ return r->leak(std::move(r));
+ }
+
+ uv_write_t* WriteRequest::get() {
+ return &req;
+ }
+
+ void WriteRequest::trigger(int status) {
+ if (status < 0) {
+ if (write_err_cb) {
+ write_err_cb(ErrorEvent{status});
+ }
+ } else {
+ if (write_callback) {
+ write_callback();
+ }
+ }
+
+ unleak();
+ }
+
+ WriteRequest& WriteRequest::leak(std::unique_ptr<WriteRequest> h) {
+ lifetime_extender = std::move(h);
+ return *lifetime_extender;
+ }
+
+ void WriteRequest::unleak() {
+ reset_callbacks();
+ lifetime_extender.reset();
+ }
+
+ void WriteRequest::reset_callbacks() {
+ write_callback = nullptr;
+ write_err_cb = nullptr;
+ }
+ // }}}
+
+ // SignalHandle {{{
+ void SignalHandle::init() {
+ UV(uv_signal_init, loop(), get());
+ }
+
+ void SignalHandle::start(int signum, cb&& user_cb) {
+ this->callback = std::move(user_cb);
+ UV(uv_signal_start, get(), event_cb<SignalEvent, &SignalHandle::callback>, signum);
+ }
+
+ void SignalHandle::reset_callbacks() {
+ callback = nullptr;
+ }
+ // }}}
+
+ // PollHandle {{{
+ void PollHandle::init(int fd) {
+ UV(uv_poll_init, loop(), get(), fd);
+ }
+
+ void PollHandle::start(int events, cb&& user_cb, cb_error&& err_cb) {
+ this->callback = std::move(user_cb);
+ this->err_cb = std::move(err_cb);
+ UV(uv_poll_start, get(), events, &poll_callback);
+ }
+
+ void PollHandle::poll_callback(uv_poll_t* handle, int status, int events) {
+ auto& self = cast(handle);
+ if (status < 0) {
+ self.err_cb(ErrorEvent{status});
+ return;
+ }
+
+ self.callback(PollEvent{(uv_poll_event)events});
+ }
+
+ void PollHandle::reset_callbacks() {
+ callback = nullptr;
+ err_cb = nullptr;
+ }
+ // }}}
+
+ // FSEventHandle {{{
+ void FSEventHandle::init() {
+ UV(uv_fs_event_init, loop(), get());
+ }
+
+ void FSEventHandle::start(const string& path, int flags, cb&& user_cb, cb_error&& err_cb) {
+ this->callback = std::move(user_cb);
+ this->err_cb = std::move(err_cb);
+ UV(uv_fs_event_start, get(), fs_event_callback, path.c_str(), flags);
+ }
+
+ void FSEventHandle::fs_event_callback(uv_fs_event_t* handle, const char* path, int events, int status) {
+ auto& self = cast(handle);
+ if (status < 0) {
+ self.err_cb(ErrorEvent{status});
+ return;
+ }
+
+ self.callback(FSEvent{path, (uv_fs_event)events});
+ }
+
+ void FSEventHandle::reset_callbacks() {
+ callback = nullptr;
+ err_cb = nullptr;
+ }
+ // }}}
+
+ // PipeHandle {{{
+ void PipeHandle::init(bool ipc) {
+ UV(uv_pipe_init, loop(), get(), ipc);
+ }
+
+ void PipeHandle::open(int fd) {
+ UV(uv_pipe_open, get(), fd);
+ }
+
+ void PipeHandle::bind(const string& path) {
+ UV(uv_pipe_bind, get(), path.c_str());
+ }
+
+ void PipeHandle::connect(const string& name, cb_connect&& user_cb, cb_error&& err_cb) {
+ this->connect_callback = std::move(user_cb);
+ this->connect_err_cb = std::move(err_cb);
+ uv_pipe_connect(new uv_connect_t(), get(), name.c_str(), connect_cb);
+ }
+
+ void PipeHandle::connect_cb(uv_connect_t* req, int status) {
+ auto& self = PipeHandle::cast((uv_pipe_t*)req->handle);
+
+ if (status < 0) {
+ self.connect_err_cb(ErrorEvent{status});
+ } else {
+ self.connect_callback();
+ }
+
+ delete req;
+ }
+
+ void PipeHandle::reset_callbacks() {
+ StreamHandle::reset_callbacks();
+ connect_callback = nullptr;
+ connect_err_cb = nullptr;
+ }
+ // }}}
+
+ // TimerHandle {{{
+ void TimerHandle::init() {
+ UV(uv_timer_init, loop(), get());
+ }
+
+ void TimerHandle::start(uint64_t timeout, uint64_t repeat, cb&& user_cb) {
+ this->callback = std::move(user_cb);
+ UV(uv_timer_start, get(), void_event_cb<&TimerHandle::callback>, timeout, repeat);
+ }
+
+ void TimerHandle::stop() {
+ UV(uv_timer_stop, get());
+ }
+
+ void TimerHandle::reset_callbacks() {
+ callback = nullptr;
+ }
+ // }}}
+
+ // AsyncHandle {{{
+ void AsyncHandle::init(cb&& user_cb) {
+ this->callback = std::move(user_cb);
+ UV(uv_async_init, loop(), get(), void_event_cb<&AsyncHandle::callback>);
+ }
+
+ void AsyncHandle::send() {
+ UV(uv_async_send, get());
+ }
+
+ void AsyncHandle::reset_callbacks() {
+ callback = nullptr;
+ }
+ // }}}
+
+ // PrepareHandle {{{
+ void PrepareHandle::init() {
+ UV(uv_prepare_init, loop(), get());
+ }
+
+ void PrepareHandle::start(cb&& user_cb) {
+ this->callback = std::move(user_cb);
+ UV(uv_prepare_start, get(), void_event_cb<&PrepareHandle::callback>);
+ }
+
+ void PrepareHandle::reset_callbacks() {
+ callback = nullptr;
+ }
+ // }}}
+
+ // eventloop {{{
+ static void close_walk_cb(uv_handle_t* handle, void*) {
+ if (!uv_is_closing(handle)) {
+ close_handle(handle);
+ }
+ }
+
+ /**
+ * Completely closes everything in the loop.
+ *
+ * After this function returns, uv_loop_close can be called.
+ */
+ static void close_loop(uv_loop_t* loop) {
+ uv_walk(loop, close_walk_cb, nullptr);
+ UV(uv_run, loop, UV_RUN_DEFAULT);
+ }
+
+ loop::loop() {
+ m_loop = std::make_unique<uv_loop_t>();
+ UV(uv_loop_init, m_loop.get());
+ m_loop->data = this;
+ }
+
+ loop::~loop() {
+ if (m_loop) {
+ try {
+ close_loop(m_loop.get());
+ UV(uv_loop_close, m_loop.get());
+ } catch (const std::exception& e) {
+ logger::make().err("%s", e.what());
+ }
+
+ m_loop.reset();
+ }
+ }
+
+ void loop::run() {
+ UV(uv_run, m_loop.get(), UV_RUN_DEFAULT);
+ }
+
+ void loop::stop() {
+ uv_stop(m_loop.get());
+ }
+
+ uint64_t loop::now() const {
+ return uv_now(m_loop.get());
+ }
+
+ uv_loop_t* loop::get() const {
+ return m_loop.get();
+ }
+ // }}}
+
+} // namespace eventloop
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/logger.hpp"
+
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/concurrency.hpp"
+#include "utils/factory.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+/**
+ * Convert string
+ */
+const char* logger::convert(string& arg) const {
+ return arg.c_str();
+}
+
+const char* logger::convert(const string& arg) const {
+ return arg.c_str();
+}
+
+/**
+ * Convert thread id
+ */
+size_t logger::convert(const std::thread::id arg) const {
+ return concurrency_util::thread_id(arg);
+}
+
+/**
+ * Create instance
+ */
+logger::make_type logger::make(loglevel level) {
+ return *factory_util::singleton<std::remove_reference_t<logger::make_type>>(level);
+}
+
+/**
+ * Construct logger
+ */
+logger::logger(loglevel level) : m_level(level) {
+ // clang-format off
+ if (isatty(m_fd)) {
+ m_prefixes[loglevel::TRACE] = "\r\033[0;32m- \033[0m";
+ m_prefixes[loglevel::INFO] = "\r\033[1;32m* \033[0m";
+ m_prefixes[loglevel::NOTICE] = "\r\033[1;34mnotice: \033[0m";
+ m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarn: \033[0m";
+ m_prefixes[loglevel::ERROR] = "\r\033[1;31merror: \033[0m";
+ m_suffixes[loglevel::TRACE] = "\033[0m";
+ m_suffixes[loglevel::INFO] = "\033[0m";
+ m_suffixes[loglevel::NOTICE] = "\033[0m";
+ m_suffixes[loglevel::WARNING] = "\033[0m";
+ m_suffixes[loglevel::ERROR] = "\033[0m";
+ } else {
+ m_prefixes.emplace(make_pair(loglevel::TRACE, "polybar|trace: "));
+ m_prefixes.emplace(make_pair(loglevel::INFO, "polybar|info: "));
+ m_prefixes.emplace(make_pair(loglevel::NOTICE, "polybar|notice: "));
+ m_prefixes.emplace(make_pair(loglevel::WARNING, "polybar|warn: "));
+ m_prefixes.emplace(make_pair(loglevel::ERROR, "polybar|error: "));
+ m_suffixes.emplace(make_pair(loglevel::TRACE, ""));
+ m_suffixes.emplace(make_pair(loglevel::INFO, ""));
+ m_suffixes.emplace(make_pair(loglevel::NOTICE, ""));
+ m_suffixes.emplace(make_pair(loglevel::WARNING, ""));
+ m_suffixes.emplace(make_pair(loglevel::ERROR, ""));
+ }
+ // clang-format on
+}
+
+/**
+ * Set output verbosity
+ */
+void logger::verbosity(loglevel level) {
+#ifndef DEBUG_LOGGER
+ if (level == loglevel::TRACE) {
+ throw application_error("Trace logging is not enabled...");
+ }
+#endif
+ m_level = level;
+}
+
+/**
+ * Convert given loglevel name to its enum type counterpart
+ */
+loglevel logger::parse_verbosity(const string& name, loglevel fallback) {
+ if (string_util::compare(name, "error")) {
+ return loglevel::ERROR;
+ } else if (string_util::compare(name, "warning")) {
+ return loglevel::WARNING;
+ } else if (string_util::compare(name, "notice")) {
+ return loglevel::NOTICE;
+ } else if (string_util::compare(name, "info")) {
+ return loglevel::INFO;
+ } else if (string_util::compare(name, "trace")) {
+ return loglevel::TRACE;
+ } else {
+ return fallback;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/renderer.hpp"
+
+#include <cassert>
+
+#include "cairo/context.hpp"
+#include "components/config.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/math.hpp"
+#include "utils/units.hpp"
+#include "x11/atoms.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/connection.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+static constexpr double BLOCK_GAP{20.0};
+
+/**
+ * Create instance
+ */
+renderer::make_type renderer::make(const bar_settings& bar, tags::action_context& action_ctxt, const config& conf) {
+ // clang-format off
+ return std::make_unique<renderer>(
+ connection::make(),
+ signal_emitter::make(),
+ conf,
+ logger::make(),
+ forward<decltype(bar)>(bar),
+ background_manager::make(),
+ action_ctxt);
+ // clang-format on
+}
+
+/**
+ * Construct renderer instance
+ */
+renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, const logger& logger,
+ const bar_settings& bar, background_manager& background, tags::action_context& action_ctxt)
+ : renderer_interface(action_ctxt)
+ , m_connection(conn)
+ , m_sig(sig)
+ , m_conf(conf)
+ , m_log(logger)
+ , m_bar(forward<const bar_settings&>(bar))
+ , m_rect(m_bar.inner_area()) {
+ m_sig.attach(this);
+
+ m_log.trace("renderer: Get TrueColor visual");
+ if ((m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, 32)) != nullptr) {
+ m_depth = 32;
+ } else if ((m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, 24)) != nullptr) {
+ m_depth = 24;
+ } else {
+ throw application_error("Could not find a 24 or 32-bit TrueColor visual");
+ }
+
+ m_log.info("renderer: Using %d-bit TrueColor visual: 0x%x", m_depth, m_visual->visual_id);
+
+ m_log.trace("renderer: Allocate colormap");
+ m_colormap = m_connection.generate_id();
+ m_connection.create_colormap(XCB_COLORMAP_ALLOC_NONE, m_colormap, m_connection.root(), m_visual->visual_id);
+
+ m_log.trace("renderer: Allocate output window");
+ // clang-format off
+ m_window = winspec(m_connection)
+ << cw_size(m_bar.size)
+ << cw_pos(m_bar.pos)
+ << cw_depth(m_depth)
+ << cw_visual(m_visual->visual_id)
+ << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
+ << cw_params_back_pixel(0)
+ << cw_params_border_pixel(0)
+ << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
+ << cw_params_colormap(m_colormap)
+ << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE
+ |XCB_EVENT_MASK_EXPOSURE
+ |XCB_EVENT_MASK_BUTTON_PRESS)
+ << cw_params_override_redirect(m_bar.override_redirect)
+ << cw_flush(true);
+ // clang-format on
+
+ m_log.trace("renderer: Allocate window pixmaps");
+ {
+ m_pixmap = m_connection.generate_id();
+ m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_bar.size.w, m_bar.size.h);
+
+ uint32_t configure_mask = 0;
+ std::array<uint32_t, 32> configure_values{};
+ xcb_params_cw_t configure_params{};
+
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, back_pixmap, m_pixmap);
+ connection::pack_values(configure_mask, &configure_params, configure_values);
+ m_connection.change_window_attributes_checked(m_window, configure_mask, configure_values.data());
+ }
+
+ m_log.trace("renderer: Allocate graphic contexts");
+ {
+ uint32_t mask{0};
+ std::array<uint32_t, 32> value_list{};
+ xcb_params_gc_t params{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, m_bar.foreground);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0);
+ connection::pack_values(mask, ¶ms, value_list);
+ m_gcontext = m_connection.generate_id();
+ m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list.data());
+ }
+
+ m_log.trace("renderer: Allocate alignment blocks");
+ {
+ m_blocks.emplace(alignment::LEFT, alignment_block{nullptr, 0.0, 0.0, 0.});
+ m_blocks.emplace(alignment::CENTER, alignment_block{nullptr, 0.0, 0.0, 0.});
+ m_blocks.emplace(alignment::RIGHT, alignment_block{nullptr, 0.0, 0.0, 0.});
+ }
+
+ m_log.trace("renderer: Allocate cairo components");
+ {
+ m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, m_visual, m_bar.size.w, m_bar.size.h);
+ m_context = make_unique<cairo::context>(*m_surface, m_log);
+ }
+
+ m_log.trace("renderer: Load fonts");
+ {
+ auto fonts = m_conf.get_list<string>(m_conf.section(), "font", {});
+ if (fonts.empty()) {
+ m_log.warn("No fonts specified, using fallback font \"fixed\"");
+ fonts.emplace_back("fixed");
+ }
+
+ for (const auto& f : fonts) {
+ int offset{0};
+ string pattern{f};
+ size_t pos = pattern.rfind(';');
+ if (pos != string::npos) {
+ offset = std::strtol(pattern.substr(pos + 1).c_str(), nullptr, 10);
+ pattern.erase(pos);
+ }
+ auto font = cairo::make_font(*m_context, string{pattern}, offset, m_bar.dpi_x, m_bar.dpi_y);
+ m_log.notice("Loaded font \"%s\" (name=%s, offset=%i, file=%s)", pattern, font->name(), offset, font->file());
+ *m_context << move(font);
+ }
+ }
+
+ m_pseudo_transparency = m_conf.get<bool>("settings", "pseudo-transparency", m_pseudo_transparency);
+ if (m_pseudo_transparency) {
+ m_log.trace("Activate root background manager");
+ m_background = background.observe(m_bar.outer_area(false), m_window);
+ }
+
+ m_comp_bg = m_conf.get<cairo_operator_t>("settings", "compositing-background", m_comp_bg);
+ m_comp_fg = m_conf.get<cairo_operator_t>("settings", "compositing-foreground", m_comp_fg);
+ m_comp_ol = m_conf.get<cairo_operator_t>("settings", "compositing-overline", m_comp_ol);
+ m_comp_ul = m_conf.get<cairo_operator_t>("settings", "compositing-underline", m_comp_ul);
+ m_comp_border = m_conf.get<cairo_operator_t>("settings", "compositing-border", m_comp_border);
+
+ m_fixedcenter = m_conf.get(m_conf.section(), "fixed-center", true);
+}
+
+/**
+ * Deconstruct instance
+ */
+renderer::~renderer() {
+ m_sig.detach(this);
+}
+
+/**
+ * Get bar window
+ */
+xcb_window_t renderer::window() const {
+ return m_window;
+}
+
+/**
+ * Get the bar window visual
+ */
+xcb_visualtype_t* renderer::visual() const {
+ return m_visual;
+}
+
+/**
+ * Get the bar window depth
+ */
+int renderer::depth() const {
+ return m_depth;
+}
+
+/**
+ * Begin render routine
+ */
+void renderer::begin(xcb_rectangle_t rect) {
+ m_log.trace_x("renderer: begin (geom=%ix%i+%i+%i)", rect.width, rect.height, rect.x, rect.y);
+
+ // Reset state
+ m_rect = rect;
+ m_align = alignment::NONE;
+
+ // Clear canvas
+ m_context->save();
+ m_context->clear();
+
+ // when pseudo-transparency is requested, render the bar into a new layer
+ // that will later be composited against the desktop background
+ if (m_pseudo_transparency) {
+ m_context->push();
+ }
+
+ // Create corner mask
+ if (m_bar.radius && m_cornermask == nullptr) {
+ m_context->save();
+ m_context->push();
+ // clang-format off
+ *m_context << cairo::rounded_corners{
+ static_cast<double>(m_rect.x),
+ static_cast<double>(m_rect.y),
+ static_cast<double>(m_rect.width),
+ static_cast<double>(m_rect.height), m_bar.radius};
+ // clang-format on
+ *m_context << rgba{0xffffffff};
+ m_context->fill();
+ m_context->pop(&m_cornermask);
+ m_context->restore();
+ }
+
+ fill_borders();
+
+ // clang-format off
+ m_context->clip(cairo::rect{
+ static_cast<double>(m_rect.x),
+ static_cast<double>(m_rect.y),
+ static_cast<double>(m_rect.width),
+ static_cast<double>(m_rect.height)});
+ // clang-format on
+}
+
+/**
+ * End render routine
+ */
+void renderer::end() {
+ m_log.trace_x("renderer: end");
+
+ if (m_align != alignment::NONE) {
+ m_log.trace_x("renderer: pop(%i)", static_cast<int>(m_align));
+ m_context->pop(&m_blocks[m_align].pattern);
+
+ // Capture the concatenated block contents
+ // so that it can be masked with the corner pattern
+ m_context->push();
+
+ // Draw the background on the new layer to make up for
+ // the areas not covered by the alignment blocks
+ fill_background();
+
+ for (auto&& b : m_blocks) {
+ flush(b.first);
+ }
+
+ cairo_pattern_t* blockcontents{};
+ m_context->pop(&blockcontents);
+
+ if (m_cornermask != nullptr) {
+ *m_context << blockcontents;
+ m_context->mask(m_cornermask);
+ } else {
+ *m_context << blockcontents;
+ m_context->paint();
+ }
+
+ m_context->destroy(&blockcontents);
+ } else {
+ fill_background();
+ }
+
+ // For pseudo-transparency, capture the contents of the rendered bar and
+ // composite it against the desktop wallpaper. This way transparent parts of
+ // the bar will be filled by the wallpaper creating illusion of transparency.
+ if (m_pseudo_transparency) {
+ cairo_pattern_t* barcontents{};
+ m_context->pop(&barcontents); // corresponding push is in renderer::begin
+
+ auto root_bg = m_background->get_surface();
+ if (root_bg != nullptr) {
+ m_log.trace_x("renderer: root background");
+ *m_context << *root_bg;
+ m_context->paint();
+ *m_context << CAIRO_OPERATOR_OVER;
+ }
+ *m_context << barcontents;
+ m_context->paint();
+ m_context->destroy(&barcontents);
+ }
+
+ m_context->restore();
+ m_surface->flush();
+
+ flush();
+
+ m_sig.emit(signals::ui::changed{});
+}
+
+/**
+ * Flush contents of given alignment block
+ */
+void renderer::flush(alignment a) {
+ if (m_blocks[a].pattern == nullptr) {
+ return;
+ }
+
+ m_context->save();
+
+ double x = static_cast<int>(block_x(a) + 0.5);
+ double y = static_cast<int>(block_y(a) + 0.5);
+ double w = static_cast<int>(block_w(a) + 0.5);
+ double h = static_cast<int>(block_h(a) + 0.5);
+ double xw = x + w;
+ bool fits{xw <= m_rect.width};
+
+ m_log.trace("renderer: flush(%i geom=%gx%g+%g+%g, falloff=%i)", static_cast<int>(a), w, h, x, y, !fits);
+
+ // Set block shape
+ *m_context << cairo::abspos{0.0, 0.0};
+ *m_context << cairo::rect{m_rect.x + x, m_rect.y + y, w, h};
+
+ // Restrict drawing to the block rectangle
+ m_context->clip(true);
+
+ // Clear the area covered by the block
+ m_context->clear();
+
+ *m_context << cairo::translate{x, 0.0};
+ *m_context << m_blocks[a].pattern;
+ m_context->paint();
+
+ *m_context << cairo::abspos{0.0, 0.0};
+ m_context->destroy(&m_blocks[a].pattern);
+ m_context->restore();
+
+ if (!fits) {
+ // Paint falloff gradient at the end of the visible block
+ // to indicate that the content expands past the canvas
+
+ /*
+ * How many pixels are hidden
+ */
+ double overflow = xw - m_rect.width;
+ double visible_width = w - overflow;
+
+ /*
+ * Width of the falloff gradient. Depends on how much of the block is hidden
+ */
+ double fsize = std::max(5.0, std::min(std::abs(overflow), 30.0));
+ m_log.trace("renderer: Drawing falloff (pos=%g, size=%g, overflow=%g)", visible_width - fsize, fsize, overflow);
+ m_context->save();
+ *m_context << cairo::translate{(double)m_rect.x, (double)m_rect.y};
+ *m_context << cairo::abspos{0.0, 0.0};
+ *m_context << cairo::rect{x + visible_width - fsize, y, fsize, h};
+ m_context->clip(true);
+ *m_context << cairo::linear_gradient{
+ x + visible_width - fsize, y, x + visible_width, y, {rgba{0x00000000}, rgba{0xFF000000}}};
+ m_context->paint(0.25);
+ m_context->restore();
+ }
+}
+
+/**
+ * Flush pixmap contents onto the target window
+ */
+void renderer::flush() {
+ m_log.trace_x("renderer: flush");
+
+ highlight_clickable_areas();
+
+ m_surface->flush();
+ // Clear entire window so that the new pixmap is shown
+ m_connection.clear_area(0, m_window, 0, 0, m_bar.size.w, m_bar.size.h);
+ m_connection.flush();
+
+ if (!m_snapshot_dst.empty()) {
+ try {
+ m_surface->write_png(m_snapshot_dst);
+ m_log.notice("Successfully wrote %s", m_snapshot_dst);
+ } catch (const exception& err) {
+ m_log.err("Failed to write snapshot (err: %s)", err.what());
+ }
+ m_snapshot_dst.clear();
+ }
+}
+
+/**
+ * Get x position of block for given alignment
+ *
+ * The position is relative to m_rect.x (the left side of the bar w/o borders and tray)
+ */
+double renderer::block_x(alignment a) const {
+ switch (a) {
+ case alignment::CENTER: {
+ // The leftmost x position this block can start at
+ double min_pos = block_w(alignment::LEFT);
+
+ if (min_pos != 0) {
+ min_pos += BLOCK_GAP;
+ }
+
+ double right_width = block_w(alignment::RIGHT);
+ /*
+ * The rightmost x position this block can end at
+ *
+ * We can't use block_x(alignment::RIGHT) because that would lead to infinite recursion
+ */
+ double max_pos = m_rect.width - right_width;
+
+ if (right_width != 0) {
+ max_pos -= BLOCK_GAP;
+ }
+
+ /*
+ * x position of the center of this block
+ *
+ * With fixed-center this will be the center of the bar unless it is pushed to the left by a large right block
+ * Without fixed-center this will be the middle between the end of the left and the start of the right block.
+ */
+ double base_pos{0.0};
+
+ if (m_fixedcenter) {
+ /*
+ * This is in the middle of the *bar*. Not just the middle of m_rect because this way we need to account for the
+ * tray.
+ *
+ * The resulting position is relative to the very left of the bar (including border and tray), so we need to
+ * compensate for that by subtracting m_rect.x
+ */
+ base_pos = m_bar.size.w / 2.0 - m_rect.x;
+
+ /*
+ * The center block can be moved to the left if the right block is too large
+ */
+ base_pos = std::min(base_pos, max_pos - block_w(a) / 2.0);
+ } else {
+ base_pos = (min_pos + max_pos) / 2.0;
+ }
+
+ /*
+ * The left block always has priority (even with fixed-center = true)
+ */
+ return std::max(base_pos - block_w(a) / 2.0, min_pos);
+ }
+ case alignment::RIGHT: {
+ /*
+ * The block immediately to the left of this block
+ *
+ * Generally the center block unless it is empty.
+ */
+ alignment left_barrier = alignment::CENTER;
+
+ if (block_w(alignment::CENTER) == 0) {
+ left_barrier = alignment::LEFT;
+ }
+
+ // The minimum x position this block can start at
+ double min_pos = block_x(left_barrier) + block_w(left_barrier);
+
+ if (block_w(left_barrier) != 0) {
+ min_pos += BLOCK_GAP;
+ }
+
+ return std::max(m_rect.width - block_w(a), min_pos);
+ }
+ default:
+ return 0.0;
+ }
+}
+
+/**
+ * Get y position of block for given alignment
+ */
+double renderer::block_y(alignment) const {
+ return 0.0;
+}
+
+/**
+ * Get block width for given alignment
+ */
+double renderer::block_w(alignment a) const {
+ return m_blocks.at(a).width;
+}
+
+/**
+ * Get block height for given alignment
+ */
+double renderer::block_h(alignment) const {
+ return m_rect.height;
+}
+
+void renderer::increase_x(double dx) {
+ m_blocks[m_align].x += dx;
+ /*
+ * The width only increases when x becomes larger than the old width.
+ */
+ m_blocks[m_align].width = std::max(m_blocks[m_align].width, m_blocks[m_align].x);
+}
+
+/**
+ * Fill background color
+ */
+void renderer::fill_background() {
+ m_context->save();
+ *m_context << m_comp_bg;
+
+ if (!m_bar.background_steps.empty()) {
+ m_log.trace_x("renderer: gradient background (steps=%lu)", m_bar.background_steps.size());
+ *m_context << cairo::linear_gradient{0.0, 0.0 + m_rect.y, 0.0, 0.0 + m_rect.height, m_bar.background_steps};
+ } else {
+ m_log.trace_x("renderer: solid background #%08x", m_bar.background);
+ *m_context << m_bar.background;
+ }
+
+ m_context->paint();
+ m_context->restore();
+}
+
+/**
+ * Fill overline color
+ */
+void renderer::fill_overline(rgba color, double x, double w) {
+ if (m_bar.overline.size) {
+ m_log.trace_x("renderer: overline(x=%f, w=%f)", x, w);
+ m_context->save();
+ *m_context << m_comp_ol;
+ *m_context << color;
+ *m_context << cairo::rect{x, static_cast<double>(m_rect.y), w, static_cast<double>(m_bar.overline.size)};
+ m_context->fill();
+ m_context->restore();
+ }
+}
+
+/**
+ * Fill underline color
+ */
+void renderer::fill_underline(rgba color, double x, double w) {
+ if (m_bar.underline.size) {
+ m_log.trace_x("renderer: underline(x=%f, w=%f)", x, w);
+ m_context->save();
+ *m_context << m_comp_ul;
+ *m_context << color;
+ *m_context << cairo::rect{x, static_cast<double>(m_rect.y + m_rect.height - m_bar.underline.size), w,
+ static_cast<double>(m_bar.underline.size)};
+ m_context->fill();
+ m_context->restore();
+ }
+}
+
+/**
+ * Fill border colors
+ */
+void renderer::fill_borders() {
+ m_context->save();
+ *m_context << m_comp_border;
+
+ // Draw round border corners
+
+ if (m_bar.radius.top_left) {
+ cairo::circle_segment borderTL;
+ borderTL.radius = m_bar.borders.at(edge::LEFT).size + m_bar.radius.top_left;
+ borderTL.x = m_bar.borders.at(edge::LEFT).size + m_bar.radius.top_left;
+ borderTL.y = m_bar.borders.at(edge::TOP).size + m_bar.radius.top_left;
+ borderTL.angle_from = 180;
+ borderTL.angle_to = 270;
+ borderTL.w = m_bar.borders.at(edge::LEFT).size;
+ (*m_context << borderTL << m_bar.borders.at(edge::LEFT).color).fill();
+ }
+
+ if (m_bar.radius.bottom_left) {
+ cairo::circle_segment borderBL;
+ borderBL.radius = m_bar.borders.at(edge::LEFT).size + m_bar.radius.bottom_left;
+ borderBL.x = m_bar.borders.at(edge::LEFT).size + m_bar.radius.bottom_left;
+ borderBL.y = m_bar.size.h - (m_bar.borders.at(edge::BOTTOM).size + m_bar.radius.bottom_left);
+ borderBL.angle_from = 90;
+ borderBL.angle_to = 180;
+ borderBL.w = m_bar.borders.at(edge::LEFT).size;
+ (*m_context << borderBL << m_bar.borders.at(edge::LEFT).color).fill();
+ }
+
+ if (m_bar.radius.top_right) {
+ cairo::circle_segment borderTR;
+ borderTR.radius = m_bar.borders.at(edge::RIGHT).size + m_bar.radius.top_right;
+ borderTR.x = m_bar.size.w - (m_bar.borders.at(edge::RIGHT).size + m_bar.radius.top_right);
+ borderTR.y = m_bar.borders.at(edge::TOP).size + m_bar.radius.top_right;
+ borderTR.angle_from = -90;
+ borderTR.angle_to = 0;
+ borderTR.w = m_bar.borders.at(edge::RIGHT).size;
+ (*m_context << borderTR << m_bar.borders.at(edge::RIGHT).color).fill();
+ }
+
+ if (m_bar.radius.bottom_right) {
+ cairo::circle_segment borderBR;
+ borderBR.radius = m_bar.borders.at(edge::RIGHT).size + m_bar.radius.bottom_right;
+ borderBR.x = m_bar.size.w - (m_bar.borders.at(edge::RIGHT).size + m_bar.radius.bottom_right);
+ borderBR.y = m_bar.size.h - (m_bar.borders.at(edge::BOTTOM).size + m_bar.radius.bottom_right);
+ borderBR.angle_from = 0;
+ borderBR.angle_to = 90;
+ borderBR.w = m_bar.borders.at(edge::RIGHT).size;
+ (*m_context << borderBR << m_bar.borders.at(edge::RIGHT).color).fill();
+ }
+
+ // Draw straight horizontal / vertical borders
+
+ if (m_bar.borders.at(edge::TOP).size) {
+ cairo::rect top{0.0, 0.0, 0.0, 0.0};
+ top.x += m_bar.borders.at(edge::LEFT).size;
+ top.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size;
+ top.h += m_bar.borders.at(edge::TOP).size;
+
+ if (m_bar.radius.top_left) {
+ top.x += m_bar.radius.top_left;
+ top.w -= m_bar.radius.top_left;
+ }
+
+ if (m_bar.radius.top_right) {
+ top.w -= m_bar.radius.top_right;
+ }
+
+ m_log.trace_x("renderer: border T(%.0f, #%08x)", top.h, m_bar.borders.at(edge::TOP).color);
+ (*m_context << top << m_bar.borders.at(edge::TOP).color).fill();
+ }
+
+ if (m_bar.borders.at(edge::BOTTOM).size) {
+ cairo::rect bottom{0.0, 0.0, 0.0, 0.0};
+ bottom.x += m_bar.borders.at(edge::LEFT).size;
+ bottom.y += m_bar.size.h - m_bar.borders.at(edge::BOTTOM).size;
+ bottom.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size;
+ bottom.h += m_bar.borders.at(edge::BOTTOM).size;
+
+ if (m_bar.radius.bottom_left) {
+ bottom.x += m_bar.radius.bottom_left;
+ bottom.w -= m_bar.radius.bottom_left;
+ }
+
+ if (m_bar.radius.bottom_right) {
+ bottom.w -= m_bar.radius.bottom_right;
+ }
+
+ m_log.trace_x("renderer: border B(%.0f, #%08x)", bottom.h, m_bar.borders.at(edge::BOTTOM).color);
+ (*m_context << bottom << m_bar.borders.at(edge::BOTTOM).color).fill();
+ }
+
+ if (m_bar.borders.at(edge::LEFT).size) {
+ cairo::rect left{0.0, 0.0, 0.0, 0.0};
+ left.w += m_bar.borders.at(edge::LEFT).size;
+ left.h += m_bar.size.h;
+
+ if (m_bar.radius.top_left) {
+ left.y += m_bar.radius.top_left + m_bar.borders.at(edge::TOP).size;
+ left.h -= m_bar.radius.top_left + m_bar.borders.at(edge::TOP).size;
+ }
+
+ if (m_bar.radius.bottom_left) {
+ left.h -= m_bar.radius.bottom_left + m_bar.borders.at(edge::BOTTOM).size;
+ }
+
+ m_log.trace_x("renderer: border L(%.0f, #%08x)", left.w, m_bar.borders.at(edge::LEFT).color);
+ (*m_context << left << m_bar.borders.at(edge::LEFT).color).fill();
+ }
+
+ if (m_bar.borders.at(edge::RIGHT).size) {
+ cairo::rect right{0.0, 0.0, 0.0, 0.0};
+ right.x += m_bar.size.w - m_bar.borders.at(edge::RIGHT).size;
+ right.w += m_bar.borders.at(edge::RIGHT).size;
+ right.h += m_bar.size.h;
+
+ if (m_bar.radius.top_right) {
+ right.y += m_bar.radius.top_right + m_bar.borders.at(edge::TOP).size;
+ right.h -= m_bar.radius.top_right + m_bar.borders.at(edge::TOP).size;
+ }
+
+ if (m_bar.radius.bottom_right) {
+ right.h -= m_bar.radius.bottom_right + m_bar.borders.at(edge::BOTTOM).size;
+ }
+
+ m_log.trace_x("renderer: border R(%.0f, #%08x)", right.w, m_bar.borders.at(edge::RIGHT).color);
+ (*m_context << right << m_bar.borders.at(edge::RIGHT).color).fill();
+ }
+
+ m_context->restore();
+}
+
+/**
+ * Draw text contents
+ */
+void renderer::render_text(const tags::context& ctxt, const string&& contents) {
+ assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align);
+ m_log.trace_x("renderer: text(%s)", contents.c_str());
+
+ cairo::abspos origin{};
+ origin.x = m_rect.x + m_blocks[m_align].x;
+ origin.y = m_rect.y + m_rect.height / 2.0;
+
+ double x_old = m_blocks[m_align].x;
+ /*
+ * This variable is increased by the text renderer
+ */
+ double x_new = x_old;
+
+ cairo::textblock block{};
+ block.align = m_align;
+ block.contents = contents;
+ block.font = ctxt.get_font();
+ block.x_advance = &x_new;
+ block.y_advance = &m_blocks[m_align].y;
+ block.bg_rect = cairo::rect{0.0, 0.0, 0.0, 0.0};
+
+ rgba bg = ctxt.get_bg();
+
+ // Only draw text background if the color differs from
+ // the background color of the bar itself
+ // Note: this means that if the user explicitly set text
+ // background color equal to background-0 it will be ignored
+ if (bg != m_bar.background) {
+ block.bg = bg;
+ block.bg_operator = m_comp_bg;
+ block.bg_rect.x = m_rect.x;
+ block.bg_rect.y = m_rect.y;
+ block.bg_rect.h = m_rect.height;
+ }
+
+ m_context->save();
+ *m_context << origin;
+ *m_context << m_comp_fg;
+ *m_context << ctxt.get_fg();
+ *m_context << block;
+ m_context->restore();
+
+ double dx = x_new - x_old;
+ increase_x(dx);
+
+ if (dx > 0.0) {
+ if (ctxt.has_underline()) {
+ fill_underline(ctxt.get_ul(), origin.x, dx);
+ }
+
+ if (ctxt.has_overline()) {
+ fill_overline(ctxt.get_ol(), origin.x, dx);
+ }
+ }
+}
+
+void renderer::draw_offset(const tags::context& ctxt, rgba color, double x, double w) {
+ if (w <= 0) {
+ return;
+ }
+
+ if (color != m_bar.background) {
+ m_log.trace_x("renderer: offset(x=%f, w=%f)", x, w);
+ m_context->save();
+ *m_context << m_comp_bg;
+ *m_context << color;
+ *m_context << cairo::rect{
+ m_rect.x + x, static_cast<double>(m_rect.y), w, static_cast<double>(m_rect.y + m_rect.height)};
+ m_context->fill();
+ m_context->restore();
+ }
+
+ if (ctxt.has_underline()) {
+ fill_underline(ctxt.get_ul(), x, w);
+ }
+
+ if (ctxt.has_overline()) {
+ fill_overline(ctxt.get_ol(), x, w);
+ }
+}
+
+void renderer::render_offset(const tags::context& ctxt, const extent_val offset) {
+ assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align);
+ m_log.trace_x("renderer: offset_pixel(%f)", offset);
+
+ int offset_width = units_utils::extent_to_pixel(offset, m_bar.dpi_x);
+ rgba bg = ctxt.get_bg();
+ draw_offset(ctxt, bg, m_blocks[m_align].x, offset_width);
+ increase_x(offset_width);
+}
+
+void renderer::change_alignment(const tags::context& ctxt) {
+ auto align = ctxt.get_alignment();
+ assert(align != alignment::NONE);
+ if (align != m_align) {
+ m_log.trace_x("renderer: change_alignment(%i)", static_cast<int>(align));
+
+ if (m_align != alignment::NONE) {
+ m_log.trace_x("renderer: pop(%i)", static_cast<int>(m_align));
+ m_context->pop(&m_blocks[m_align].pattern);
+ }
+
+ m_align = align;
+ m_blocks[m_align].x = 0.0;
+ m_blocks[m_align].y = 0.0;
+ m_blocks[m_align].width = 0.;
+ m_context->push();
+ m_log.trace_x("renderer: push(%i)", static_cast<int>(m_align));
+
+ fill_background();
+ }
+}
+
+double renderer::get_x(const tags::context& ctxt) const {
+ assert(ctxt.get_alignment() != alignment::NONE && ctxt.get_alignment() == m_align);
+ return m_blocks.at(ctxt.get_alignment()).x;
+}
+
+double renderer::get_alignment_start(const alignment align) const {
+ return block_x(align) + m_rect.x;
+}
+
+/**
+ * Colorize the bounding box of created action blocks
+ */
+void renderer::highlight_clickable_areas() {
+#ifdef DEBUG_HINTS
+ map<alignment, int> hint_num{};
+ for (auto&& action : m_action_ctxt.get_blocks()) {
+ int n = hint_num.find(action.align)->second++;
+ double x = action.start_x;
+ double y = m_rect.y;
+ double w = action.width();
+ double h = m_rect.height;
+
+ m_context->save();
+ *m_context << CAIRO_OPERATOR_DIFFERENCE << (n % 2 ? rgba{0xFF00FF00} : rgba{0xFFFF0000});
+ *m_context << cairo::rect{x, y, w, h};
+ m_context->fill();
+ m_context->restore();
+ }
+ m_surface->flush();
+#endif
+}
+
+bool renderer::on(const signals::ui::request_snapshot& evt) {
+ m_snapshot_dst = evt.cast();
+ return true;
+}
+
+void renderer::apply_tray_position(const tags::context& context) {
+ if (context.get_relative_tray_position() != std::pair<alignment, int>()) {
+ int absolute_x = static_cast<int>(
+ block_x(context.get_relative_tray_position().first) + context.get_relative_tray_position().second);
+ m_sig.emit(signals::ui_tray::tray_pos_change{absolute_x});
+ m_sig.emit(signals::ui_tray::tray_visibility{true});
+ } else {
+ m_sig.emit(signals::ui_tray::tray_visibility{false});
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/screen.hpp"
+
+#include <algorithm>
+#include <csignal>
+#include <thread>
+
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+#include "x11/types.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+using namespace signals::eventqueue;
+
+/**
+ * Create instance
+ */
+screen::make_type screen::make(const config& config) {
+ return std::make_unique<screen>(connection::make(), signal_emitter::make(), logger::make(), config);
+}
+
+/**
+ * Construct screen instance
+ */
+screen::screen(connection& conn, signal_emitter& emitter, const logger& logger, const config& conf)
+ : m_connection(conn)
+ , m_sig(emitter)
+ , m_log(logger)
+ , m_conf(conf)
+ , m_root(conn.root())
+ , m_monitors(randr_util::get_monitors(m_connection, true, false))
+ , m_size({conn.screen()->width_in_pixels, conn.screen()->height_in_pixels}) {
+ // Check if the reloading has been disabled by the user
+ if (!m_conf.get("settings", "screenchange-reload", false)) {
+ return;
+ }
+
+ // clang-format off
+ m_proxy = winspec(m_connection)
+ << cw_size(1U, 1U)
+ << cw_pos(-1, -1)
+ << cw_parent(m_root)
+ << cw_params_override_redirect(true)
+ << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE)
+ << cw_flush(true);
+ // clang-format on
+
+ // Update the root windows event mask
+ auto attributes = m_connection.get_window_attributes(m_root);
+ m_root_mask = attributes->your_event_mask;
+ attributes->your_event_mask = attributes->your_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
+ m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &attributes->your_event_mask);
+
+ // Receive randr events
+ m_connection.randr().select_input(m_proxy, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
+
+ // Attach the sink to process randr events
+ m_connection.attach_sink(this, SINK_PRIORITY_SCREEN);
+
+ // Create window used as event proxy
+ m_connection.map_window(m_proxy);
+ m_connection.flush();
+}
+
+/**
+ * Deconstruct screen instance
+ */
+screen::~screen() {
+ m_connection.detach_sink(this, SINK_PRIORITY_SCREEN);
+
+ if (m_proxy != XCB_NONE) {
+ m_connection.destroy_window(m_proxy);
+ }
+}
+
+void screen::handle(const evt::map_notify& evt) {
+ if (evt->window != m_proxy) {
+ return;
+ }
+
+ // Once the proxy window has been mapped, restore the original root window event mask.
+ m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &m_root_mask);
+}
+
+/**
+ * Handle XCB_RANDR_SCREEN_CHANGE_NOTIFY events
+ *
+ * If any of the monitors have changed we trigger a reload
+ */
+void screen::handle(const evt::randr_screen_change_notify& evt) {
+ if (m_sigraised || evt->request_window != m_proxy) {
+ return;
+ }
+
+ m_connection.reset_screen();
+ auto screen = m_connection.screen();
+ auto changed = false;
+
+ // We need to reload if the screen size changed as well
+ if (screen->width_in_pixels != m_size.w || screen->height_in_pixels != m_size.h) {
+ changed = true;
+ } else {
+ changed = have_monitors_changed();
+ }
+
+ if (changed) {
+ m_log.notice("randr_screen_change_notify (%ux%u)... reloading", evt->width, evt->height);
+ m_sig.emit(exit_reload{});
+ m_sigraised = true;
+ }
+}
+
+/**
+ * Checks if the stored monitor list is different from a newly fetched one
+ *
+ * Fetches the monitor list and compares it with the one stored
+ */
+bool screen::have_monitors_changed() const {
+ auto monitors = randr_util::get_monitors(m_connection, true, false);
+
+ if (monitors.size() != m_monitors.size()) {
+ return true;
+ }
+
+ for (auto m : m_monitors) {
+ auto it =
+ std::find_if(monitors.begin(), monitors.end(), [m](auto& monitor) -> bool { return m->equals(*monitor); });
+
+ /*
+ * Every monitor in the stored list should also exist in the newly fetched
+ * list. If this holds then the two lists are equivalent since they have
+ * the same size
+ */
+ if (it == monitors.end()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/animation.hpp"
+
+#include "drawtypes/label.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ void animation::add(label_t&& frame) {
+ m_frames.emplace_back(forward<decltype(frame)>(frame));
+ m_framecount = m_frames.size();
+ m_frame = m_framecount - 1;
+ }
+
+ label_t animation::get() const {
+ return m_frames[m_frame];
+ }
+
+ unsigned int animation::framerate() const {
+ return m_framerate_ms;
+ }
+
+ animation::operator bool() const {
+ return !m_frames.empty();
+ }
+
+ void animation::increment() {
+ auto tmp = m_frame.load();
+ ++tmp;
+ tmp %= m_framecount;
+
+ m_frame = tmp;
+ }
+
+ /**
+ * Create an animation by loading values
+ * from the configuration
+ */
+ animation_t load_animation(const config& conf, const string& section, string name, bool required) {
+ vector<label_t> vec;
+ vector<string> frames;
+
+ name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+ auto anim_defaults = load_optional_label(conf, section, name);
+
+ if (required) {
+ frames = conf.get_list(section, name);
+ } else {
+ frames = conf.get_list(section, name, {});
+ }
+
+ for (size_t i = 0; i < frames.size(); i++) {
+ vec.emplace_back(forward<label_t>(load_optional_label(conf, section, name + "-" + to_string(i), frames[i])));
+ vec.back()->copy_undefined(anim_defaults);
+ }
+
+ auto framerate = conf.get(section, name + "-framerate", 1000);
+
+ return std::make_shared<animation>(move(vec), framerate);
+ }
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/iconset.hpp"
+
+#include <algorithm>
+
+POLYBAR_NS
+
+namespace drawtypes {
+ void iconset::add(string id, label_t&& icon) {
+ m_icons.emplace(id, forward<decltype(icon)>(icon));
+ }
+
+ bool iconset::has(const string& id) {
+ return m_icons.find(id) != m_icons.end();
+ }
+
+ label_t iconset::get(const string& id, const string& fallback_id, bool fuzzy_match) {
+ // Try to match exactly first
+ auto icon = m_icons.find(id);
+ if (icon != m_icons.end()) {
+ return icon->second;
+ }
+
+ // If fuzzy matching is turned on, try that first before returning the fallback.
+ if (fuzzy_match) {
+ // works by finding the *longest* matching icon to the given workspace id
+ size_t max_size = -1;
+ label_t max_label;
+
+ for (auto const& icon : m_icons) {
+ if (id.find(icon.first) != std::string::npos) {
+ if (icon.first.length() > max_size || !max_label) {
+ max_size = icon.first.length();
+ max_label = icon.second;
+ }
+ }
+ }
+
+ if (max_label) {
+ return max_label;
+ }
+ }
+
+ return m_icons.find(fallback_id)->second;
+ }
+
+ iconset::operator bool() {
+ return !m_icons.empty();
+ }
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/label.hpp"
+
+#include <cmath>
+#include <utility>
+
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ /**
+ * Gets the text from the label as it should be rendered
+ *
+ * Here tokens are replaced with values and minlen and maxlen properties are applied
+ */
+ string label::get() const {
+ const size_t len = string_util::char_len(m_tokenized);
+ if (len >= m_minlen) {
+ string text = m_tokenized;
+ if (m_maxlen > 0 && len > m_maxlen) {
+ if (m_ellipsis) {
+ text = string_util::utf8_truncate(std::move(text), m_maxlen - 3) + "...";
+ } else {
+ text = string_util::utf8_truncate(std::move(text), m_maxlen);
+ }
+ }
+ return text;
+ }
+
+ const size_t num_fill_chars = m_minlen - len;
+ size_t right_fill_len = 0;
+ size_t left_fill_len = 0;
+ if (m_alignment == alignment::RIGHT) {
+ left_fill_len = num_fill_chars;
+ } else if (m_alignment == alignment::LEFT) {
+ right_fill_len = num_fill_chars;
+ } else {
+ right_fill_len = std::ceil(num_fill_chars / 2.0);
+ left_fill_len = right_fill_len;
+ // The text is positioned one character to the left if we can't perfectly center it
+ if (len + left_fill_len + right_fill_len > m_minlen) {
+ --left_fill_len;
+ }
+ }
+ return string(left_fill_len, ' ') + m_tokenized + string(right_fill_len, ' ');
+ }
+
+ label::operator bool() {
+ return !m_tokenized.empty();
+ }
+
+ label_t label::clone() {
+ vector<token> tokens;
+ if (!m_tokens.empty()) {
+ std::back_insert_iterator<decltype(tokens)> back_it(tokens);
+ std::copy(m_tokens.begin(), m_tokens.end(), back_it);
+ }
+ return std::make_shared<label>(m_text, m_foreground, m_background, m_underline, m_overline, m_font, m_padding,
+ m_margin, m_minlen, m_maxlen, m_alignment, m_ellipsis, move(tokens));
+ }
+
+ void label::clear() {
+ m_tokenized.clear();
+ }
+
+ void label::reset_tokens() {
+ m_tokenized = m_text;
+ }
+
+ void label::reset_tokens(const string& tokenized) {
+ m_tokenized = tokenized;
+ }
+
+ bool label::has_token(const string& token) const {
+ return m_tokenized.find(token) != string::npos;
+ }
+
+ void label::replace_token(const string& token, string replacement) {
+ if (!has_token(token)) {
+ return;
+ }
+
+ for (auto&& tok : m_tokens) {
+ string repl{replacement};
+ if (token == tok.token) {
+ if (tok.max != 0_z && string_util::char_len(repl) > tok.max) {
+ repl = string_util::utf8_truncate(std::move(repl), tok.max) + tok.suffix;
+ } else if (tok.min != 0_z && repl.length() < tok.min) {
+ if (tok.rpadding) {
+ repl.append(tok.min - repl.length(), ' ');
+ } else {
+ repl.insert(0_z, tok.min - repl.length(), tok.zpad ? '0' : ' ');
+ }
+ }
+
+ /*
+ * Only replace first occurence, so that the proper token objects can be used
+ */
+ m_tokenized = string_util::replace(m_tokenized, token, move(repl));
+ }
+ }
+ }
+
+ void label::replace_defined_values(const label_t& label) {
+ if (label->m_foreground.has_color()) {
+ m_foreground = label->m_foreground;
+ }
+ if (label->m_background.has_color()) {
+ m_background = label->m_background;
+ }
+ if (label->m_underline.has_color()) {
+ m_underline = label->m_underline;
+ }
+ if (label->m_overline.has_color()) {
+ m_overline = label->m_overline;
+ }
+ if (label->m_font != 0) {
+ m_font = label->m_font;
+ }
+ if (label->m_padding.left) {
+ m_padding.left = label->m_padding.left;
+ }
+ if (label->m_padding.right) {
+ m_padding.right = label->m_padding.right;
+ }
+ if (label->m_margin.left) {
+ m_margin.left = label->m_margin.left;
+ }
+ if (label->m_margin.right) {
+ m_margin.right = label->m_margin.right;
+ }
+ if (label->m_maxlen != 0_z) {
+ m_maxlen = label->m_maxlen;
+ m_ellipsis = label->m_ellipsis;
+ }
+ }
+
+ void label::copy_undefined(const label_t& label) {
+ if (!m_foreground.has_color() && label->m_foreground.has_color()) {
+ m_foreground = label->m_foreground;
+ }
+ if (!m_background.has_color() && label->m_background.has_color()) {
+ m_background = label->m_background;
+ }
+ if (!m_underline.has_color() && label->m_underline.has_color()) {
+ m_underline = label->m_underline;
+ }
+ if (!m_overline.has_color() && label->m_overline.has_color()) {
+ m_overline = label->m_overline;
+ }
+ if (m_font == 0 && label->m_font != 0) {
+ m_font = label->m_font;
+ }
+ if (!m_padding.left && label->m_padding.left) {
+ m_padding.left = label->m_padding.left;
+ }
+ if (!m_padding.right && label->m_padding.right) {
+ m_padding.right = label->m_padding.right;
+ }
+ if (!m_margin.left && label->m_margin.left) {
+ m_margin.left = label->m_margin.left;
+ }
+ if (!m_margin.right && label->m_margin.right) {
+ m_margin.right = label->m_margin.right;
+ }
+ if (m_maxlen == 0_z && label->m_maxlen != 0_z) {
+ m_maxlen = label->m_maxlen;
+ m_ellipsis = label->m_ellipsis;
+ }
+ }
+
+ /**
+ * Create a label by loading values from the configuration
+ */
+ label_t load_label(const config& conf, const string& section, string name, bool required, string def) {
+ vector<token> tokens;
+ size_t start, end, pos;
+
+ name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+ string text;
+
+ struct side_values padding {
+ }, margin{};
+
+ if (required) {
+ text = conf.get(section, name);
+ } else {
+ text = conf.get(section, name, def);
+ }
+
+ const auto get_left_right = [&](string&& key) {
+ const auto parse_or_throw = [&](const string& key, spacing_val default_value) {
+ try {
+ return conf.get(section, key, default_value);
+ } catch (const std::exception& err) {
+ throw application_error(
+ sstream() << "Failed to set " << section << "." << key << " (reason: " << err.what() << ")");
+ }
+ };
+
+ auto value = parse_or_throw(key, ZERO_SPACE);
+ auto left = parse_or_throw(key + "-left", value);
+ auto right = parse_or_throw(key + "-right", value);
+ return side_values{left, right};
+ };
+
+ padding = get_left_right(name + "-padding");
+ margin = get_left_right(name + "-margin");
+
+ string line{text};
+
+ while ((start = line.find('%')) != string::npos && (end = line.find('%', start + 1)) != string::npos) {
+ auto token_str = line.substr(start, end - start + 1);
+
+ // ignore false positives
+ // lemonbar tags %{...}
+ // trailing percentage signs %token%%
+ if (token_str.find_first_of("abcdefghijklmnopqrstuvwxyz") != 1) {
+ line.erase(0, end);
+ continue;
+ }
+
+ line.erase(start, end - start + 1);
+ tokens.emplace_back(token{token_str, 0_z, 0_z});
+ auto& token = tokens.back();
+
+ // find min delimiter
+ if ((pos = token_str.find(':')) == string::npos) {
+ continue;
+ }
+
+ // strip min/max specifiers from the label string token
+ token.token = token_str.substr(0, pos) + '%';
+ text = string_util::replace(text, token_str, token.token);
+
+ try {
+ if (token_str[pos + 1] == '-') {
+ token.rpadding = true;
+ pos++;
+ }
+ token.min = std::stoul(&token_str[pos + 1], nullptr, 10);
+ // When the number starts with 0 the string is 0-padded
+ token.zpad = token_str[pos + 1] == '0';
+ } catch (const std::invalid_argument& err) {
+ continue;
+ }
+
+ // find max delimiter
+ if ((pos = token_str.find(':', pos + 1)) == string::npos) {
+ continue;
+ }
+
+ try {
+ token.max = std::stoul(&token_str[pos + 1], nullptr, 10);
+ } catch (const std::invalid_argument& err) {
+ continue;
+ }
+
+ // ignore max lengths less than min
+ if (token.max < token.min) {
+ token.max = 0_z;
+ }
+
+ // find suffix delimiter
+ if ((pos = token_str.find(':', pos + 1)) != string::npos) {
+ token.suffix = token_str.substr(pos + 1, token_str.size() - pos - 2);
+ }
+ }
+ size_t minlen = conf.get(section, name + "-minlen", 0_z);
+ string alignment_conf_value = conf.get(section, name + "-alignment", "left"s);
+ alignment label_alignment;
+ if (alignment_conf_value == "right") {
+ label_alignment = alignment::RIGHT;
+ } else if (alignment_conf_value == "left") {
+ label_alignment = alignment::LEFT;
+ } else if (alignment_conf_value == "center") {
+ label_alignment = alignment::CENTER;
+ } else {
+ throw application_error(sstream() << "Label " << section << "." << name << " has invalid alignment "
+ << alignment_conf_value << ", expecting one of: right, left, center.");
+ }
+
+ size_t maxlen = conf.get(section, name + "-maxlen", 0_z);
+ if (maxlen > 0 && maxlen < minlen) {
+ throw application_error(sstream() << "Label " << section << "." << name << " has maxlen " << maxlen
+ << " which is smaller than minlen " << minlen);
+ }
+ bool ellipsis = conf.get(section, name + "-ellipsis", true);
+
+ // clang-format off
+ if (ellipsis && maxlen > 0 && maxlen < 3) {
+ throw application_error(sstream() << "Label " << section << "." << name << " has maxlen " << maxlen
+ << ", which is smaller than length of ellipsis (3)");
+ }
+ // clang-format on
+
+ // clang-format off
+ return std::make_shared<label>(text,
+ conf.get(section, name + "-foreground", rgba{}),
+ conf.get(section, name + "-background", rgba{}),
+ conf.get(section, name + "-underline", rgba{}),
+ conf.get(section, name + "-overline", rgba{}),
+ conf.get(section, name + "-font", 0),
+ padding,
+ margin,
+ minlen,
+ maxlen,
+ label_alignment,
+ ellipsis,
+ move(tokens));
+ // clang-format on
+ }
+
+ /**
+ * Create a label by loading optional values from the configuration
+ */
+ label_t load_optional_label(const config& conf, string section, string name, string def) {
+ return load_label(conf, section, move(name), false, move(def));
+ }
+
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/layouticonset.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ layouticonset::layouticonset(label_t&& default_icon) : m_default_icon(default_icon) {}
+
+ bool layouticonset::add(const string& layout, const string& variant, label_t&& icon) {
+ if (layout == VARIANT_ANY && variant == VARIANT_ANY) {
+ return false;
+ }
+ m_layout_icons.emplace_back(layout, variant, icon);
+ return true;
+ }
+
+ label_t layouticonset::get(const string& layout, const string& variant) const {
+ // The layout, variant are matched against defined icons in that order:
+ // 1. perfect match on layout and perfect match on variant (ex: us;Colemak;<icon>)
+ // 2. perfect match on layout and case insensitive search on variant (ex: us;coLEmAk;<icon>)
+ // 3. perfect match on layout and the any variant '_' (ex: us;<icon> or us;_;<icon>)
+ // 4. any layout for icon and perfect match on variant (ex: _;Colemak;<icon>)
+ // 5. any layout for icon and case insensitive search on variant (ex: _;coLEmAk;<icon>)
+ // 6. no match at all => default icon if defined
+
+ /*
+ * The minimal case that was matched.
+ * Once a case is matched, this is updated and no case with the same or higher number can be matched again.
+ */
+ int min_case = 6;
+
+ // Case 6: initializing with default
+ label_t icon = m_default_icon;
+
+ for (auto it : m_layout_icons) {
+ const string& icon_layout = std::get<0>(it);
+ const string& icon_variant = std::get<1>(it);
+ label_t icon_label = std::get<2>(it);
+
+ bool is_variant_match = icon_variant == variant;
+
+ bool is_variant_any = icon_variant == VARIANT_ANY;
+
+ bool is_variant_match_fuzzy =
+ !is_variant_any && !icon_variant.empty() && string_util::contains_ignore_case(variant, icon_variant);
+
+ // Which of the 6 match cases is matched here.
+ int current_case = 6;
+
+ if (icon_layout == layout) {
+ if (is_variant_match) {
+ // Case 1
+ current_case = 1;
+ } else if (is_variant_match_fuzzy) {
+ // Case 2
+ current_case = 2;
+ } else if (is_variant_any) {
+ // Case 3
+ current_case = 3;
+ }
+ } else if (icon_layout == VARIANT_ANY) {
+ if (is_variant_match) {
+ // Case 4
+ current_case = 4;
+ } else if (is_variant_match_fuzzy) {
+ // Case 5
+ current_case = 5;
+ }
+ }
+
+ /*
+ * We matched with a higher priority than before -> update icon.
+ */
+ if (current_case < min_case) {
+ icon = icon_label;
+ min_case = current_case;
+ }
+
+ if (current_case == 1) {
+ // Case 1: perfect match, we can break early
+ break;
+ }
+ }
+
+ return icon;
+ }
+
+ bool layouticonset::contains(const string& layout, const string& variant) const {
+ for (auto it : m_layout_icons) {
+ const string& icon_layout = std::get<0>(it);
+ const string& icon_variant = std::get<1>(it);
+ if (icon_layout == layout && icon_variant == variant) {
+ return true;
+ }
+ }
+ return false;
+ }
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/progressbar.hpp"
+
+#include <utility>
+
+#include "drawtypes/label.hpp"
+#include "utils/color.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ progressbar::progressbar(const bar_settings& bar, int width, string format)
+ : m_builder(std::make_unique<builder>(bar)), m_format(move(format)), m_width(width) {}
+
+ void progressbar::set_fill(label_t&& fill) {
+ m_fill = forward<decltype(fill)>(fill);
+ }
+
+ void progressbar::set_empty(label_t&& empty) {
+ m_empty = forward<decltype(empty)>(empty);
+ }
+
+ void progressbar::set_indicator(label_t&& indicator) {
+ if (!m_indicator && indicator.get()) {
+ m_width--;
+ }
+ m_indicator = forward<decltype(indicator)>(indicator);
+ }
+
+ void progressbar::set_gradient(bool mode) {
+ m_gradient = mode;
+ }
+
+ void progressbar::set_colors(vector<rgba>&& colors) {
+ m_colors = forward<decltype(colors)>(colors);
+
+ m_colorstep = m_colors.empty() ? 1 : m_width / m_colors.size();
+ }
+
+ string progressbar::output(float percentage) {
+ string output{m_format};
+
+ // Get fill/empty widths based on percentage
+ unsigned int perc = math_util::cap(percentage, 0.0f, 100.0f);
+ unsigned int fill_width = math_util::percentage_to_value(perc, m_width);
+ unsigned int empty_width = m_width - fill_width;
+
+ // Output fill icons
+ fill(perc, fill_width);
+ output = string_util::replace_all(output, "%fill%", m_builder->flush());
+
+ // Output indicator icon
+ m_builder->node(m_indicator);
+ output = string_util::replace_all(output, "%indicator%", m_builder->flush());
+
+ // Output empty icons
+ m_builder->node_repeat(m_empty, empty_width);
+ output = string_util::replace_all(output, "%empty%", m_builder->flush());
+
+ return output;
+ }
+
+ void progressbar::fill(unsigned int perc, unsigned int fill_width) {
+ if (m_colors.empty()) {
+ m_builder->node_repeat(m_fill, fill_width);
+ } else if (m_gradient) {
+ size_t color = 0;
+ for (size_t i = 0; i < fill_width; i++) {
+ if (i % m_colorstep == 0 && color < m_colors.size()) {
+ m_fill->m_foreground = m_colors[color++];
+ }
+ m_builder->node(m_fill);
+ }
+ } else {
+ size_t color = math_util::percentage_to_value<size_t>(perc, m_colors.size() - 1);
+ m_fill->m_foreground = m_colors[color];
+ m_builder->node_repeat(m_fill, fill_width);
+ }
+ }
+
+ /**
+ * Create a progressbar by loading values
+ * from the configuration
+ */
+ progressbar_t load_progressbar(const bar_settings& bar, const config& conf, const string& section, string name) {
+ // Remove the start and end tag from the name in case a format tag is passed
+ name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+ string format = "%fill%%indicator%%empty%";
+ unsigned int width;
+
+ if ((format = conf.get(section, name + "-format", format)).empty()) {
+ throw application_error("Invalid format defined at [" + section + "." + name + "]");
+ }
+ if ((width = conf.get<decltype(width)>(section, name + "-width")) < 1) {
+ throw application_error("Invalid width defined at [" + section + "." + name + "]");
+ }
+
+ auto pbar = std::make_shared<progressbar>(bar, width, format);
+ pbar->set_gradient(conf.get(section, name + "-gradient", true));
+ pbar->set_colors(conf.get_list(section, name + "-foreground", vector<rgba>{}));
+
+ label_t icon_empty;
+ label_t icon_fill;
+ label_t icon_indicator;
+
+ if (format.find("%empty%") != string::npos) {
+ icon_empty = load_label(conf, section, name + "-empty");
+ }
+ if (format.find("%fill%") != string::npos) {
+ icon_fill = load_label(conf, section, name + "-fill");
+ }
+ if (format.find("%indicator%") != string::npos) {
+ icon_indicator = load_label(conf, section, name + "-indicator");
+ }
+
+ // If a foreground/background color is defined for the indicator
+ // but not for the empty icon we use the bar's default colors to
+ // avoid color bleed
+ if (icon_empty && icon_indicator) {
+ if (icon_indicator->m_background.has_color() && !icon_empty->m_background.has_color()) {
+ icon_empty->m_background = bar.background;
+ }
+ if (icon_indicator->m_foreground.has_color() && !icon_empty->m_foreground.has_color()) {
+ icon_empty->m_foreground = bar.foreground;
+ }
+ }
+
+ pbar->set_empty(move(icon_empty));
+ pbar->set_fill(move(icon_fill));
+ pbar->set_indicator(move(icon_indicator));
+
+ return pbar;
+ }
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "drawtypes/ramp.hpp"
+
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+ void ramp::add(label_t&& icon) {
+ m_icons.emplace_back(forward<decltype(icon)>(icon));
+ }
+
+ void ramp::add(label_t&& icon, unsigned weight) {
+ while (weight--) {
+ m_icons.emplace_back(icon);
+ }
+ }
+
+ label_t ramp::get(size_t index) {
+ return m_icons[index];
+ }
+
+ label_t ramp::get_by_percentage(float percentage) {
+ size_t index = percentage * m_icons.size() / 100.0f;
+ return m_icons[math_util::cap<size_t>(index, 0, m_icons.size() - 1)];
+ }
+
+ label_t ramp::get_by_percentage_with_borders(int value, int min, int max) {
+ return get_by_percentage_with_borders(static_cast<float>(value), static_cast<float>(min), static_cast<float>(max));
+ }
+
+ label_t ramp::get_by_percentage_with_borders(float value, float min, float max) {
+ size_t index;
+ if (value <= min) {
+ index = 0;
+ } else if (value >= max) {
+ index = m_icons.size() - 1;
+ } else {
+ float percentage = math_util::percentage(value, min, max);
+ index = percentage * (m_icons.size() - 2) / 100.0f + 1;
+ index = math_util::cap<size_t>(index, 0, m_icons.size() - 1);
+ }
+ return m_icons[index];
+ }
+
+ ramp::operator bool() {
+ return !m_icons.empty();
+ }
+
+ /**
+ * Create a ramp by loading values
+ * from the configuration
+ */
+ ramp_t load_ramp(const config& conf, const string& section, string name, bool required) {
+ name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+ auto ramp_defaults = load_optional_label(conf, section, name);
+
+ vector<label_t> vec;
+ vector<string> icons;
+
+ if (required) {
+ icons = conf.get_list<string>(section, name);
+ } else {
+ icons = conf.get_list<string>(section, name, {});
+ }
+
+ for (size_t i = 0; i < icons.size(); i++) {
+ auto ramp_name = name + "-" + to_string(i);
+ auto icon = load_optional_label(conf, section, ramp_name, icons[i]);
+ icon->copy_undefined(ramp_defaults);
+
+ auto weight = conf.get(section, ramp_name + "-weight", 1U);
+ while (weight--) {
+ vec.emplace_back(icon);
+ }
+ }
+
+ return std::make_shared<drawtypes::ramp>(move(vec));
+ }
+} // namespace drawtypes
+
+POLYBAR_NS_END
--- /dev/null
+#include "events/signal_emitter.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+signal_receivers_t g_signal_receivers;
+
+/**
+ * Create instance
+ */
+signal_emitter::make_type signal_emitter::make() {
+ return static_cast<signal_emitter&>(*factory_util::singleton<signal_emitter>());
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "events/signal_receiver.hpp"
--- /dev/null
+#include "ipc/decoder.hpp"
+
+#include <cassert>
+#include <cstring>
+
+POLYBAR_NS
+
+namespace ipc {
+ decoder::decoder(const logger& logger, cb callback) : callback(callback), m_log(logger) {}
+
+ void decoder::on_read(const uint8_t* data, size_t size) {
+ if (state == state::CLOSED) {
+ throw error("Decoder is closed");
+ }
+
+ try {
+ process_data(data, size);
+ } catch (const error& e) {
+ close();
+ throw;
+ }
+ }
+
+ void decoder::process_data(const uint8_t* data, size_t size) {
+ m_log.trace("ipc: Received %zd bytes", size);
+
+ size_t buf_pos = 0;
+ size_t remain = size;
+
+ while (remain > 0) {
+ if (state == state::HEADER) {
+ ssize_t num_read = process_header_data(data + buf_pos, remain);
+ assert(num_read > 0);
+ assert(remain >= (size_t)num_read);
+
+ buf_pos += num_read;
+ remain -= num_read;
+
+ /*
+ * If an empty message arrives, we need to explicitly trigger this because there is no further data that would
+ * call process_msg_data.
+ */
+ if (remain == 0 && state == state::PAYLOAD && to_read_buf == 0) {
+ ssize_t num_read_data = process_msg_data(data + buf_pos, remain);
+ assert(num_read_data == 0);
+ (void)num_read_data;
+ }
+
+ } else {
+ assert(to_read_header == 0);
+ ssize_t num_read = process_msg_data(data + buf_pos, remain);
+ assert(num_read > 0);
+ assert(remain >= (size_t)num_read);
+
+ buf_pos += num_read;
+ remain -= num_read;
+ }
+ }
+ }
+
+ void decoder::close() noexcept {
+ state = state::CLOSED;
+ }
+
+ bool decoder::closed() const {
+ return state == state::CLOSED;
+ }
+
+ /**
+ * If we are waiting for header data, read as many bytes as possible from the given buffer.
+ *
+ * @return Number of bytes processed.
+ * @throws decoder::error on message errors
+ */
+ ssize_t decoder::process_header_data(const uint8_t* data, size_t size) {
+ assert(state == state::HEADER);
+ assert(to_read_header > 0);
+
+ size_t num_read = std::min(size, to_read_header);
+
+ std::copy(data, data + num_read, header.d + HEADER_SIZE - to_read_header);
+ to_read_header -= num_read;
+
+ if (to_read_header == 0) {
+ uint8_t version = header.s.version;
+ uint32_t msg_size = header.s.size;
+
+ m_log.trace(
+ "Received full ipc header (magic=%.*s version=%d size=%zd)", MAGIC.size(), header.s.magic, version, msg_size);
+
+ if (memcmp(header.s.magic, MAGIC.data(), MAGIC.size()) != 0) {
+ throw error("Invalid magic header, expected '" + MAGIC_STR + "', got '" +
+ string(reinterpret_cast<const char*>(header.s.magic), MAGIC.size()) + "'");
+ }
+ if (version != VERSION) {
+ throw error("Unsupported message format version " + to_string(version));
+ }
+
+ assert(buf.empty());
+ state = state::PAYLOAD;
+ to_read_buf = msg_size;
+ }
+
+ return num_read;
+ }
+
+ /**
+ * If we are waiting for message data, read as many bytes as possible from the given buffer.
+ *
+ * @return Number of bytes processed.
+ * @throws decoder::error on message errors
+ */
+ ssize_t decoder::process_msg_data(const uint8_t* data, size_t size) {
+ assert(state == state::PAYLOAD);
+
+ size_t num_read = std::min(size, to_read_buf);
+
+ buf.reserve(buf.size() + num_read);
+ for (size_t i = 0; i < num_read; i++) {
+ buf.push_back(data[i]);
+ }
+ to_read_buf -= num_read;
+
+ if (to_read_buf == 0) {
+ callback(header.s.version, header.s.type, buf);
+
+ state = state::HEADER;
+ to_read_header = HEADER_SIZE;
+ buf.clear();
+ }
+
+ return num_read;
+ }
+} // namespace ipc
+POLYBAR_NS_END
--- /dev/null
+#include "ipc/encoder.hpp"
+
+#include <cassert>
+#include <cstring>
+
+POLYBAR_NS
+
+namespace ipc {
+ template <typename V>
+ vector<uint8_t> encode(const type_t type, const V& payload) {
+ size_t total_size = HEADER_SIZE + payload.size();
+ std::vector<uint8_t> data(total_size);
+
+ auto* msg_header = reinterpret_cast<header*>(data.data());
+ std::copy(MAGIC.begin(), MAGIC.end(), msg_header->s.magic);
+ msg_header->s.version = VERSION;
+ msg_header->s.size = payload.size();
+ msg_header->s.type = type;
+
+ std::copy(payload.begin(), payload.end(), data.begin() + HEADER_SIZE);
+ return data;
+ }
+
+ vector<uint8_t> encode(const type_t type, const vector<uint8_t>& payload) {
+ return encode<vector<uint8_t>>(type, payload);
+ }
+
+ vector<uint8_t> encode(const type_t type, const string& payload) {
+ return encode<string>(type, payload);
+ }
+
+} // namespace ipc
+POLYBAR_NS_END
--- /dev/null
+#include "ipc/ipc.hpp"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cassert>
+
+#include "components/eventloop.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "ipc/encoder.hpp"
+#include "ipc/util.hpp"
+#include "utils/env.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+using namespace eventloop;
+
+namespace ipc {
+
+ /**
+ * Message types
+ */
+ static constexpr const char* ipc_command_prefix{"cmd:"};
+ static constexpr const char* ipc_hook_prefix{"hook:"};
+ static constexpr const char* ipc_action_prefix{"action:"};
+
+ /**
+ * Create instance
+ */
+ ipc::make_type ipc::make(loop& loop) {
+ return std::make_unique<ipc>(signal_emitter::make(), logger::make(), loop);
+ }
+
+ /**
+ * Construct ipc handler
+ */
+ ipc::ipc(signal_emitter& emitter, const logger& logger, loop& loop)
+ : m_sig(emitter), m_log(logger), m_loop(loop), m_socket(loop.handle<PipeHandle>()) {
+ m_pipe_path = string_util::replace(PATH_MESSAGING_FIFO, "%pid%", to_string(getpid()));
+
+ if (file_util::exists(m_pipe_path) && unlink(m_pipe_path.c_str()) == -1) {
+ throw system_error("Failed to remove ipc channel");
+ }
+ if (mkfifo(m_pipe_path.c_str(), 0600) == -1) {
+ throw system_error("Failed to create ipc channel");
+ }
+ m_log.info("Created legacy ipc fifo at '%s'", m_pipe_path);
+
+ ipc_pipe = make_unique<fifo>(m_loop, *this, m_pipe_path);
+
+ string sock_path = get_socket_path(getpid());
+
+ m_log.info("Opening ipc socket at '%s'", sock_path);
+ m_log.notice("Listening for IPC messages (PID: %d)", getpid());
+ m_socket->bind(sock_path);
+ m_socket->listen(
+ 4, [this]() { on_connection(); },
+ [this](const auto& e) {
+ m_log.err("libuv error while listening to IPC socket: %s", uv_strerror(e.status));
+ m_socket->close();
+ });
+ }
+
+ /**
+ * Deconstruct ipc handler
+ */
+ ipc::~ipc() {
+ m_log.trace("ipc: Removing named pipe at: %s", m_pipe_path);
+ if (unlink(m_pipe_path.c_str()) == -1) {
+ m_log.err("Failed to delete ipc named pipe: %s", strerror(errno));
+ }
+ }
+
+ string ipc::get_socket_path(int pid) {
+ return ensure_runtime_path() + "/ipc." + to_string(pid) + ".sock";
+ }
+
+ bool ipc::trigger_ipc(v0::ipc_type type, const string& msg) {
+ switch (type) {
+ case v0::ipc_type::CMD:
+ m_log.info("Received ipc command: '%s'", msg);
+ return m_sig.emit(signals::ipc::command{msg});
+ case v0::ipc_type::ACTION:
+ m_log.info("Received ipc action: '%s'", msg);
+ return m_sig.emit(signals::ipc::action{msg});
+ }
+
+ assert(false);
+ return false;
+ }
+
+ void ipc::trigger_legacy_ipc(const string& msg) {
+ m_log.info("Received ipc message: '%s'", msg);
+ if (msg.find(ipc_command_prefix) == 0) {
+ m_sig.emit(signals::ipc::command{msg.substr(strlen(ipc_command_prefix))});
+ } else if (msg.find(ipc_hook_prefix) == 0) {
+ m_sig.emit(signals::ipc::hook{msg.substr(strlen(ipc_hook_prefix))});
+ } else if (msg.find(ipc_action_prefix) == 0) {
+ m_sig.emit(signals::ipc::action{msg.substr(strlen(ipc_action_prefix))});
+ } else {
+ m_log.warn("Received unknown ipc message: (payload=%s)", msg);
+ }
+ }
+
+ void ipc::on_connection() {
+ auto connection = make_unique<ipc::connection>(
+ m_loop, [this](ipc::connection& c, uint8_t, type_t type, const vector<uint8_t>& msg) {
+ vector<uint8_t> response;
+
+ if (type == to_integral(v0::ipc_type::ACTION) || type == to_integral(v0::ipc_type::CMD)) {
+ auto ipc_type = static_cast<v0::ipc_type>(type);
+ string str;
+ str.insert(str.end(), msg.begin(), msg.end());
+ if (trigger_ipc(ipc_type, str)) {
+ response = encode(TYPE_OK);
+ } else {
+ response = encode(TYPE_ERR, "Error while executing ipc message, see polybar log for details.");
+ }
+ } else {
+ response = encode(TYPE_ERR, "Unrecognized IPC message type " + to_string(type));
+ }
+ c.client_pipe->write(
+ response, [this, &c]() { remove_client(c); },
+ [this, &c](const auto& e) {
+ m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status));
+ remove_client(c);
+ });
+ });
+
+ auto& c = *connection;
+ m_socket->accept(*c.client_pipe);
+
+ c.client_pipe->read_start(
+ [this, &c](const auto& e) {
+ try {
+ c.dec.on_read(reinterpret_cast<const uint8_t*>(e.data), e.len);
+ } catch (const decoder::error& e) {
+ m_log.err("ipc: Failed to decode IPC message (reason: %s)", e.what());
+
+ c.client_pipe->write(
+ encode(TYPE_ERR, "Invalid binary message format: "s + e.what()), [this, &c]() { remove_client(c); },
+ [this, &c](const auto& e) {
+ m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status));
+ remove_client(c);
+ });
+ }
+ },
+ [this, &c]() { remove_client(c); },
+ [this, &c](const auto& e) {
+ m_log.err("ipc: libuv error while listening to IPC socket: %s", uv_strerror(e.status));
+ remove_client(c);
+ });
+
+ connections.emplace(std::move(connection));
+ m_log.info("ipc: New connection (%d clients)", connections.size());
+ }
+
+ void ipc::remove_client(connection& conn) {
+ connections.erase(connections.find(conn));
+ }
+
+ ipc::connection::connection(loop& loop, cb msg_callback)
+ : client_pipe(loop.handle<PipeHandle>())
+ , dec(logger::make(), [this, msg_callback](uint8_t version, auto type, const auto& msg) {
+ msg_callback(*this, version, type, msg);
+ }) {}
+
+ ipc::connection::~connection() {
+ client_pipe->close();
+ }
+
+ ipc::fifo::fifo(loop& loop, ipc& ipc, const string& path) : pipe_handle(loop.handle<PipeHandle>()) {
+ int fd{};
+ if ((fd = open(path.c_str(), O_RDONLY | O_NONBLOCK)) == -1) {
+ throw system_error("Failed to open pipe '" + path + "'");
+ }
+
+ pipe_handle->open(fd);
+ pipe_handle->read_start([&ipc](const auto& e) mutable { ipc.receive_data(string(e.data, e.len)); },
+ [&ipc]() { ipc.receive_eof(); },
+ [&ipc](const auto& e) mutable {
+ ipc.m_log.err("libuv error while listening to IPC channel: %s", uv_strerror(e.status));
+ ipc.ipc_pipe.reset();
+ });
+ }
+
+ ipc::fifo::~fifo() {
+ pipe_handle->close();
+ }
+
+ /**
+ * Receive parts of an IPC message
+ */
+ void ipc::receive_data(const string& buf) {
+ m_pipe_buffer += buf;
+
+ m_log.warn("Using the named pipe at '%s' for ipc is deprecated, always use 'polybar-msg'", m_pipe_path);
+ }
+
+ /**
+ * Called once the end of the message arrives.
+ */
+ void ipc::receive_eof() {
+ ipc_pipe = make_unique<fifo>(m_loop, *this, m_pipe_path);
+
+ if (m_pipe_buffer.empty()) {
+ return;
+ }
+
+ trigger_legacy_ipc(string_util::trim(std::move(m_pipe_buffer), '\n'));
+ m_pipe_buffer.clear();
+ }
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#include "ipc/util.hpp"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace ipc {
+
+ static constexpr auto SUFFIX = ".sock";
+ static constexpr auto XDG_RUNTIME_DIR = "XDG_RUNTIME_DIR";
+
+ string get_runtime_path() {
+ if (env_util::has(XDG_RUNTIME_DIR)) {
+ return env_util::get("XDG_RUNTIME_DIR") + "/polybar";
+ } else {
+ return "/tmp/polybar-" + to_string(getuid());
+ }
+ return env_util::get("XDG_RUNTIME_DIR", "/tmp") + "/polybar";
+ }
+
+ string ensure_runtime_path() {
+ string runtime_path = get_runtime_path();
+ if (!file_util::exists(runtime_path) && mkdir(runtime_path.c_str(), 0700) == -1) {
+ // It's possible the folder was created in the meantime, we have to check again.
+ if (!file_util::exists(runtime_path)) {
+ throw system_error("Failed to create ipc socket folders");
+ }
+ }
+
+ return runtime_path;
+ }
+
+ string get_socket_path(const string& pid_string) {
+ return get_runtime_path() + "/ipc." + pid_string + SUFFIX;
+ }
+
+ string get_socket_path(int pid) {
+ return get_socket_path(to_string(pid));
+ }
+
+ string get_glob_socket_path() {
+ return get_socket_path("*");
+ }
+
+ int get_pid_from_socket(const string& path) {
+ if (!string_util::ends_with(path, SUFFIX)) {
+ return -1;
+ }
+
+ auto stripped = path.substr(0, path.size() - strlen(SUFFIX));
+ auto p = stripped.rfind('.');
+
+ if (p == string::npos) {
+ return -1;
+ }
+
+ try {
+ return std::stoi(stripped.substr(p + 1));
+ } catch (...) {
+ return -1;
+ }
+ }
+} // namespace ipc
+
+POLYBAR_NS_END
--- /dev/null
+#include "components/bar.hpp"
+#include "components/command_line.hpp"
+#include "components/config.hpp"
+#include "components/config_parser.hpp"
+#include "components/controller.hpp"
+#include "ipc/ipc.hpp"
+#include "utils/env.hpp"
+#include "utils/inotify.hpp"
+#include "utils/process.hpp"
+#include "x11/connection.hpp"
+
+using namespace polybar;
+using namespace eventloop;
+
+int main(int argc, char** argv) {
+ // clang-format off
+ const command_line::options opts{
+ command_line::option{"-h", "--help", "Display this help and exit"},
+ command_line::option{"-v", "--version", "Display build details and exit"},
+ command_line::option{"-l", "--log", "Set the logging verbosity (default: notice)", "LEVEL", {"error", "warning", "notice", "info", "trace"}},
+ command_line::option{"-q", "--quiet", "Be quiet (will override -l)"},
+ command_line::option{"-c", "--config", "Path to the configuration file", "FILE"},
+ command_line::option{"-r", "--reload", "Reload when the configuration has been modified"},
+ command_line::option{"-d", "--dump", "Print value of PARAM in bar section and exit", "PARAM"},
+ command_line::option{"-m", "--list-monitors", "Print list of available monitors and exit (Removes cloned monitors)"},
+ command_line::option{"-M", "--list-all-monitors", "Print list of all available monitors (Including cloned monitors) and exit"},
+ command_line::option{"-w", "--print-wmname", "Print the generated WM_NAME and exit"},
+ command_line::option{"-s", "--stdout", "Output data to stdout instead of drawing it to the X window"},
+ command_line::option{"-p", "--png", "Save png snapshot to FILE after running for 3 seconds", "FILE"},
+ };
+ // clang-format on
+
+ unsigned char exit_code{EXIT_SUCCESS};
+ bool reload{false};
+
+ logger& logger{const_cast<decltype(logger)>(logger::make(loglevel::NOTICE))};
+
+ try {
+ //==================================================
+ // Parse command line arguments
+ //==================================================
+ string scriptname{argv[0]};
+ vector<string> args{argv + 1, argv + argc};
+
+ command_line::parser::make_type cli{command_line::parser::make(move(scriptname), move(opts))};
+ cli->process_input(args);
+
+ if (cli->has("quiet")) {
+ logger.verbosity(loglevel::ERROR);
+ } else if (cli->has("log")) {
+ logger.verbosity(logger::parse_verbosity(cli->get("log")));
+ }
+
+ if (cli->has("help")) {
+ cli->usage();
+ return EXIT_SUCCESS;
+ } else if (cli->has("version")) {
+ print_build_info(version_details(args));
+ return EXIT_SUCCESS;
+ }
+
+ loop loop{};
+
+ //==================================================
+ // Connect to X server
+ //==================================================
+ auto xcb_error = 0;
+ auto xcb_screen = 0;
+ auto xcb_connection = xcb_connect(nullptr, &xcb_screen);
+
+ if (xcb_connection == nullptr) {
+ throw application_error("A connection to X could not be established...");
+ } else if ((xcb_error = xcb_connection_has_error(xcb_connection))) {
+ throw application_error("X connection error... (what: " + connection::error_str(xcb_error) + ")");
+ }
+
+ connection& conn{connection::make(xcb_connection, xcb_screen)};
+ conn.ensure_event_mask(conn.root(), XCB_EVENT_MASK_PROPERTY_CHANGE);
+
+ //==================================================
+ // List available XRandR entries
+ //==================================================
+ if (cli->has("list-monitors") || cli->has("list-all-monitors")) {
+ bool purge_clones = !cli->has("list-all-monitors");
+ auto monitors = randr_util::get_monitors(conn, true, purge_clones);
+ for (auto&& mon : monitors) {
+ if (mon->output == XCB_NONE) {
+ printf("%s: %ix%i+%i+%i (no output%s)\n", mon->name.c_str(), mon->w, mon->h, mon->x, mon->y,
+ mon->primary ? ", primary" : "");
+ } else {
+ printf("%s: %ix%i+%i+%i%s\n", mon->name.c_str(), mon->w, mon->h, mon->x, mon->y,
+ mon->primary ? " (primary)" : "");
+ }
+ }
+ return EXIT_SUCCESS;
+ }
+
+ //==================================================
+ // Load user configuration
+ //==================================================
+ string confpath;
+
+ // Make sure a bar name is passed in
+ if (cli->has(1)) {
+ fprintf(stderr, "Unrecognized argument \"%s\"\n", cli->get(1).c_str());
+ cli->usage();
+ return EXIT_FAILURE;
+ }
+
+ if (cli->has("config")) {
+ confpath = cli->get("config");
+ } else {
+ confpath = file_util::get_config_path();
+
+ if (string_util::ends_with(confpath, "/config")) {
+ logger::make().warn(
+ "Naming your configuration file 'config' is deprecated, the expected name is 'config.ini'.");
+ }
+ }
+
+ if (confpath.empty()) {
+ throw application_error("Define configuration using --config=PATH");
+ }
+
+ string barname;
+ if (cli->has(0)) {
+ barname = cli->get(0);
+ }
+
+ config_parser parser{logger, move(confpath)};
+ config conf = parser.parse(move(barname));
+
+ //==================================================
+ // Dump requested data
+ //==================================================
+ if (cli->has("dump")) {
+ printf("%s\n", conf.get(conf.section(), cli->get("dump")).c_str());
+ return EXIT_SUCCESS;
+ }
+ if (cli->has("print-wmname")) {
+ printf("%s\n", bar::make(loop, conf, true)->settings().wmname.c_str());
+ return EXIT_SUCCESS;
+ }
+
+ //==================================================
+ // Create controller and run application
+ //==================================================
+ unique_ptr<ipc::ipc> ipc{};
+
+ if (conf.get(conf.section(), "enable-ipc", false)) {
+ try {
+ ipc = ipc::ipc::make(loop);
+ } catch (const std::exception& e) {
+ ipc.reset();
+ logger.err("Disabling IPC channels due to error: %s", e.what());
+ }
+ }
+
+ auto ctrl = controller::make((bool)ipc, loop, conf);
+
+ if (!ctrl->run(cli->has("stdout"), cli->get("png"), cli->has("reload"))) {
+ reload = true;
+ }
+ } catch (const exception& err) {
+ logger.err("Uncaught exception, shutting down: %s", err.what());
+ exit_code = EXIT_FAILURE;
+ }
+
+ logger.info("Waiting for spawned processes to end");
+ while (process_util::notify_childprocess()) {
+ ;
+ }
+
+ if (reload) {
+ logger.info("Re-launching application...");
+ process_util::exec(move(argv[0]), move(argv));
+ }
+
+ logger.info("Reached end of application...");
+ return exit_code;
+}
--- /dev/null
+#include "modules/alsa.hpp"
+
+#include "adapters/alsa/control.hpp"
+#include "adapters/alsa/generic.hpp"
+#include "adapters/alsa/mixer.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "settings.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+using namespace alsa;
+
+namespace modules {
+ template class module<alsa_module>;
+
+ alsa_module::alsa_module(const bar_settings& bar, string name_, const config& config)
+ : event_module<alsa_module>(bar, move(name_), config) {
+ if (m_handle_events) {
+ m_router->register_action(EVENT_DEC, [this]() { action_dec(); });
+ m_router->register_action(EVENT_INC, [this]() { action_inc(); });
+ m_router->register_action(EVENT_TOGGLE, [this]() { action_toggle(); });
+ }
+
+ // Load configuration values
+ m_mapped = m_conf.get(name(), "mapped", m_mapped);
+ m_interval = m_conf.get(name(), "interval", m_interval);
+
+ auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s);
+ auto speaker_mixer_name = m_conf.get(name(), "speaker-mixer", ""s);
+ auto headphone_mixer_name = m_conf.get(name(), "headphone-mixer", ""s);
+
+ // m_soundcard_name: Master Soundcard Name
+ // s_soundcard_name: Speaker Soundcard Name
+ // h_soundcard_name: Headphone Soundcard Name
+ auto m_soundcard_name = m_conf.get(name(), "master-soundcard", "default"s);
+ auto s_soundcard_name = m_conf.get(name(), "speaker-soundcard", "default"s);
+ auto h_soundcard_name = m_conf.get(name(), "headphone-soundcard", "default"s);
+
+ if (!headphone_mixer_name.empty()) {
+ m_headphoneid = m_conf.get<decltype(m_headphoneid)>(name(), "headphone-id");
+ }
+
+ if (string_util::compare(speaker_mixer_name, "master")) {
+ throw module_error("Master mixer is already defined");
+ }
+ if (string_util::compare(headphone_mixer_name, "master")) {
+ throw module_error("Master mixer is already defined");
+ }
+
+ // Setup mixers
+ try {
+ if (!master_mixer_name.empty()) {
+ m_mixer[mixer::MASTER].reset(new mixer_t::element_type{move(master_mixer_name), move(m_soundcard_name)});
+ }
+ if (!speaker_mixer_name.empty()) {
+ m_mixer[mixer::SPEAKER].reset(new mixer_t::element_type{move(speaker_mixer_name), move(s_soundcard_name)});
+ }
+ if (!headphone_mixer_name.empty()) {
+ m_mixer[mixer::HEADPHONE].reset(new mixer_t::element_type{move(headphone_mixer_name), move(h_soundcard_name)});
+ }
+ if (m_mixer[mixer::HEADPHONE]) {
+ m_ctrl[control::HEADPHONE].reset(new control_t::element_type{m_headphoneid});
+ }
+ if (m_mixer.empty()) {
+ throw module_error("No configured mixers");
+ }
+ } catch (const mixer_error& err) {
+ throw module_error(err.what());
+ } catch (const control_error& err) {
+ throw module_error(err.what());
+ }
+
+ // Add formats and elements
+ m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
+ m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
+
+ if (m_formatter->has(TAG_BAR_VOLUME)) {
+ m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
+ }
+ if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
+ m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
+ m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_RAMP_VOLUME)) {
+ m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME);
+ m_ramp_headphones = load_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
+ }
+ }
+
+ void alsa_module::teardown() {
+ m_mixer.clear();
+ m_ctrl.clear();
+ snd_config_update_free_global();
+ }
+
+ bool alsa_module::has_event() {
+ // Poll for mixer and control events
+ try {
+ if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) {
+ return true;
+ }
+ if (m_mixer[mixer::SPEAKER] && m_mixer[mixer::SPEAKER]->wait(25)) {
+ return true;
+ }
+ if (m_mixer[mixer::HEADPHONE] && m_mixer[mixer::HEADPHONE]->wait(25)) {
+ return true;
+ }
+ if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->wait(25)) {
+ return true;
+ }
+ } catch (const alsa_exception& e) {
+ m_log.err("%s: %s", name(), e.what());
+ }
+
+ return false;
+ }
+
+ bool alsa_module::update() {
+ // Consume pending events
+ if (m_mixer[mixer::MASTER]) {
+ m_mixer[mixer::MASTER]->process_events();
+ }
+ if (m_mixer[mixer::SPEAKER]) {
+ m_mixer[mixer::SPEAKER]->process_events();
+ }
+ if (m_mixer[mixer::HEADPHONE]) {
+ m_mixer[mixer::HEADPHONE]->process_events();
+ }
+ if (m_ctrl[control::HEADPHONE]) {
+ m_ctrl[control::HEADPHONE]->process_events();
+ }
+
+ // Get volume, mute and headphone state
+ m_volume = 100;
+ m_muted = false;
+ m_headphones = false;
+
+ try {
+ if (m_mixer[mixer::MASTER]) {
+ m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f
+ : m_mixer[mixer::MASTER]->get_volume() / 100.0f);
+ m_muted = m_muted || m_mixer[mixer::MASTER]->is_muted();
+ }
+ } catch (const alsa_exception& err) {
+ m_log.err("%s: Failed to query master mixer (%s)", name(), err.what());
+ }
+
+ try {
+ if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->test_device_plugged()) {
+ m_headphones = true;
+ m_volume = m_volume * (m_mapped ? m_mixer[mixer::HEADPHONE]->get_normalized_volume() / 100.0f
+ : m_mixer[mixer::HEADPHONE]->get_volume() / 100.0f);
+ m_muted = m_muted || m_mixer[mixer::HEADPHONE]->is_muted();
+ }
+ } catch (const alsa_exception& err) {
+ m_log.err("%s: Failed to query headphone mixer (%s)", name(), err.what());
+ }
+
+ try {
+ if (!m_headphones && m_mixer[mixer::SPEAKER]) {
+ m_volume = m_volume * (m_mapped ? m_mixer[mixer::SPEAKER]->get_normalized_volume() / 100.0f
+ : m_mixer[mixer::SPEAKER]->get_volume() / 100.0f);
+ m_muted = m_muted || m_mixer[mixer::SPEAKER]->is_muted();
+ }
+ } catch (const alsa_exception& err) {
+ m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what());
+ }
+
+ // Replace label tokens
+ if (m_label_volume) {
+ m_label_volume->reset_tokens();
+ m_label_volume->replace_token("%percentage%", to_string(m_volume));
+ }
+
+ if (m_label_muted) {
+ m_label_muted->reset_tokens();
+ m_label_muted->replace_token("%percentage%", to_string(m_volume));
+ }
+
+ return true;
+ }
+
+ string alsa_module::get_format() const {
+ return m_muted ? FORMAT_MUTED : FORMAT_VOLUME;
+ }
+
+ string alsa_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapper
+ // with the cmd handlers
+ string output{module::get_output()};
+
+ if (m_handle_events) {
+ auto click_middle = m_conf.get(name(), "click-middle", ""s);
+ auto click_right = m_conf.get(name(), "click-right", ""s);
+
+ if (!click_middle.empty()) {
+ m_builder->action(mousebtn::MIDDLE, click_middle);
+ }
+
+ if (!click_right.empty()) {
+ m_builder->action(mousebtn::RIGHT, click_right);
+ }
+
+ m_builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "");
+ m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+ }
+
+ m_builder->node(output);
+
+ return m_builder->flush();
+ }
+
+ bool alsa_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_BAR_VOLUME) {
+ builder->node(m_bar_volume->output(m_volume));
+ } else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones)) {
+ builder->node(m_ramp_volume->get_by_percentage(m_volume));
+ } else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones) {
+ builder->node(m_ramp_headphones->get_by_percentage(m_volume));
+ } else if (tag == TAG_LABEL_VOLUME) {
+ builder->node(m_label_volume);
+ } else if (tag == TAG_LABEL_MUTED) {
+ builder->node(m_label_muted);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void alsa_module::action_inc() {
+ change_volume(m_interval);
+ }
+
+ void alsa_module::action_dec() {
+ change_volume(-m_interval);
+ }
+
+ void alsa_module::action_toggle() {
+ if (!m_mixer[mixer::MASTER]) {
+ return;
+ }
+ const auto& mixers = get_mixers();
+ for (auto&& mixer : mixers) {
+ mixer->set_mute(m_muted || mixers[0]->is_muted());
+ }
+
+ action_epilogue(mixers);
+ }
+
+ void alsa_module::change_volume(int interval) {
+ if (!m_mixer[mixer::MASTER]) {
+ return;
+ }
+ const auto& mixers = get_mixers();
+ for (auto&& mixer : mixers) {
+ m_mapped ? mixer->set_normalized_volume(math_util::cap<float>(mixer->get_normalized_volume() + interval, 0, 100))
+ : mixer->set_volume(math_util::cap<float>(mixer->get_volume() + interval, 0, 100));
+ }
+ action_epilogue(mixers);
+ }
+
+ void alsa_module::action_epilogue(const vector<mixer_t>& mixers) {
+ for (auto&& mixer : mixers) {
+ if (mixer->wait(0)) {
+ mixer->process_events();
+ }
+ }
+ }
+
+ vector<mixer_t> alsa_module::get_mixers() {
+ vector<mixer_t> mixers;
+ bool headphones{m_headphones};
+
+ if (m_mixer[mixer::MASTER] && !m_mixer[mixer::MASTER]->get_name().empty()) {
+ mixers.emplace_back(std::make_shared<mixer_t::element_type>(
+ string{m_mixer[mixer::MASTER]->get_name()}, string{m_mixer[mixer::MASTER]->get_sound_card()}));
+ }
+ if (m_mixer[mixer::HEADPHONE] && !m_mixer[mixer::HEADPHONE]->get_name().empty() && headphones) {
+ mixers.emplace_back(std::make_shared<mixer_t::element_type>(
+ string{m_mixer[mixer::HEADPHONE]->get_name()}, string{m_mixer[mixer::HEADPHONE]->get_sound_card()}));
+ }
+ if (m_mixer[mixer::SPEAKER] && !m_mixer[mixer::SPEAKER]->get_name().empty() && !headphones) {
+ mixers.emplace_back(std::make_shared<mixer_t::element_type>(
+ string{m_mixer[mixer::SPEAKER]->get_name()}, string{m_mixer[mixer::SPEAKER]->get_sound_card()}));
+ }
+
+ return mixers;
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/backlight.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<backlight_module>;
+
+ void backlight_module::brightness_handle::filepath(const string& path) {
+ if (!file_util::exists(path)) {
+ throw module_error("The file '" + path + "' does not exist");
+ }
+ m_path = path;
+ }
+
+ float backlight_module::brightness_handle::read() const {
+ return std::strtof(file_util::contents(m_path).c_str(), nullptr);
+ }
+
+ backlight_module::backlight_module(const bar_settings& bar, string name_, const config& config)
+ : inotify_module<backlight_module>(bar, move(name_), config) {
+ m_router->register_action(EVENT_DEC, [this]() { action_dec(); });
+ m_router->register_action(EVENT_INC, [this]() { action_inc(); });
+ auto card = m_conf.get(name(), "card", ""s);
+ if (card.empty()) {
+ vector<string> backlight_card_names = file_util::list_files(string_util::replace(PATH_BACKLIGHT, "%card%", ""));
+ backlight_card_names.erase(std::remove_if(backlight_card_names.begin(), backlight_card_names.end(),
+ [&](const string& card) -> bool {
+ auto dir = string_util::replace(PATH_BACKLIGHT, "%card%", card);
+ return !(file_util::is_file(dir + "/actual_brightness") &&
+ file_util::is_file(dir + "/brightness") &&
+ file_util::is_file(dir + "/max_brightness"));
+ }),
+ backlight_card_names.end());
+
+ if (backlight_card_names.empty()) {
+ throw module_error("no viable default backlight found");
+ }
+ card = backlight_card_names.at(0);
+ if (backlight_card_names.size() > 1) {
+ m_log.warn("%s: multiple backlights found, using %s", name(), card);
+ } else {
+ m_log.info("%s: no backlight specified, using `%s`", name(), card);
+ }
+ }
+ // Get flag to check if we should add scroll handlers for changing value
+ m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+
+ m_scroll_interval = m_conf.get(name(), "scroll-interval", m_scroll_interval);
+
+ // Add formats and elements
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_BAR)) {
+ m_progressbar = load_progressbar(m_bar, m_conf, name(), TAG_BAR);
+ }
+ if (m_formatter->has(TAG_RAMP)) {
+ m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+ }
+
+ // Build path to the sysfs folder the current/maximum brightness values are located
+ m_path_backlight = string_util::replace(PATH_BACKLIGHT, "%card%", card);
+
+ m_use_actual_brightness = m_conf.get(name(), "use-actual-brightness", m_use_actual_brightness);
+
+ m_interval = m_conf.get<decltype(m_interval)>(name(), "poll-interval", m_use_actual_brightness? 0s : 5s);
+ m_lastpoll = chrono::steady_clock::now();
+
+ std::string brightness_type = (m_use_actual_brightness ? "actual_brightness" : "brightness");
+ auto path_backlight_val = m_path_backlight + "/" + brightness_type;
+
+ m_val.filepath(path_backlight_val);
+ m_max.filepath(m_path_backlight + "/max_brightness");
+
+ // Add inotify watch
+ watch(path_backlight_val);
+ }
+
+ void backlight_module::idle() {
+ if (m_interval.count() > 0) {
+ /*
+ * Update module after interval (if any).
+ * We don't always receive inotify events for the backlight files.
+ * Mainly, the `backlight` file never receive an event on modification.
+ * In that case, updating at an interval is the only way to get the new value.
+ */
+ auto now = chrono::steady_clock::now();
+ if (chrono::duration_cast<decltype(m_interval)>(now - m_lastpoll) > m_interval) {
+ m_lastpoll = now;
+ if (on_event({})) {
+ broadcast();
+ }
+ }
+ }
+
+ this->inotify_module::idle();
+ }
+
+ bool backlight_module::on_event(const inotify_event& event) {
+ if (event.is_valid) {
+ m_log.trace("%s: on_event{filename: %s, is_dir: %s, wd: %d, cookie: %d, mask: 0x%x}", name(), event.filename,
+ event.is_dir? "true" : "false", event.wd, event.cookie, event.mask);
+ }
+
+ m_max_brightness = m_max.read();
+ float val = m_val.read();
+ int percentage = math_util::percentage(val, m_max_brightness);
+
+ if (m_percentage != percentage) {
+ m_log.trace("%s: %d%% -> %d%% (val: %f, max: %f)", name(), m_percentage, percentage, val, m_max_brightness);
+ m_percentage = percentage;
+
+ if (m_label) {
+ m_label->reset_tokens();
+ m_label->replace_token("%percentage%", to_string(m_percentage));
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ string backlight_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapped
+ // with the cmd handlers
+ string output{module::get_output()};
+
+ if (m_scroll) {
+ m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+ }
+
+ m_builder->node(output);
+
+ m_builder->action_close();
+ m_builder->action_close();
+
+ return m_builder->flush();
+ }
+
+ bool backlight_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_BAR) {
+ builder->node(m_progressbar->output(m_percentage));
+ } else if (tag == TAG_RAMP) {
+ builder->node(m_ramp->get_by_percentage(m_percentage));
+ } else if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void backlight_module::action_inc() {
+ change_value(m_scroll_interval);
+ }
+
+ void backlight_module::action_dec() {
+ change_value(-m_scroll_interval);
+ }
+
+ void backlight_module::change_value(int value_mod) {
+ m_log.info("%s: Changing value by %d%", name(), value_mod);
+
+ try {
+ int rounded = math_util::cap<double>(m_percentage + value_mod, 0.0, 100.0) + 0.5;
+ int value = math_util::percentage_to_value<int>(rounded, m_max_brightness);
+ file_util::write_contents(m_path_backlight + "/brightness", to_string(value));
+ } catch (const exception& err) {
+ m_log.err(
+ "%s: Unable to change backlight value. Your system may require additional "
+ "configuration. Please read the module documentation.\n(reason: %s)",
+ name(), err.what());
+ }
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/battery.hpp"
+
+#include "drawtypes/animation.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<battery_module>;
+
+ template <typename ValueReader>
+ typename ValueReader::return_type read(ValueReader& reader) {
+ std::lock_guard<ValueReader> guard(reader);
+ return reader.read();
+ }
+
+ /**
+ * Bootstrap module by setting up required components
+ */
+ battery_module::battery_module(const bar_settings& bar, string name_, const config& config)
+ : inotify_module<battery_module>(bar, move(name_), config) {
+ // Load configuration values
+ m_fullat = std::min(m_conf.get(name(), "full-at", m_fullat), 100);
+ m_lowat = std::max(m_conf.get(name(), "low-at", m_lowat), 0);
+ m_interval = m_conf.get<decltype(m_interval)>(name(), "poll-interval", 5s);
+ m_lastpoll = chrono::steady_clock::now();
+
+ auto path_adapter = string_util::replace(PATH_ADAPTER, "%adapter%", m_conf.get(name(), "adapter", "ADP1"s)) + "/";
+ auto path_battery = string_util::replace(PATH_BATTERY, "%battery%", m_conf.get(name(), "battery", "BAT0"s)) + "/";
+
+ // Make state reader
+ if (file_util::exists((m_fstate = path_battery + "status"))) {
+ m_state_reader =
+ make_unique<state_reader>([=] { return file_util::contents(m_fstate).compare(0, 8, "Charging") == 0; });
+ } else if (file_util::exists((m_fstate = path_adapter + "online"))) {
+ m_state_reader = make_unique<state_reader>([=] { return file_util::contents(m_fstate).compare(0, 1, "1") == 0; });
+ } else {
+ throw module_error("No suitable way to get current charge state");
+ }
+
+ // Make capacity reader
+ if ((m_fcapnow = file_util::pick({path_battery + "charge_now", path_battery + "energy_now"})).empty()) {
+ throw module_error("No suitable way to get current capacity value");
+ } else if ((m_fcapfull = file_util::pick({path_battery + "charge_full", path_battery + "energy_full"})).empty()) {
+ throw module_error("No suitable way to get max capacity value");
+ }
+
+ m_capacity_reader = make_unique<capacity_reader>([=] {
+ auto cap_now = std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10);
+ auto cap_max = std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10);
+ return math_util::percentage(cap_now, 0UL, cap_max);
+ });
+
+ // Make rate reader
+ if ((m_fvoltage = file_util::pick({path_battery + "voltage_now"})).empty()) {
+ throw module_error("No suitable way to get current voltage value");
+ } else if ((m_frate = file_util::pick({path_battery + "current_now", path_battery + "power_now"})).empty()) {
+ throw module_error("No suitable way to get current charge rate value");
+ }
+
+ m_rate_reader = make_unique<rate_reader>([this] {
+ unsigned long rate{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+ unsigned long volt{std::strtoul(file_util::contents(m_fvoltage).c_str(), nullptr, 10) / 1000UL};
+ unsigned long now{std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10)};
+ unsigned long max{std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10)};
+ unsigned long cap{read(*m_state_reader) ? max - now : now};
+
+ if (rate && volt && cap) {
+ auto remaining = (cap / volt);
+ auto current_rate = (rate / volt);
+
+ if (remaining && current_rate) {
+ return 3600UL * remaining / current_rate;
+ }
+ }
+
+ return 0UL;
+ });
+
+ // Make consumption reader
+ m_consumption_reader = make_unique<consumption_reader>([this] {
+ float consumption;
+
+ // if the rate we found was the current, calculate power (P = I*V)
+ if (string_util::contains(m_frate, "current_now")) {
+ unsigned long current{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+ unsigned long voltage{std::strtoul(file_util::contents(m_fvoltage).c_str(), nullptr, 10)};
+
+ consumption = ((voltage / 1000.0) * (current / 1000.0)) / 1e6;
+ } else {
+ // if it was power, just use as is
+ unsigned long power{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+
+ consumption = power / 1e6;
+ }
+
+ // convert to string with 2 decimmal places
+ string rtn(16, '\0'); // 16 should be plenty big. Cant see it needing more than 6/7..
+ auto written = std::snprintf(&rtn[0], rtn.size(), "%.2f", consumption);
+ rtn.resize(written);
+
+ return rtn;
+ });
+
+ // Load state and capacity level
+ m_state = current_state();
+ m_percentage = current_percentage();
+
+ // Add formats and elements
+ m_formatter->add(FORMAT_CHARGING, TAG_LABEL_CHARGING,
+ {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_CHARGING, TAG_LABEL_CHARGING});
+ m_formatter->add(FORMAT_DISCHARGING, TAG_LABEL_DISCHARGING,
+ {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_DISCHARGING, TAG_LABEL_DISCHARGING});
+ m_formatter->add_optional(FORMAT_LOW, {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_LOW, TAG_LABEL_LOW});
+ m_formatter->add(FORMAT_FULL, TAG_LABEL_FULL, {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_FULL});
+
+ if (m_formatter->has(TAG_ANIMATION_CHARGING, FORMAT_CHARGING)) {
+ m_animation_charging = load_animation(m_conf, name(), TAG_ANIMATION_CHARGING);
+ }
+ if (m_formatter->has(TAG_ANIMATION_DISCHARGING, FORMAT_DISCHARGING)) {
+ m_animation_discharging = load_animation(m_conf, name(), TAG_ANIMATION_DISCHARGING);
+ }
+ if (m_formatter->has(TAG_ANIMATION_LOW, FORMAT_LOW)) {
+ m_animation_low = load_animation(m_conf, name(), TAG_ANIMATION_LOW);
+ }
+ if (m_formatter->has(TAG_BAR_CAPACITY)) {
+ m_bar_capacity = load_progressbar(m_bar, m_conf, name(), TAG_BAR_CAPACITY);
+ }
+ if (m_formatter->has(TAG_RAMP_CAPACITY)) {
+ m_ramp_capacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
+ }
+ if (m_formatter->has(TAG_LABEL_CHARGING, FORMAT_CHARGING)) {
+ m_label_charging = load_optional_label(m_conf, name(), TAG_LABEL_CHARGING, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_DISCHARGING, FORMAT_DISCHARGING)) {
+ m_label_discharging = load_optional_label(m_conf, name(), TAG_LABEL_DISCHARGING, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_LOW, FORMAT_LOW)) {
+ m_label_low = load_optional_label(m_conf, name(), TAG_LABEL_LOW, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_FULL, FORMAT_FULL)) {
+ m_label_full = load_optional_label(m_conf, name(), TAG_LABEL_FULL, "%percentage%%");
+ }
+
+ // Create inotify watches
+ watch(m_fcapnow, IN_ACCESS);
+ watch(m_fstate, IN_ACCESS);
+
+ // Setup time if token is used
+ if ((m_label_charging && m_label_charging->has_token("%time%")) ||
+ (m_label_discharging && m_label_discharging->has_token("%time%")) ||
+ (m_label_low && m_label_low->has_token("%time%"))) {
+ if (!m_bar.locale.empty()) {
+ setlocale(LC_TIME, m_bar.locale.c_str());
+ }
+ m_timeformat = m_conf.get(name(), "time-format", "%H:%M:%S"s);
+ }
+ }
+
+ /**
+ * Dispatch the subthread used to update the
+ * charging animation when the module is started
+ */
+ void battery_module::start() {
+ this->inotify_module::start();
+ // We only start animation thread if there is at least one animation.
+ if (m_animation_charging || m_animation_discharging || m_animation_low) {
+ m_subthread = thread(&battery_module::subthread, this);
+ }
+ }
+
+ /**
+ * Release wake lock when stopping the module
+ */
+ void battery_module::teardown() {
+ if (m_subthread.joinable()) {
+ m_subthread.join();
+ }
+ }
+
+ /**
+ * Idle between polling inotify watches for events.
+ *
+ * If the defined interval has been reached, trigger a manual
+ * poll in case the inotify events aren't fired.
+ *
+ * This fallback is needed because some systems won't
+ * report inotify events for files on sysfs.
+ */
+ void battery_module::idle() {
+ if (m_interval.count() > 0) {
+ auto now = chrono::steady_clock::now();
+ if (chrono::duration_cast<decltype(m_interval)>(now - m_lastpoll) > m_interval) {
+ m_lastpoll = now;
+ m_log.info("%s: Polling values (inotify fallback)", name());
+ on_event({});
+ }
+ }
+
+ this->inotify_module::idle();
+ }
+
+ /**
+ * Update values when tracked files have changed
+ */
+ bool battery_module::on_event(const inotify_event& event) {
+ auto state = current_state();
+ auto percentage = current_percentage();
+
+ // Reset timer to avoid unnecessary polling
+ m_lastpoll = chrono::steady_clock::now();
+
+ if (event.is_valid) {
+ m_log.trace("%s: Inotify event reported for %s", name(), event.filename);
+
+ if (state == m_state && percentage == m_percentage && m_unchanged--) {
+ return false;
+ }
+
+ m_unchanged = SKIP_N_UNCHANGED;
+ }
+
+ m_state = state;
+ m_percentage = percentage;
+
+ const auto replace_tokens = [&](label_t& label) {
+ if (!label) {
+ return;
+ }
+
+ label->reset_tokens();
+ label->replace_token("%percentage%", to_string(clamp_percentage(m_percentage, m_state)));
+ label->replace_token("%percentage_raw%", to_string(m_percentage));
+ label->replace_token("%consumption%", current_consumption());
+
+ if (m_state != battery_module::state::FULL && !m_timeformat.empty()) {
+ label->replace_token("%time%", current_time());
+ }
+ };
+
+ replace_tokens(m_label_full);
+ replace_tokens(m_label_discharging);
+ replace_tokens(m_label_low);
+ replace_tokens(m_label_charging);
+
+ return true;
+ }
+
+ /**
+ * Get the output format based on state
+ */
+ string battery_module::get_format() const {
+ switch (m_state) {
+ case battery_module::state::FULL:
+ return FORMAT_FULL;
+ case battery_module::state::LOW:
+ if (m_formatter->has_format(FORMAT_LOW)) {
+ return FORMAT_LOW;
+ }
+ return FORMAT_DISCHARGING;
+ case battery_module::state::DISCHARGING:
+ return FORMAT_DISCHARGING;
+ default:
+ return FORMAT_CHARGING;
+ }
+ }
+
+ /**
+ * Generate module output using defined drawtypes
+ */
+ bool battery_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_ANIMATION_CHARGING) {
+ builder->node(m_animation_charging->get());
+ } else if (tag == TAG_ANIMATION_DISCHARGING) {
+ builder->node(m_animation_discharging->get());
+ } else if (tag == TAG_ANIMATION_LOW) {
+ builder->node(m_animation_low->get());
+ } else if (tag == TAG_BAR_CAPACITY) {
+ builder->node(m_bar_capacity->output(clamp_percentage(m_percentage, m_state)));
+ } else if (tag == TAG_RAMP_CAPACITY) {
+ builder->node(m_ramp_capacity->get_by_percentage_with_borders(m_percentage, m_lowat, m_fullat));
+ } else if (tag == TAG_LABEL_CHARGING) {
+ builder->node(m_label_charging);
+ } else if (tag == TAG_LABEL_DISCHARGING) {
+ builder->node(m_label_discharging);
+ } else if (tag == TAG_LABEL_LOW) {
+ builder->node(m_label_low);
+ } else if (tag == TAG_LABEL_FULL) {
+ builder->node(m_label_full);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the current battery state
+ */
+ battery_module::state battery_module::current_state() {
+ auto charge = read(*m_capacity_reader);
+ if (charge >= m_fullat) {
+ return battery_module::state::FULL;
+ } else if (!read(*m_state_reader)) {
+ return charge <= m_lowat ? battery_module::state::LOW : battery_module::state::DISCHARGING;
+ } else {
+ return battery_module::state::CHARGING;
+ }
+ }
+
+ /**
+ * Get the current capacity level
+ */
+ int battery_module::current_percentage() {
+ return read(*m_capacity_reader);
+ }
+
+ int battery_module::clamp_percentage(int percentage, state state) const {
+ if (state == battery_module::state::FULL && percentage >= m_fullat) {
+ return 100;
+ }
+ return percentage;
+ }
+
+ /**
+ * Get the current power consumption
+ */
+ string battery_module::current_consumption() {
+ return read(*m_consumption_reader);
+ }
+
+ /**
+ * Get estimate of remaining time until fully dis-/charged
+ */
+ string battery_module::current_time() {
+ struct tm t {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr
+ };
+
+ chrono::seconds sec{read(*m_rate_reader)};
+ if (sec.count() > 0) {
+ t.tm_hour = chrono::duration_cast<chrono::hours>(sec).count();
+ sec -= chrono::seconds{3600 * t.tm_hour};
+ t.tm_min = chrono::duration_cast<chrono::minutes>(sec).count();
+ sec -= chrono::seconds{60 * t.tm_min};
+ t.tm_sec = chrono::duration_cast<chrono::seconds>(sec).count();
+ }
+
+ char buffer[256]{0};
+ strftime(buffer, sizeof(buffer), m_timeformat.c_str(), &t);
+ return {buffer};
+ }
+
+ /**
+ * Subthread runner that emits update events to refresh <animation-charging>
+ * or <animation-discharging> in case they are used. Note, that it is ok to
+ * use a single thread, because the two animations are never shown at the
+ * same time.
+ */
+ void battery_module::subthread() {
+ m_log.trace("%s: Start of subthread", name());
+
+ while (running()) {
+ auto now = chrono::steady_clock::now();
+ auto framerate = 1000U; // milliseconds
+ if (m_state == battery_module::state::CHARGING && m_animation_charging) {
+ m_animation_charging->increment();
+ broadcast();
+ framerate = m_animation_charging->framerate();
+ } else if (m_state == battery_module::state::DISCHARGING && m_animation_discharging) {
+ m_animation_discharging->increment();
+ broadcast();
+ framerate = m_animation_discharging->framerate();
+ } else if (m_state == battery_module::state::LOW && m_animation_low) {
+ m_animation_low->increment();
+ broadcast();
+ framerate = m_animation_low->framerate();
+ }
+
+ // We don't count the the first part of the loop to be as close as possible to the framerate.
+ now += chrono::milliseconds(framerate);
+ this_thread::sleep_until(now);
+ }
+
+ m_log.trace("%s: End of subthread", name());
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/bspwm.hpp"
+
+#include <sys/socket.h>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace {
+ using bspwm_state = modules::bspwm_module::state;
+
+ unsigned int make_mask(bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
+ unsigned int mask{0U};
+ if (static_cast<unsigned int>(s1)) {
+ mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
+ }
+ if (static_cast<unsigned int>(s2)) {
+ mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
+ }
+ return mask;
+ }
+
+ unsigned int check_mask(unsigned int base, bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
+ unsigned int mask{0U};
+ if (static_cast<unsigned int>(s1)) {
+ mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
+ }
+ if (static_cast<unsigned int>(s2)) {
+ mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
+ }
+ return (base & mask) == mask;
+ }
+} // namespace
+
+namespace modules {
+ template class module<bspwm_module>;
+
+ bspwm_module::bspwm_module(const bar_settings& bar, string name_, const config& config)
+ : event_module<bspwm_module>(bar, move(name_), config) {
+ m_router->register_action_with_data(EVENT_FOCUS, [this](const std::string& data) { action_focus(data); });
+ m_router->register_action(EVENT_NEXT, [this]() { action_next(); });
+ m_router->register_action(EVENT_PREV, [this]() { action_prev(); });
+
+ auto socket_path = bspwm_util::get_socket_path();
+
+ if (!file_util::exists(socket_path)) {
+ throw module_error("Could not find socket: " + (socket_path.empty() ? "<empty>" : socket_path));
+ }
+
+ // Create ipc subscriber
+ m_subscriber = bspwm_util::make_subscriber();
+
+ // Load configuration values
+ m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+ m_click = m_conf.get(name(), "enable-click", m_click);
+ m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+ m_occscroll = m_conf.get(name(), "occupied-scroll", m_occscroll);
+ m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
+ m_inlinemode = m_conf.get(name(), "inline-mode", m_inlinemode);
+ m_fuzzy_match = m_conf.get(name(), "fuzzy-match", m_fuzzy_match);
+
+ // Add formats and create components
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MONITOR, TAG_LABEL_MODE});
+
+ if (m_formatter->has(TAG_LABEL_MONITOR)) {
+ m_monitorlabel = load_optional_label(m_conf, name(), "label-monitor", DEFAULT_MONITOR_LABEL);
+ }
+
+ if (m_formatter->has(TAG_LABEL_STATE)) {
+ // XXX: Warn about deprecated parameters
+ m_conf.warn_deprecated(name(), "label-dimmed-active", "label-dimmed-focused");
+
+ // clang-format off
+ try {
+ m_statelabels.emplace(make_mask(state::FOCUSED), load_label(m_conf, name(), "label-active", DEFAULT_LABEL));
+ m_conf.warn_deprecated(name(), "label-active", "label-focused and label-dimmed-focused");
+ } catch (const key_error& err) {
+ m_statelabels.emplace(make_mask(state::FOCUSED), load_optional_label(m_conf, name(), "label-focused", DEFAULT_LABEL));
+ }
+
+ m_statelabels.emplace(make_mask(state::OCCUPIED),
+ load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL));
+ m_statelabels.emplace(make_mask(state::URGENT),
+ load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL));
+ m_statelabels.emplace(make_mask(state::EMPTY),
+ load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL));
+ m_statelabels.emplace(make_mask(state::DIMMED),
+ load_optional_label(m_conf, name(), "label-dimmed"));
+
+ vector<pair<state, string>> focused_overrides{
+ {state::OCCUPIED, "label-focused-occupied"},
+ {state::URGENT, "label-focused-urgent"},
+ {state::EMPTY, "label-focused-empty"}};
+
+ for (auto&& os : focused_overrides) {
+ unsigned int mask{make_mask(state::FOCUSED, os.first)};
+ try {
+ m_statelabels.emplace(mask, load_label(m_conf, name(), os.second));
+ } catch (const key_error& err) {
+ m_statelabels.emplace(mask, m_statelabels.at(make_mask(state::FOCUSED))->clone());
+ }
+ }
+
+ vector<pair<state, string>> dimmed_overrides{
+ {state::FOCUSED, "label-dimmed-focused"},
+ {state::OCCUPIED, "label-dimmed-occupied"},
+ {state::URGENT, "label-dimmed-urgent"},
+ {state::EMPTY, "label-dimmed-empty"}};
+
+ for (auto&& os : dimmed_overrides) {
+ m_statelabels.emplace(make_mask(state::DIMMED, os.first),
+ load_optional_label(m_conf, name(), os.second, m_statelabels.at(make_mask(os.first))->get()));
+ }
+ // clang-format on
+ }
+
+ if (m_formatter->has(TAG_LABEL_MODE)) {
+ m_modelabels.emplace(mode::LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"));
+ m_modelabels.emplace(mode::LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"));
+ m_modelabels.emplace(mode::STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen"));
+ m_modelabels.emplace(mode::STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"));
+ m_modelabels.emplace(mode::STATE_PSEUDOTILED, load_optional_label(m_conf, name(), "label-pseudotiled"));
+ m_modelabels.emplace(mode::NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"));
+ m_modelabels.emplace(mode::NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"));
+ m_modelabels.emplace(mode::NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"));
+ m_modelabels.emplace(mode::NODE_MARKED, load_optional_label(m_conf, name(), "label-marked"));
+ }
+
+ m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+ m_icons = std::make_shared<iconset>();
+ m_icons->add(DEFAULT_ICON, std::make_shared<label>(m_conf.get(name(), DEFAULT_ICON, ""s)));
+
+ int i = 0;
+ for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
+ auto vec = string_util::tokenize(workspace, ';');
+ if (vec.size() == 2) {
+ m_icons->add(vec[0], std::make_shared<label>(vec[1]));
+ } else {
+ m_log.err("%s: Ignoring ws-icon-%d because it has %s semicolons", name(), i, vec.size() > 2? "too many" : "too few");
+ }
+
+ i++;
+ }
+ }
+
+ void bspwm_module::stop() {
+ if (m_subscriber) {
+ m_log.info("%s: Disconnecting from socket", name());
+ m_subscriber->disconnect();
+ }
+ event_module::stop();
+ }
+
+ bool bspwm_module::has_event() {
+ if (m_subscriber->poll(POLLHUP, 0)) {
+ m_log.notice("%s: Reconnecting to socket...", name());
+ m_subscriber = bspwm_util::make_subscriber();
+ }
+ return m_subscriber->peek(1);
+ }
+
+ bool bspwm_module::update() {
+ if (!m_subscriber) {
+ return false;
+ }
+
+ string data{m_subscriber->receive(BUFSIZ)};
+ bool result = false;
+
+ for (auto&& status_line : string_util::split(data, '\n')) {
+ // Need to return true if ANY of the handle_status calls
+ // return true
+ result = this->handle_status(status_line) || result;
+ }
+
+ return result;
+ }
+
+ bool bspwm_module::handle_status(string& data) {
+ if (data.empty()) {
+ return false;
+ }
+
+ size_t prefix_len{strlen(BSPWM_STATUS_PREFIX)};
+ if (data.compare(0, prefix_len, BSPWM_STATUS_PREFIX) != 0) {
+ m_log.err("%s: Unknown status '%s'", name(), data);
+ return false;
+ }
+
+ string_util::hash_type hash;
+ if ((hash = string_util::hash(data)) == m_hash) {
+ return false;
+ }
+
+ m_hash = hash;
+
+ size_t pos;
+
+ // Extract the string for the defined monitor
+ if (m_pinworkspaces) {
+ const auto needle_active = ":M" + m_bar.monitor->name + ":";
+ const auto needle_inactive = ":m" + m_bar.monitor->name + ":";
+
+ if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
+ data = data.replace(pos, prefix_len, ":");
+ }
+ if ((pos = data.find(needle_active)) != string::npos) {
+ data.erase(0, pos + 1);
+ }
+ if ((pos = data.find(needle_inactive)) != string::npos) {
+ data.erase(0, pos + 1);
+ }
+ if ((pos = data.find(":m", 1)) != string::npos) {
+ data.erase(pos);
+ }
+ if ((pos = data.find(":M", 1)) != string::npos) {
+ data.erase(pos);
+ }
+ } else if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
+ data = data.replace(pos, prefix_len, ":");
+ } else {
+ return false;
+ }
+
+ m_log.info("%s: Parsing socket data: %s", name(), data);
+
+ m_monitors.clear();
+
+ size_t workspace_n{0U};
+
+ for (auto&& tag : string_util::split(data, ':')) {
+ auto value = tag.substr(1);
+ auto mode_flag = mode::NONE;
+ unsigned int workspace_mask{0U};
+
+ if (tag[0] == 'm' || tag[0] == 'M') {
+ m_monitors.emplace_back(std::make_unique<bspwm_monitor>());
+ m_monitors.back()->name = value;
+
+ if (m_monitorlabel) {
+ m_monitors.back()->label = m_monitorlabel->clone();
+ m_monitors.back()->label->replace_token("%name%", value);
+ }
+ }
+
+ switch (tag[0]) {
+ case 'm':
+ m_monitors.back()->focused = false;
+ break;
+ case 'M':
+ m_monitors.back()->focused = true;
+ break;
+ case 'F':
+ workspace_mask = make_mask(state::FOCUSED, state::EMPTY);
+ break;
+ case 'O':
+ workspace_mask = make_mask(state::FOCUSED, state::OCCUPIED);
+ break;
+ case 'U':
+ workspace_mask = make_mask(state::FOCUSED, state::URGENT);
+ break;
+ case 'f':
+ workspace_mask = make_mask(state::EMPTY);
+ break;
+ case 'o':
+ workspace_mask = make_mask(state::OCCUPIED);
+ break;
+ case 'u':
+ workspace_mask = make_mask(state::URGENT);
+ break;
+ case 'L':
+ switch (value[0]) {
+ case 0:
+ break;
+ case 'M':
+ mode_flag = mode::LAYOUT_MONOCLE;
+ break;
+ case 'T':
+ mode_flag = mode::LAYOUT_TILED;
+ break;
+ default:
+ m_log.warn("%s: Undefined L => '%s'", name(), value);
+ }
+ break;
+
+ case 'T':
+ switch (value[0]) {
+ case 0:
+ break;
+ case '@':
+ break;
+ case 'T':
+ break;
+ case '=':
+ mode_flag = mode::STATE_FULLSCREEN;
+ break;
+ case 'F':
+ mode_flag = mode::STATE_FLOATING;
+ break;
+ case 'P':
+ mode_flag = mode::STATE_PSEUDOTILED;
+ break;
+ default:
+ m_log.warn("%s: Undefined T => '%s'", name(), value);
+ }
+ break;
+
+ case 'G':
+ if (!m_monitors.back()->focused) {
+ break;
+ }
+
+ for (size_t i = 0U; i < value.length(); i++) {
+ switch (value[i]) {
+ case 0:
+ break;
+ case 'L':
+ mode_flag = mode::NODE_LOCKED;
+ break;
+ case 'S':
+ mode_flag = mode::NODE_STICKY;
+ break;
+ case 'P':
+ mode_flag = mode::NODE_PRIVATE;
+ break;
+ case 'M':
+ mode_flag = mode::NODE_MARKED;
+ break;
+ default:
+ m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
+ }
+
+ if (mode_flag != mode::NONE && !m_modelabels.empty()) {
+ m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
+ }
+ }
+ continue;
+
+ default:
+ m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1));
+ continue;
+ }
+
+ if (!m_monitors.back()) {
+ m_log.warn("%s: No monitor created", name());
+ continue;
+ }
+
+ if (workspace_mask && m_formatter->has(TAG_LABEL_STATE)) {
+ auto icon = m_icons->get(value, DEFAULT_ICON, m_fuzzy_match);
+ auto label = m_statelabels.at(workspace_mask)->clone();
+
+ if (!m_monitors.back()->focused) {
+ if (m_statelabels[make_mask(state::DIMMED)]) {
+ label->replace_defined_values(m_statelabels[make_mask(state::DIMMED)]);
+ }
+ if (workspace_mask & make_mask(state::EMPTY)) {
+ label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::EMPTY)]);
+ }
+ if (workspace_mask & make_mask(state::OCCUPIED)) {
+ label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::OCCUPIED)]);
+ }
+ if (workspace_mask & make_mask(state::FOCUSED)) {
+ label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::FOCUSED)]);
+ }
+ if (workspace_mask & make_mask(state::URGENT)) {
+ label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::URGENT)]);
+ }
+ }
+
+ label->reset_tokens();
+ label->replace_token("%name%", value);
+ label->replace_token("%icon%", icon->get());
+ label->replace_token("%index%", to_string(++workspace_n));
+
+ m_monitors.back()->workspaces.emplace_back(workspace_mask, move(label));
+ }
+
+ if (mode_flag != mode::NONE && !m_modelabels.empty()) {
+ m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
+ }
+ }
+
+ return true;
+ }
+
+ string bspwm_module::get_output() {
+ string output;
+ for (m_index = 0U; m_index < m_monitors.size(); m_index++) {
+ if (m_index > 0) {
+ m_builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
+ }
+ output += this->event_module::get_output();
+ }
+ return output;
+ }
+
+ bool bspwm_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_MONITOR) {
+ builder->node(m_monitors[m_index]->label);
+ return true;
+ } else if (tag == TAG_LABEL_STATE && !m_monitors[m_index]->workspaces.empty()) {
+ size_t workspace_n{0U};
+
+ if (m_scroll) {
+ builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
+ builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
+ }
+
+ for (auto&& ws : m_monitors[m_index]->workspaces) {
+ if (ws.second.get()) {
+ if (workspace_n != 0 && *m_labelseparator) {
+ builder->node(m_labelseparator);
+ }
+
+ workspace_n++;
+
+ if (m_click) {
+ builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, sstream() << m_index << "+" << workspace_n, ws.second);
+ } else {
+ builder->node(ws.second);
+ }
+
+ if (m_inlinemode && m_monitors[m_index]->focused && check_mask(ws.first, bspwm_state::FOCUSED)) {
+ for (auto&& mode : m_monitors[m_index]->modes) {
+ builder->node(mode);
+ }
+ }
+ }
+ }
+
+ if (m_scroll) {
+ builder->action_close();
+ builder->action_close();
+ }
+
+ return workspace_n > 0;
+ } else if (tag == TAG_LABEL_MODE && !m_inlinemode && m_monitors[m_index]->focused &&
+ !m_monitors[m_index]->modes.empty()) {
+ int modes_n = 0;
+
+ for (auto&& mode : m_monitors[m_index]->modes) {
+ if (mode && *mode) {
+ builder->node(mode);
+ modes_n++;
+ }
+ }
+
+ return modes_n > 0;
+ }
+
+ return false;
+ }
+
+ void bspwm_module::action_focus(const string& data) {
+ size_t separator{string_util::find_nth(data, 0, "+", 1)};
+ size_t monitor_n{std::strtoul(data.substr(0, separator).c_str(), nullptr, 10)};
+ string workspace_n{data.substr(separator + 1)};
+
+ if (monitor_n < m_monitors.size()) {
+ send_command("desktop -f " + m_monitors[monitor_n]->name + ":^" + workspace_n,
+ "Sending desktop focus command to ipc handler");
+ } else {
+ m_log.err("%s: Invalid monitor index in command: %s", name(), data);
+ }
+ }
+ void bspwm_module::action_next() {
+ focus_direction(true);
+ }
+
+ void bspwm_module::action_prev() {
+ focus_direction(false);
+ }
+
+ void bspwm_module::focus_direction(bool next) {
+ string scrolldir = next ? "next" : "prev";
+ string modifier;
+
+ if (m_occscroll) {
+ modifier += ".occupied";
+ }
+
+ if (m_pinworkspaces) {
+ modifier += ".local";
+ for (const auto& mon : m_monitors) {
+ if (m_bar.monitor->match(mon->name, false) && !mon->focused) {
+ send_command("monitor -f " + mon->name, "Sending monitor focus command to ipc handler");
+ break;
+ }
+ }
+ }
+
+ send_command("desktop -f " + scrolldir + modifier, "Sending desktop " + scrolldir + " command to ipc handler");
+ }
+
+ void bspwm_module::send_command(const string& payload_cmd, const string& log_info) {
+ auto ipc = bspwm_util::make_connection();
+ auto payload = bspwm_util::make_payload(payload_cmd);
+ m_log.info("%s: %s", name(), log_info);
+ ipc->send(payload->data, payload->len, 0);
+ ipc->disconnect();
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/counter.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<counter_module>;
+
+ counter_module::counter_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<counter_module>(bar, move(name_), config) {
+ set_interval(1s);
+ m_formatter->add(DEFAULT_FORMAT, TAG_COUNTER, {TAG_COUNTER});
+ }
+
+ bool counter_module::update() {
+ m_counter++;
+ return true;
+ }
+
+ bool counter_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_COUNTER) {
+ builder->node(to_string(m_counter));
+ return true;
+ }
+ return false;
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/cpu.hpp"
+
+#include <fstream>
+#include <istream>
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<cpu_module>;
+
+ cpu_module::cpu_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<cpu_module>(bar, move(name_), config) {
+ set_interval(1s);
+ m_totalwarn = m_conf.get(name(), "warn-percentage", m_totalwarn);
+ m_ramp_padding = m_conf.get(name(), "ramp-coreload-spacing", m_ramp_padding);
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
+ m_formatter->add_optional(FORMAT_WARN, {TAG_LABEL_WARN, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
+
+ // warmup cpu times
+ read_values();
+ read_values();
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_WARN)) {
+ m_labelwarn = load_optional_label(m_conf, name(), TAG_LABEL_WARN, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_BAR_LOAD)) {
+ m_barload = load_progressbar(m_bar, m_conf, name(), TAG_BAR_LOAD);
+ }
+ if (m_formatter->has(TAG_RAMP_LOAD)) {
+ m_rampload = load_ramp(m_conf, name(), TAG_RAMP_LOAD);
+ }
+ if (m_formatter->has(TAG_RAMP_LOAD_PER_CORE)) {
+ m_rampload_core = load_ramp(m_conf, name(), TAG_RAMP_LOAD_PER_CORE);
+ }
+ }
+
+ bool cpu_module::update() {
+ if (!read_values()) {
+ return false;
+ }
+
+ m_total = 0.0f;
+ m_load.clear();
+
+ auto cores_n = m_cputimes.size();
+ if (!cores_n) {
+ return false;
+ }
+
+ vector<string> percentage_cores;
+ for (size_t i = 0; i < cores_n; i++) {
+ auto load = get_load(i);
+ m_total += load;
+ m_load.emplace_back(load);
+
+ if (m_label || m_labelwarn) {
+ percentage_cores.emplace_back(to_string(static_cast<int>(load + 0.5)));
+ }
+ }
+
+ m_total = m_total / static_cast<float>(cores_n);
+
+ const auto replace_tokens = [&](label_t& label) {
+ label->reset_tokens();
+ label->replace_token("%percentage%", to_string(static_cast<int>(m_total + 0.5)));
+ label->replace_token(
+ "%percentage-sum%", to_string(static_cast<int>(m_total * static_cast<float>(cores_n) + 0.5)));
+ label->replace_token("%percentage-cores%", string_util::join(percentage_cores, "% ") + "%");
+
+ for (size_t i = 0; i < percentage_cores.size(); i++) {
+ label->replace_token("%percentage-core" + to_string(i + 1) + "%", percentage_cores[i]);
+ }
+ };
+
+ if (m_label) {
+ replace_tokens(m_label);
+ }
+ if (m_labelwarn) {
+ replace_tokens(m_labelwarn);
+ }
+
+ return true;
+ }
+
+ string cpu_module::get_format() const {
+ if (m_total >= m_totalwarn && m_formatter->has_format(FORMAT_WARN)) {
+ return FORMAT_WARN;
+ } else {
+ return DEFAULT_FORMAT;
+ }
+ }
+
+ bool cpu_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else if (tag == TAG_LABEL_WARN) {
+ builder->node(m_labelwarn);
+ } else if (tag == TAG_BAR_LOAD) {
+ builder->node(m_barload->output(m_total));
+ } else if (tag == TAG_RAMP_LOAD) {
+ builder->node(m_rampload->get_by_percentage_with_borders(m_total, 0.0f, m_totalwarn));
+ } else if (tag == TAG_RAMP_LOAD_PER_CORE) {
+ auto i = 0;
+ for (auto&& load : m_load) {
+ if (i++ > 0) {
+ builder->spacing(m_ramp_padding);
+ }
+ builder->node(m_rampload_core->get_by_percentage_with_borders(load, 0.0f, m_totalwarn));
+ }
+ builder->node(builder->flush());
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ bool cpu_module::read_values() {
+ m_cputimes_prev.swap(m_cputimes);
+ m_cputimes.clear();
+
+ try {
+ std::ifstream in(PATH_CPU_INFO);
+ string str;
+
+ while (std::getline(in, str) && str.compare(0, 3, "cpu") == 0) {
+ // skip line with accumulated value
+ if (str.compare(0, 4, "cpu ") == 0) {
+ continue;
+ }
+
+ auto values = string_util::split(str, ' ');
+
+ m_cputimes.emplace_back(new cpu_time);
+ m_cputimes.back()->user = std::stoull(values[1], nullptr, 10);
+ m_cputimes.back()->nice = std::stoull(values[2], nullptr, 10);
+ m_cputimes.back()->system = std::stoull(values[3], nullptr, 10);
+ m_cputimes.back()->idle = std::stoull(values[4], nullptr, 10);
+ m_cputimes.back()->steal = std::stoull(values[8], nullptr, 10);
+ m_cputimes.back()->total = m_cputimes.back()->user + m_cputimes.back()->nice + m_cputimes.back()->system +
+ m_cputimes.back()->idle + m_cputimes.back()->steal;
+ }
+ } catch (const std::ios_base::failure& e) {
+ m_log.err("Failed to read CPU values (what: %s)", e.what());
+ }
+
+ return !m_cputimes.empty();
+ }
+
+ float cpu_module::get_load(size_t core) const {
+ if (m_cputimes.empty() || m_cputimes_prev.empty()) {
+ return 0;
+ } else if (core >= m_cputimes.size() || core >= m_cputimes_prev.size()) {
+ return 0;
+ }
+
+ auto& last = m_cputimes[core];
+ auto& prev = m_cputimes_prev[core];
+
+ auto last_idle = last->idle;
+ auto prev_idle = prev->idle;
+
+ auto diff = last->total - prev->total;
+
+ if (diff == 0) {
+ return 0;
+ }
+
+ float percentage = 100.0f * (diff - (last_idle - prev_idle)) / diff;
+
+ return math_util::cap<float>(percentage, 0, 100);
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/date.hpp"
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<date_module>;
+
+ date_module::date_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<date_module>(bar, move(name_), config) {
+ if (!m_bar.locale.empty()) {
+ datetime_stream.imbue(std::locale(m_bar.locale.c_str()));
+ }
+
+ m_router->register_action(EVENT_TOGGLE, [this]() { action_toggle(); });
+
+ m_dateformat = m_conf.get(name(), "date", ""s);
+ m_dateformat_alt = m_conf.get(name(), "date-alt", ""s);
+ m_timeformat = m_conf.get(name(), "time", ""s);
+ m_timeformat_alt = m_conf.get(name(), "time-alt", ""s);
+
+ if (m_dateformat.empty() && m_timeformat.empty()) {
+ throw module_error("No date or time format specified");
+ }
+
+ set_interval(1s);
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_DATE});
+
+ if (m_formatter->has(TAG_DATE)) {
+ m_log.warn("%s: The format tag `<date>` is deprecated, use `<label>` instead.", name());
+
+ m_formatter->get(DEFAULT_FORMAT)->value =
+ string_util::replace_all(m_formatter->get(DEFAULT_FORMAT)->value, TAG_DATE, TAG_LABEL);
+ }
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), "label", "%date%");
+ }
+ }
+
+ bool date_module::update() {
+ auto time = std::time(nullptr);
+
+ auto date_format = m_toggled ? m_dateformat_alt : m_dateformat;
+ // Clear stream contents
+ datetime_stream.str("");
+ datetime_stream << std::put_time(localtime(&time), date_format.c_str());
+ auto date_string = datetime_stream.str();
+
+ auto time_format = m_toggled ? m_timeformat_alt : m_timeformat;
+ // Clear stream contents
+ datetime_stream.str("");
+ datetime_stream << std::put_time(localtime(&time), time_format.c_str());
+ auto time_string = datetime_stream.str();
+
+ if (m_date == date_string && m_time == time_string) {
+ return false;
+ }
+
+ m_date = date_string;
+ m_time = time_string;
+
+ if (m_label) {
+ m_label->reset_tokens();
+ m_label->replace_token("%date%", m_date);
+ m_label->replace_token("%time%", m_time);
+ }
+
+ return true;
+ }
+
+ bool date_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ if (!m_dateformat_alt.empty() || !m_timeformat_alt.empty()) {
+ builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "", m_label);
+ } else {
+ builder->node(m_label);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ void date_module::action_toggle() {
+ m_toggled = !m_toggled;
+ wakeup();
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/fs.hpp"
+
+#include <sys/statvfs.h>
+
+#include <fstream>
+#include <utility>
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+// Columns in /proc/self/mountinfo
+#define MOUNTINFO_DIR 4
+#define MOUNTINFO_TYPE 7
+#define MOUNTINFO_FSNAME 8
+
+namespace modules {
+ template class module<fs_module>;
+
+ /**
+ * Bootstrap the module by reading config values and
+ * setting up required components
+ */
+ fs_module::fs_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<fs_module>(bar, move(name_), config) {
+ m_mountpoints = m_conf.get_list(name(), "mount", {});
+ if (m_mountpoints.empty()) {
+ m_log.info("%s: No mountpoints specified, using fallback \"/\"", name());
+ m_mountpoints.emplace_back("/");
+ }
+ m_remove_unmounted = m_conf.get(name(), "remove-unmounted", m_remove_unmounted);
+ m_perc_used_warn = m_conf.get(name(), "warn-percentage", 90);
+ m_fixed = m_conf.get(name(), "fixed-values", m_fixed);
+ m_spacing = m_conf.get(name(), "spacing", m_spacing);
+ set_interval(30s);
+
+ // Add formats and elements
+ m_formatter->add(
+ FORMAT_MOUNTED, TAG_LABEL_MOUNTED, {TAG_LABEL_MOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
+ m_formatter->add_optional(FORMAT_WARN, {TAG_LABEL_WARN, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
+ m_formatter->add(FORMAT_UNMOUNTED, TAG_LABEL_UNMOUNTED, {TAG_LABEL_UNMOUNTED});
+
+ if (m_formatter->has(TAG_LABEL_MOUNTED)) {
+ m_labelmounted = load_optional_label(m_conf, name(), TAG_LABEL_MOUNTED, "%mountpoint% %percentage_free%%");
+ }
+ if (m_formatter->has(TAG_LABEL_WARN)) {
+ m_labelwarn = load_optional_label(m_conf, name(), TAG_LABEL_WARN, "%mountpoint% %percentage_free%%");
+ }
+ if (m_formatter->has(TAG_LABEL_UNMOUNTED)) {
+ m_labelunmounted = load_optional_label(m_conf, name(), TAG_LABEL_UNMOUNTED, "%mountpoint% is not mounted");
+ }
+ if (m_formatter->has(TAG_BAR_FREE)) {
+ m_barfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
+ }
+ if (m_formatter->has(TAG_BAR_USED)) {
+ m_barused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
+ }
+ if (m_formatter->has(TAG_RAMP_CAPACITY)) {
+ m_rampcapacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
+ }
+
+ // Warn about "unreachable" format tag
+ if (m_formatter->has(TAG_LABEL_UNMOUNTED) && m_remove_unmounted) {
+ m_log.warn("%s: Defined format tag \"%s\" will never be used (reason: `remove-unmounted = true`)", name(),
+ string{TAG_LABEL_UNMOUNTED});
+ }
+ }
+
+ /**
+ * Update mountpoints
+ */
+ bool fs_module::update() {
+ m_mounts.clear();
+
+ vector<vector<string>> mountinfo;
+ std::ifstream filestream("/proc/self/mountinfo");
+ string line;
+
+ // Get details for mounted filesystems
+ while (std::getline(filestream, line)) {
+ auto cols = string_util::split(line, ' ');
+ if (std::find(m_mountpoints.begin(), m_mountpoints.end(), cols[MOUNTINFO_DIR]) != m_mountpoints.end()) {
+ mountinfo.emplace_back(move(cols));
+ }
+ }
+
+ // Get data for defined mountpoints
+ for (auto&& mountpoint : m_mountpoints) {
+ auto details = std::find_if(mountinfo.begin(), mountinfo.end(),
+ [&](const vector<string>& m) { return m.size() > 4 && m[MOUNTINFO_DIR] == mountpoint; });
+
+ m_mounts.emplace_back(std::make_unique<fs_mount>(mountpoint, details != mountinfo.end()));
+ struct statvfs buffer {};
+
+ if (!m_mounts.back()->mounted) {
+ m_log.warn("%s: Mountpoint %s is not mounted", name(), mountpoint);
+ } else if (statvfs(mountpoint.c_str(), &buffer) == -1) {
+ m_log.err("%s: Failed to query filesystem (statvfs() error: %s)", name(), strerror(errno));
+ } else {
+ auto& mount = m_mounts.back();
+ mount->mountpoint = details->at(MOUNTINFO_DIR);
+ mount->type = details->at(MOUNTINFO_TYPE);
+ mount->fsname = details->at(MOUNTINFO_FSNAME);
+
+ // see: https://en.cppreference.com/w/cpp/filesystem/space
+ mount->bytes_total = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_blocks);
+ mount->bytes_free = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bfree);
+ mount->bytes_used = mount->bytes_total - mount->bytes_free;
+ mount->bytes_avail = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bavail);
+
+ mount->percentage_free =
+ math_util::percentage<double>(mount->bytes_avail, mount->bytes_used + mount->bytes_avail);
+ mount->percentage_used =
+ math_util::percentage<double>(mount->bytes_used, mount->bytes_used + mount->bytes_avail);
+ }
+ }
+
+ if (m_remove_unmounted) {
+ auto new_end =
+ std::stable_partition(m_mounts.begin(), m_mounts.end(), [](const auto& mount) { return mount->mounted; });
+
+ for (auto it = new_end; it < m_mounts.end(); ++it) {
+ m_log.info("%s: Removing mountpoint \"%s\" (reason: `remove-unmounted = true`)", name(), (*it)->mountpoint);
+ m_mountpoints.erase(
+ std::remove(m_mountpoints.begin(), m_mountpoints.end(), (*it)->mountpoint), m_mountpoints.end());
+ }
+
+ m_mounts.erase(new_end, m_mounts.end());
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate the module output
+ */
+ string fs_module::get_output() {
+ string output;
+
+ for (m_index = 0_z; m_index < m_mounts.size(); ++m_index) {
+ string mount_output = timer_module::get_output();
+ /*
+ * Add spacing before the mountpoint, but only if the mountpoint contents
+ * are not empty and there is already other content in the module.
+ */
+ if (!output.empty() && !mount_output.empty()) {
+ m_builder->spacing(m_spacing);
+ output += m_builder->flush();
+ }
+ output += mount_output;
+ }
+
+ return output;
+ }
+
+ /**
+ * Select format based on fs state
+ */
+ string fs_module::get_format() const {
+ if (!m_mounts[m_index]->mounted) {
+ return FORMAT_UNMOUNTED;
+ }
+ if (m_mounts[m_index]->percentage_used >= m_perc_used_warn && m_formatter->has_format(FORMAT_WARN)) {
+ return FORMAT_WARN;
+ }
+ return FORMAT_MOUNTED;
+ }
+
+ /**
+ * Output content using configured format tags
+ */
+ bool fs_module::build(builder* builder, const string& tag) const {
+ auto& mount = m_mounts[m_index];
+
+ auto replace_tokens = [&](const label_t& label) {
+ label->reset_tokens();
+ label->replace_token("%mountpoint%", mount->mountpoint);
+ label->replace_token("%type%", mount->type);
+ label->replace_token("%fsname%", mount->fsname);
+ label->replace_token("%percentage_free%", to_string(mount->percentage_free));
+ label->replace_token("%percentage_used%", to_string(mount->percentage_used));
+ label->replace_token(
+ "%total%", string_util::filesize(mount->bytes_total, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+ label->replace_token("%free%", string_util::filesize(mount->bytes_avail, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+ label->replace_token("%used%", string_util::filesize(mount->bytes_used, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+ };
+
+ if (tag == TAG_BAR_FREE) {
+ builder->node(m_barfree->output(mount->percentage_free));
+ } else if (tag == TAG_BAR_USED) {
+ builder->node(m_barused->output(mount->percentage_used));
+ } else if (tag == TAG_RAMP_CAPACITY) {
+ builder->node(m_rampcapacity->get_by_percentage_with_borders(mount->percentage_free, 0, m_perc_used_warn));
+ } else if (tag == TAG_LABEL_MOUNTED) {
+ replace_tokens(m_labelmounted);
+ builder->node(m_labelmounted);
+ } else if (tag == TAG_LABEL_WARN) {
+ replace_tokens(m_labelwarn);
+ builder->node(m_labelwarn);
+ } else if (tag == TAG_LABEL_UNMOUNTED) {
+ m_labelunmounted->reset_tokens();
+ m_labelunmounted->replace_token("%mountpoint%", mount->mountpoint);
+ builder->node(m_labelunmounted);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/github.hpp"
+
+#include <cassert>
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<github_module>;
+
+ /**
+ * Construct module
+ */
+ github_module::github_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<github_module>(bar, move(name_), config) {
+ m_accesstoken = m_conf.get(name(), "token");
+ m_user = m_conf.get(name(), "user", ""s);
+ m_api_url = m_conf.get(name(), "api-url", "https://api.github.com/"s);
+ if (m_api_url.back() != '/') {
+ m_api_url += '/';
+ }
+
+ set_interval(60s);
+ m_empty_notifications = m_conf.get(name(), "empty-notifications", m_empty_notifications);
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "Notifications: %notifications%");
+ }
+
+ m_formatter->add(FORMAT_OFFLINE, TAG_LABEL_OFFLINE, {TAG_LABEL_OFFLINE});
+
+ if (m_formatter->has(TAG_LABEL_OFFLINE)) {
+ m_label_offline = load_optional_label(m_conf, name(), TAG_LABEL_OFFLINE, "Offline");
+ }
+
+ update_label(0);
+ }
+
+ /**
+ * Update module contents
+ */
+ bool github_module::update() {
+ auto notification = get_number_of_notification();
+
+ update_label(notification);
+
+ return true;
+ }
+
+ string github_module::request() {
+ string content;
+ if (m_user.empty()) {
+ content = m_http.get(m_api_url + "notifications?access_token=" + m_accesstoken);
+ } else {
+ content = m_http.get(m_api_url + "notifications", m_user, m_accesstoken);
+ }
+
+ long response_code{m_http.response_code()};
+ switch (response_code) {
+ case 200:
+ break;
+ case 401:
+ throw module_error("Bad credentials");
+ case 403:
+ throw module_error("Maximum number of login attempts exceeded");
+ default:
+ throw module_error("Unspecified error (" + to_string(response_code) + ")");
+ }
+
+ return content;
+ }
+
+ int github_module::get_number_of_notification() {
+ string content;
+ try {
+ content = request();
+ } catch (application_error& e) {
+ if (!m_offline) {
+ m_log.info("%s: cannot complete the request to github: %s", name(), e.what());
+ }
+ m_offline = true;
+ return -1;
+ }
+
+ m_offline = false;
+
+ size_t pos{0};
+ size_t notifications{0};
+
+ while ((pos = content.find("\"unread\":true", pos + 1)) != string::npos) {
+ notifications++;
+ }
+
+ return notifications;
+ }
+
+ string github_module::get_format() const {
+ return m_offline ? FORMAT_OFFLINE : DEFAULT_FORMAT;
+ }
+
+ void github_module::update_label(const int notifications) {
+ if (!m_label) {
+ return;
+ }
+
+ if (0 != notifications || m_empty_notifications) {
+ m_label->reset_tokens();
+ m_label->replace_token("%notifications%", to_string(notifications));
+ } else {
+ m_label->clear();
+ }
+ }
+
+ /**
+ * Build module content
+ */
+ bool github_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ return true;
+ } else if (tag == TAG_LABEL_OFFLINE) {
+ builder->node(m_label_offline);
+ return true;
+ }
+
+ return false;
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/i3.hpp"
+
+#include <sys/socket.h>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/file.hpp"
+
+#include <i3ipc++/ipc-util.hpp>
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<i3_module>;
+
+ i3_module::i3_module(const bar_settings& bar, string name_, const config& conf)
+ : event_module<i3_module>(bar, move(name_), conf) {
+ m_router->register_action_with_data(EVENT_FOCUS, [this](const std::string& data) { action_focus(data); });
+ m_router->register_action(EVENT_NEXT, [this]() { action_next(); });
+ m_router->register_action(EVENT_PREV, [this]() { action_prev(); });
+
+ try {
+ auto socket_path = i3ipc::get_socketpath();
+ if (!file_util::exists(socket_path)) {
+ throw module_error("i3 socket does not exist: " + (socket_path.empty() ? "<empty>" : socket_path));
+ } else {
+ m_log.info("%s: Found i3 socket at '%s'", name(), socket_path);
+ }
+ } catch (const i3ipc::ipc_error& e) {
+ throw module_error("Could not find i3 socket: " + string(e.what()));
+ }
+
+ m_ipc = std::make_unique<i3ipc::connection>();
+
+ // Load configuration values
+ m_click = m_conf.get(name(), "enable-click", m_click);
+ m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+ m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
+ m_wrap = m_conf.get(name(), "wrapping-scroll", m_wrap);
+ m_indexsort = m_conf.get(name(), "index-sort", m_indexsort);
+ m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+ m_show_urgent = m_conf.get(name(), "show-urgent", m_show_urgent);
+ m_strip_wsnumbers = m_conf.get(name(), "strip-wsnumbers", m_strip_wsnumbers);
+ m_fuzzy_match = m_conf.get(name(), "fuzzy-match", m_fuzzy_match);
+
+ m_conf.warn_deprecated(name(), "wsname-maxlen", "%name:min:max%");
+
+ // Add formats and create components
+ m_formatter->add(DEFAULT_FORMAT, DEFAULT_TAGS, {TAG_LABEL_STATE, TAG_LABEL_MODE});
+
+ if (m_formatter->has(TAG_LABEL_STATE)) {
+ m_statelabels.insert(
+ make_pair(state::FOCUSED, load_optional_label(m_conf, name(), "label-focused", DEFAULT_WS_LABEL)));
+ m_statelabels.insert(
+ make_pair(state::UNFOCUSED, load_optional_label(m_conf, name(), "label-unfocused", DEFAULT_WS_LABEL)));
+ m_statelabels.insert(
+ make_pair(state::VISIBLE, load_optional_label(m_conf, name(), "label-visible", DEFAULT_WS_LABEL)));
+ m_statelabels.insert(
+ make_pair(state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
+ }
+
+ if (m_formatter->has(TAG_LABEL_MODE)) {
+ m_modelabel = load_optional_label(m_conf, name(), "label-mode", "%mode%");
+ }
+
+ m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+ m_icons = std::make_shared<iconset>();
+ m_icons->add(DEFAULT_WS_ICON, std::make_shared<label>(m_conf.get(name(), DEFAULT_WS_ICON, ""s)));
+
+ int i = 0;
+ for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
+ auto vec = string_util::tokenize(workspace, ';');
+ if (vec.size() == 2) {
+ m_icons->add(vec[0], std::make_shared<label>(vec[1]));
+ } else {
+ m_log.err("%s: Ignoring ws-icon-%d because it has %s semicolons", name(), i, vec.size() > 2? "too many" : "too few");
+ }
+
+ i++;
+ }
+
+ try {
+ if (m_modelabel) {
+ m_ipc->on_mode_event = [this](const i3ipc::mode_t& mode) {
+ m_modeactive = (mode.change != DEFAULT_MODE);
+ if (m_modeactive) {
+ m_modelabel->reset_tokens();
+ m_modelabel->replace_token("%mode%", mode.change);
+ }
+ };
+ }
+ m_ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_MODE);
+ } catch (const exception& err) {
+ throw module_error(err.what());
+ }
+ }
+
+ i3_module::workspace::operator bool() {
+ return label && *label;
+ }
+
+ void i3_module::stop() {
+ try {
+ if (m_ipc) {
+ m_log.info("%s: Disconnecting from socket", name());
+ shutdown(m_ipc->get_event_socket_fd(), SHUT_RDWR);
+ shutdown(m_ipc->get_main_socket_fd(), SHUT_RDWR);
+ }
+ } catch (...) {
+ }
+
+ event_module::stop();
+ }
+
+ bool i3_module::has_event() {
+ try {
+ m_ipc->handle_event();
+ return true;
+ } catch (const exception& err) {
+ try {
+ m_log.warn("%s: Attempting to reconnect socket (reason: %s)", name(), err.what());
+ m_ipc->connect_event_socket(true);
+ m_log.info("%s: Reconnecting socket succeeded", name());
+ } catch (const exception& err) {
+ m_log.err("%s: Failed to reconnect socket (reason: %s)", name(), err.what());
+ }
+ return false;
+ }
+ }
+
+ bool i3_module::update() {
+ /*
+ * update only populates m_workspaces and those are only needed when
+ * <label-state> appears in the format
+ */
+ if (!m_formatter->has(TAG_LABEL_STATE)) {
+ return true;
+ }
+ m_workspaces.clear();
+ i3_util::connection_t ipc;
+
+ try {
+ vector<shared_ptr<i3_util::workspace_t>> workspaces;
+
+ if (m_pinworkspaces) {
+ workspaces = i3_util::workspaces(ipc, m_bar.monitor->name, m_show_urgent);
+ } else {
+ workspaces = i3_util::workspaces(ipc);
+ }
+
+ if (m_indexsort) {
+ sort(workspaces.begin(), workspaces.end(), i3_util::ws_numsort);
+ }
+
+ for (auto&& ws : workspaces) {
+ state ws_state{state::NONE};
+
+ if (ws->focused) {
+ ws_state = state::FOCUSED;
+ } else if (ws->urgent) {
+ ws_state = state::URGENT;
+ } else if (ws->visible) {
+ ws_state = state::VISIBLE;
+ } else {
+ ws_state = state::UNFOCUSED;
+ }
+
+ string ws_name{ws->name};
+
+ // Remove workspace numbers "0:"
+ if (m_strip_wsnumbers) {
+ ws_name.erase(0, string_util::find_nth(ws_name, 0, ":", 1) + 1);
+ }
+
+ // Trim leading and trailing whitespace
+ ws_name = string_util::trim(move(ws_name), ' ');
+
+ auto icon = m_icons->get(ws->name, DEFAULT_WS_ICON, m_fuzzy_match);
+ auto label = m_statelabels.find(ws_state)->second->clone();
+
+ label->reset_tokens();
+ label->replace_token("%output%", ws->output);
+ label->replace_token("%name%", ws_name);
+ label->replace_token("%icon%", icon->get());
+ label->replace_token("%index%", to_string(ws->num));
+ m_workspaces.emplace_back(std::make_unique<workspace>(ws->name, ws_state, move(label)));
+ }
+
+ return true;
+ } catch (const exception& err) {
+ m_log.err("%s: %s", name(), err.what());
+ return false;
+ }
+ }
+
+ bool i3_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_MODE && m_modeactive) {
+ builder->node(m_modelabel);
+ } else if (tag == TAG_LABEL_STATE && !m_workspaces.empty()) {
+ if (m_scroll) {
+ builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
+ builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
+ }
+
+ bool first = true;
+ for (auto&& ws : m_workspaces) {
+ /*
+ * The separator should only be inserted in between the workspaces, so
+ * we insert it in front of all workspaces except the first one.
+ */
+ if (first) {
+ first = false;
+ } else if (*m_labelseparator) {
+ builder->node(m_labelseparator);
+ }
+
+ if (m_click) {
+ builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, ws->name, ws->label);
+ } else {
+ builder->node(ws->label);
+ }
+ }
+
+ if (m_scroll) {
+ builder->action_close();
+ builder->action_close();
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ void i3_module::action_focus(const string& ws) {
+ const i3_util::connection_t conn{};
+ m_log.info("%s: Sending workspace focus command to ipc handler", name());
+ conn.send_command(make_workspace_command(ws));
+ }
+
+ void i3_module::action_next() {
+ focus_direction(true);
+ }
+
+ void i3_module::action_prev() {
+ focus_direction(false);
+ }
+
+ void i3_module::focus_direction(bool next) {
+ const i3_util::connection_t conn{};
+
+ auto workspaces = i3_util::workspaces(conn, m_bar.monitor->name);
+ auto current_ws = std::find_if(workspaces.begin(), workspaces.end(), [](auto ws) { return ws->visible; });
+
+ if (current_ws == workspaces.end()) {
+ m_log.warn("%s: Current workspace not found", name());
+ return;
+ }
+
+ if (next && (m_wrap || std::next(current_ws) != workspaces.end())) {
+ if (!(*current_ws)->focused) {
+ m_log.info("%s: Sending workspace focus command to ipc handler", name());
+ conn.send_command(make_workspace_command((*current_ws)->name));
+ }
+ m_log.info("%s: Sending workspace next_on_output command to ipc handler", name());
+ conn.send_command("workspace next_on_output");
+ } else if (!next && (m_wrap || current_ws != workspaces.begin())) {
+ if (!(*current_ws)->focused) {
+ m_log.info("%s: Sending workspace focus command to ipc handler", name());
+ conn.send_command(make_workspace_command((*current_ws)->name));
+ }
+ m_log.info("%s: Sending workspace prev_on_output command to ipc handler", name());
+ conn.send_command("workspace prev_on_output");
+ }
+ }
+
+ string i3_module::make_workspace_command(const string& workspace) {
+ return "workspace \"" + workspace + "\"";
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/ipc.hpp"
+
+#include <unistd.h>
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<ipc_module>;
+
+ /**
+ * Load user-defined ipc hooks and
+ * create formatting tags
+ */
+ ipc_module::ipc_module(const bar_settings& bar, string name_, const config& config)
+ : module<ipc_module>(bar, move(name_), config) {
+ m_router->register_action_with_data(EVENT_SEND, [this](const std::string& data) { action_send(data); });
+ m_router->register_action_with_data(EVENT_HOOK, [this](const std::string& data) { action_hook(data); });
+ m_router->register_action(EVENT_NEXT, [this]() { action_next(); });
+ m_router->register_action(EVENT_PREV, [this]() { action_prev(); });
+ m_router->register_action(EVENT_RESET, [this]() { action_reset(); });
+
+ size_t index = 0;
+
+ for (auto&& command : m_conf.get_list<string>(name(), "hook", {})) {
+ m_hooks.emplace_back(std::make_unique<hook>(hook{name() + to_string(++index), command}));
+ }
+
+ m_log.info("%s: Loaded %d hooks", name(), m_hooks.size());
+
+ // Negative initial values should always be -1
+ m_initial = std::max(-1, m_conf.get(name(), "initial", 0) - 1);
+ if (has_initial()) {
+ if ((size_t)m_initial >= m_hooks.size()) {
+ throw module_error("Initial hook out of bounds: '" + to_string(m_initial + 1) +
+ "'. Defined hooks go from 1 to " + to_string(m_hooks.size()) + ")");
+ }
+ m_current_hook = m_initial;
+ } else {
+ m_current_hook = -1;
+ }
+
+ // clang-format off
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::LEFT, m_conf.get(name(), "click-left", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::MIDDLE, m_conf.get(name(), "click-middle", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::RIGHT, m_conf.get(name(), "click-right", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::SCROLL_UP, m_conf.get(name(), "scroll-up", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::SCROLL_DOWN, m_conf.get(name(), "scroll-down", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_LEFT, m_conf.get(name(), "double-click-left", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_MIDDLE, m_conf.get(name(), "double-click-middle", ""s)));
+ m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_RIGHT, m_conf.get(name(), "double-click-right", ""s)));
+ // clang-format on
+
+ const auto pid_token = [](const string& s) { return string_util::replace_all(s, "%pid%", to_string(getpid())); };
+
+ for (auto& action : m_actions) {
+ action.second = pid_token(action.second);
+ }
+ for (auto& hook : m_hooks) {
+ hook->command = pid_token(hook->command);
+ }
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_OUTPUT});
+
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%output%");
+
+ for (size_t i = 0; i < m_hooks.size(); i++) {
+ string format_i = "format-" + to_string(i);
+ m_formatter->add_optional(format_i, {TAG_LABEL});
+ }
+ }
+
+ /**
+ * Start module and run first defined hook if configured to
+ */
+ void ipc_module::start() {
+ this->module::start();
+ m_mainthread = thread([&] {
+ m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+ update();
+ broadcast();
+ });
+ }
+
+ void ipc_module::update() {
+ if (has_hook()) {
+ exec_hook();
+ }
+ }
+
+ /**
+ * Wrap the output with defined mouse actions
+ */
+ string ipc_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapper
+ // with the cmd handlers
+ string output{module::get_output()};
+ if (output.empty()) {
+ return "";
+ }
+
+ for (auto&& action : m_actions) {
+ if (!action.second.empty()) {
+ m_builder->action(action.first, action.second);
+ }
+ }
+
+ m_builder->node(output);
+ return m_builder->flush();
+ }
+
+ string ipc_module::get_format() const {
+ if (m_current_hook != -1 && (size_t)m_current_hook < m_hooks.size()) {
+ string format_i = "format-" + to_string(m_current_hook);
+ if (m_formatter->has_format(format_i)) {
+ return format_i;
+ } else {
+ return DEFAULT_FORMAT;
+ }
+ }
+ return DEFAULT_FORMAT;
+ }
+ /**
+ * Output content retrieved from hook commands
+ */
+ bool ipc_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ return true;
+ } else if (tag == TAG_OUTPUT) {
+ builder->node(m_output);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Map received message hook to the ones
+ * configured from the user config and
+ * execute its command
+ *
+ * This code path is deprecated, all messages to ipc modules should go through actions.
+ */
+ void ipc_module::on_message(const string& message) {
+ for (size_t i = 0; i < m_hooks.size(); i++) {
+ const auto& hook = m_hooks[i];
+ if (hook->payload == message) {
+ m_log.info("%s: Found matching hook (%s)", name(), hook->payload);
+ set_hook(i);
+ break;
+ }
+ }
+ }
+
+ void ipc_module::action_send(const string& data) {
+ m_output = data;
+ update_output();
+ }
+
+ void ipc_module::action_hook(const string& data) {
+ try {
+ int hook = std::stoi(data);
+
+ if (hook < 0 || (size_t)hook >= m_hooks.size()) {
+ m_log.err("%s: Hook action received with an out of bounds hook '%s'. Defined hooks go from 0 to %zu.", name(),
+ data, m_hooks.size() - 1);
+ } else {
+ set_hook(hook);
+ }
+ } catch (const std::invalid_argument& err) {
+ m_log.err(
+ "%s: Hook action received '%s' cannot be converted to a valid hook index. Defined hooks goes from 0 to "
+ "%zu.",
+ name(), data, m_hooks.size() - 1);
+ }
+ }
+
+ void ipc_module::action_next() {
+ hook_offset(1);
+ }
+
+ void ipc_module::action_prev() {
+ hook_offset(-1);
+ }
+
+ void ipc_module::action_reset() {
+ if (has_initial()) {
+ set_hook(m_initial);
+ } else {
+ m_current_hook = -1;
+ m_output.clear();
+
+ update_output();
+ }
+ }
+
+ /**
+ * Changes the current hook by the given offset.
+ *
+ * Also deals with the case where the there is no active hook, in which case a positive offset starts from -1 and a
+ * negative from 0. This ensures that 'next' executes the first and 'prev' the last hook if no hook is set.
+ */
+ void ipc_module::hook_offset(int offset) {
+ int start_hook;
+
+ if (has_hook()) {
+ start_hook = m_current_hook;
+ } else {
+ if (offset > 0) {
+ start_hook = -1;
+ } else {
+ start_hook = 0;
+ }
+ }
+
+ set_hook((start_hook + offset + m_hooks.size()) % m_hooks.size());
+ }
+
+ bool ipc_module::has_initial() const {
+ return m_initial >= 0;
+ }
+
+ bool ipc_module::has_hook() const {
+ return m_current_hook >= 0;
+ }
+
+ void ipc_module::set_hook(int h) {
+ assert(h >= 0 && (size_t)h < m_hooks.size());
+ m_current_hook = h;
+ exec_hook();
+ }
+
+ void ipc_module::exec_hook() {
+ // Clear the output in case the command produces no output
+ m_output.clear();
+
+ try {
+ command<output_policy::REDIRECTED> cmd(m_log, m_hooks[m_current_hook]->command);
+ cmd.exec(false);
+ cmd.tail([this](string line) { m_output = line; });
+ } catch (const exception& err) {
+ m_log.err("%s: Failed to execute hook command (err: %s)", name(), err.what());
+ m_output.clear();
+ }
+
+ update_output();
+ }
+
+ void ipc_module::update_output() {
+ if (m_label) {
+ m_label->reset_tokens();
+ m_label->replace_token("%output%", m_output);
+ }
+ broadcast();
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include <fstream>
+#include <iomanip>
+#include <istream>
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/memory.hpp"
+#include "utils/math.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<memory_module>;
+
+ memory_module::memory_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<memory_module>(bar, move(name_), config) {
+ set_interval(1s);
+ m_perc_memused_warn = m_conf.get(name(), "warn-percentage", 90);
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_USED, TAG_BAR_FREE, TAG_RAMP_USED, TAG_RAMP_FREE,
+ TAG_BAR_SWAP_USED, TAG_BAR_SWAP_FREE, TAG_RAMP_SWAP_USED, TAG_RAMP_SWAP_FREE});
+ m_formatter->add_optional(FORMAT_WARN, {TAG_LABEL_WARN, TAG_BAR_USED, TAG_BAR_FREE, TAG_RAMP_USED, TAG_RAMP_FREE,
+ TAG_BAR_SWAP_USED, TAG_BAR_SWAP_FREE, TAG_RAMP_SWAP_USED, TAG_RAMP_SWAP_FREE});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage_used%%");
+ }
+ if (m_formatter->has(TAG_LABEL_WARN)) {
+ m_labelwarn = load_optional_label(m_conf, name(), TAG_LABEL_WARN, "%percentage_used%%");
+ }
+ if (m_formatter->has(TAG_BAR_USED)) {
+ m_bar_memused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
+ }
+ if (m_formatter->has(TAG_BAR_FREE)) {
+ m_bar_memfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
+ }
+ if(m_formatter->has(TAG_RAMP_USED)) {
+ m_ramp_memused = load_ramp(m_conf, name(), TAG_RAMP_USED);
+ }
+ if(m_formatter->has(TAG_RAMP_FREE)) {
+ m_ramp_memfree = load_ramp(m_conf, name(), TAG_RAMP_FREE);
+ }
+ if (m_formatter->has(TAG_BAR_SWAP_USED)) {
+ m_bar_swapused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_SWAP_USED);
+ }
+ if (m_formatter->has(TAG_BAR_SWAP_FREE)) {
+ m_bar_swapfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_SWAP_FREE);
+ }
+ if(m_formatter->has(TAG_RAMP_SWAP_USED)) {
+ m_ramp_swapused = load_ramp(m_conf, name(), TAG_RAMP_SWAP_USED);
+ }
+ if(m_formatter->has(TAG_RAMP_SWAP_FREE)) {
+ m_ramp_swapfree = load_ramp(m_conf, name(), TAG_RAMP_SWAP_FREE);
+ }
+ }
+
+ bool memory_module::update() {
+ unsigned long long kb_total{0ULL};
+ unsigned long long kb_avail{0ULL};
+ unsigned long long kb_swap_total{0ULL};
+ unsigned long long kb_swap_free{0ULL};
+
+ try {
+ std::ifstream meminfo(PATH_MEMORY_INFO);
+ std::map<std::string, unsigned long long int> parsed;
+
+ std::string line;
+ while (std::getline(meminfo, line)) {
+ size_t sep_off = line.find(':');
+ size_t value_off = line.find_first_of("123456789", sep_off);
+
+ if (sep_off == std::string::npos || value_off == std::string::npos) continue;
+
+ std::string id = line.substr(0, sep_off);
+ unsigned long long int value = std::strtoull(&line[value_off], nullptr, 10);
+ parsed[id] = value;
+ }
+
+ kb_total = parsed["MemTotal"];
+ kb_swap_total = parsed["SwapTotal"];
+ kb_swap_free = parsed["SwapFree"];
+
+ // newer kernels (3.4+) have an accurate available memory field,
+ // see https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+ // for details
+ if (parsed.count("MemAvailable")) {
+ kb_avail = parsed["MemAvailable"];
+ } else {
+ // old kernel; give a best-effort approximation of available memory
+ kb_avail = parsed["MemFree"] + parsed["Buffers"] + parsed["Cached"] + parsed["SReclaimable"] - parsed["Shmem"];
+ }
+ } catch (const std::exception& err) {
+ m_log.err("Failed to read memory values (what: %s)", err.what());
+ }
+
+ m_perc_memfree = math_util::percentage(kb_avail, kb_total);
+ m_perc_memused = 100 - m_perc_memfree;
+ m_perc_swap_free = math_util::percentage(kb_swap_free, kb_swap_total);
+ m_perc_swap_used = 100 - m_perc_swap_free;
+
+ // replace tokens
+ const auto replace_tokens = [&](label_t& label) {
+ label->reset_tokens();
+ label->replace_token("%gb_used%", string_util::filesize_gib(kb_total - kb_avail, 2, m_bar.locale));
+ label->replace_token("%gb_free%", string_util::filesize_gib(kb_avail, 2, m_bar.locale));
+ label->replace_token("%gb_total%", string_util::filesize_gib(kb_total, 2, m_bar.locale));
+ label->replace_token("%mb_used%", string_util::filesize_mib(kb_total - kb_avail, 0, m_bar.locale));
+ label->replace_token("%mb_free%", string_util::filesize_mib(kb_avail, 0, m_bar.locale));
+ label->replace_token("%mb_total%", string_util::filesize_mib(kb_total, 0, m_bar.locale));
+ label->replace_token("%percentage_used%", to_string(m_perc_memused));
+ label->replace_token("%percentage_free%", to_string(m_perc_memfree));
+ label->replace_token("%percentage_swap_used%", to_string(m_perc_swap_used));
+ label->replace_token("%percentage_swap_free%", to_string(m_perc_swap_free));
+ label->replace_token("%mb_swap_total%", string_util::filesize_mib(kb_swap_total, 0, m_bar.locale));
+ label->replace_token("%mb_swap_free%", string_util::filesize_mib(kb_swap_free, 0, m_bar.locale));
+ label->replace_token("%mb_swap_used%", string_util::filesize_mib(kb_swap_total - kb_swap_free, 0, m_bar.locale));
+ label->replace_token("%gb_swap_total%", string_util::filesize_gib(kb_swap_total, 2, m_bar.locale));
+ label->replace_token("%gb_swap_free%", string_util::filesize_gib(kb_swap_free, 2, m_bar.locale));
+ label->replace_token("%gb_swap_used%", string_util::filesize_gib(kb_swap_total - kb_swap_free, 2, m_bar.locale));
+ label->replace_token("%used%", string_util::filesize_gib_mib(kb_total - kb_avail, 0, 2, m_bar.locale));
+ label->replace_token("%free%", string_util::filesize_gib_mib(kb_avail, 0, 2, m_bar.locale));
+ label->replace_token("%total%", string_util::filesize_gib_mib(kb_total, 0, 2, m_bar.locale));
+ label->replace_token("%swap_total%", string_util::filesize_gib_mib(kb_swap_total, 0, 2, m_bar.locale));
+ label->replace_token("%swap_free%", string_util::filesize_gib_mib(kb_swap_free, 0, 2, m_bar.locale));
+ label->replace_token("%swap_used%", string_util::filesize_gib_mib(kb_swap_total - kb_swap_free, 0, 2, m_bar.locale));
+ };
+
+ if (m_label) {
+ replace_tokens(m_label);
+ }
+
+ if (m_labelwarn) {
+ replace_tokens(m_labelwarn);
+ }
+
+ return true;
+ }
+
+ string memory_module::get_format() const {
+ if (m_perc_memused>= m_perc_memused_warn && m_formatter->has_format(FORMAT_WARN)) {
+ return FORMAT_WARN;
+ } else {
+ return DEFAULT_FORMAT;
+ }
+ }
+
+ bool memory_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_BAR_USED) {
+ builder->node(m_bar_memused->output(m_perc_memused));
+ } else if (tag == TAG_BAR_FREE) {
+ builder->node(m_bar_memfree->output(m_perc_memfree));
+ } else if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else if (tag == TAG_LABEL_WARN) {
+ builder->node(m_labelwarn);
+ } else if (tag == TAG_RAMP_FREE) {
+ builder->node(m_ramp_memfree->get_by_percentage_with_borders(m_perc_memfree, 0, m_perc_memused_warn));
+ } else if (tag == TAG_RAMP_USED) {
+ builder->node(m_ramp_memused->get_by_percentage_with_borders(m_perc_memused, 0, m_perc_memused_warn));
+ } else if (tag == TAG_BAR_SWAP_USED) {
+ builder->node(m_bar_swapused->output(m_perc_swap_used));
+ } else if (tag == TAG_BAR_SWAP_FREE) {
+ builder->node(m_bar_swapfree->output(m_perc_swap_free));
+ } else if (tag == TAG_RAMP_SWAP_FREE) {
+ builder->node(m_ramp_swapfree->get_by_percentage_with_borders(m_perc_swap_free, 0, m_perc_memused_warn));
+ } else if (tag == TAG_RAMP_SWAP_USED) {
+ builder->node(m_ramp_swapused->get_by_percentage_with_borders(m_perc_swap_used, 0, m_perc_memused_warn));
+ } else {
+ return false;
+ }
+ return true;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/menu.hpp"
+
+#include "drawtypes/label.hpp"
+#include "events/signal.hpp"
+#include "modules/meta/base.inl"
+#include "utils/actions.hpp"
+#include "utils/scope.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<menu_module>;
+
+ menu_module::menu_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<menu_module>(bar, move(name_), config) {
+ m_expand_right = m_conf.get(name(), "expand-right", m_expand_right);
+
+ m_router->register_action_with_data(EVENT_OPEN, [this](const std::string& data) { action_open(data); });
+ m_router->register_action(EVENT_CLOSE, [this]() { action_close(); });
+ m_router->register_action_with_data(EVENT_EXEC, [this](const std::string& data) { action_exec(data); });
+
+ string default_format;
+ if (m_expand_right) {
+ default_format += TAG_LABEL_TOGGLE;
+ default_format += TAG_MENU;
+ } else {
+ default_format += TAG_MENU;
+ default_format += TAG_LABEL_TOGGLE;
+ }
+
+ m_formatter->add(DEFAULT_FORMAT, default_format, {TAG_LABEL_TOGGLE, TAG_MENU});
+
+ if (m_formatter->has(TAG_LABEL_TOGGLE)) {
+ m_labelopen = load_label(m_conf, name(), "label-open");
+ m_labelclose = load_optional_label(m_conf, name(), "label-close", "x");
+ }
+
+ m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+ if (!m_formatter->has(TAG_MENU)) {
+ return;
+ }
+
+ while (true) {
+ string level_param{"menu-" + to_string(m_levels.size())};
+
+ if (m_conf.get(name(), level_param + "-0", ""s).empty()) {
+ break;
+ }
+
+ m_log.trace("%s: Creating menu level %i", name(), m_levels.size());
+ m_levels.emplace_back(std::make_unique<menu_tree>());
+
+ while (true) {
+ string item_param{level_param + "-" + to_string(m_levels.back()->items.size())};
+
+ if (m_conf.get(name(), item_param, ""s).empty()) {
+ break;
+ }
+
+ m_log.trace("%s: Creating menu level item %i", name(), m_levels.back()->items.size());
+ auto item = std::make_unique<menu_tree_item>();
+ item->label = load_label(m_conf, name(), item_param);
+ item->exec = m_conf.get(name(), item_param + "-exec", actions_util::get_action_string(*this, EVENT_CLOSE, ""));
+ m_levels.back()->items.emplace_back(move(item));
+ }
+ }
+ }
+
+ bool menu_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_TOGGLE && m_level == -1) {
+ builder->action(mousebtn::LEFT, *this, string(EVENT_OPEN), "0", m_labelopen);
+ } else if (tag == TAG_LABEL_TOGGLE && m_level > -1) {
+ builder->action(mousebtn::LEFT, *this, EVENT_CLOSE, "", m_labelclose);
+ } else if (tag == TAG_MENU && m_level > -1) {
+ auto spacing = m_formatter->get(get_format())->spacing;
+ // Insert separator after menu-toggle and before menu-items for expand-right=true
+ if (m_expand_right && *m_labelseparator) {
+ builder->node(m_labelseparator);
+ builder->spacing(spacing);
+ }
+ auto&& items = m_levels[m_level]->items;
+ for (size_t i = 0; i < items.size(); i++) {
+ auto&& item = items[i];
+ builder->action(
+ mousebtn::LEFT, *this, string(EVENT_EXEC), to_string(m_level) + "-" + to_string(i), item->label);
+ if (item != m_levels[m_level]->items.back()) {
+ builder->spacing(spacing);
+ if (*m_labelseparator) {
+ builder->node(m_labelseparator);
+ builder->spacing(spacing);
+ }
+ // Insert separator after last menu-item and before menu-toggle for expand-right=false
+ } else if (!m_expand_right && *m_labelseparator) {
+ builder->spacing(spacing);
+ builder->node(m_labelseparator);
+ }
+ }
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void menu_module::action_open(const string& data) {
+ string level = data.empty() ? "0" : data;
+ int level_num = m_level = std::strtol(level.c_str(), nullptr, 10);
+ m_log.info("%s: Opening menu level '%i'", name(), static_cast<int>(level_num));
+
+ if (static_cast<size_t>(level_num) >= m_levels.size()) {
+ m_log.warn("%s: Cannot open unexisting menu level '%s'", name(), level);
+ m_level = -1;
+ } else {
+ m_level = level_num;
+ }
+ broadcast();
+ }
+
+ void menu_module::action_close() {
+ m_log.info("%s: Closing menu tree", name());
+ if (m_level != -1) {
+ m_level = -1;
+ broadcast();
+ }
+ }
+
+ void menu_module::action_exec(const string& element) {
+ auto sep = element.find("-");
+
+ if (sep == element.npos) {
+ m_log.err("%s: Malformed data for exec action (data: '%s')", name(), element);
+ }
+
+ auto level = std::strtoul(element.substr(0, sep).c_str(), nullptr, 10);
+ auto item = std::strtoul(element.substr(sep + 1).c_str(), nullptr, 10);
+
+ if (level >= m_levels.size() || item >= m_levels[level]->items.size()) {
+ m_log.err("%s: menu-exec-%d-%d doesn't exist (data: '%s')", name(), level, item, element);
+ }
+
+ string exec = m_levels[level]->items[item]->exec;
+ // Send exec action to be executed
+ m_sig.emit(signals::ipc::action{std::move(exec)});
+
+ /*
+ * Only close the menu if the executed action is visible in the menu
+ * This stops the menu from closing, if the exec action comes from an
+ * external source
+ */
+ if (m_level == (int)level) {
+ m_level = -1;
+ broadcast();
+ }
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/meta/base.hpp"
+
+#include <utility>
+
+#include "components/builder.hpp"
+#include "drawtypes/label.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ // module_format {{{
+
+ string module_format::decorate(builder* builder, string output) {
+ if (output.empty()) {
+ builder->flush();
+ return "";
+ }
+ if (offset) {
+ builder->offset(offset);
+ }
+ if (margin) {
+ builder->spacing(margin);
+ }
+ if (bg.has_color()) {
+ builder->background(bg);
+ }
+ if (fg.has_color()) {
+ builder->foreground(fg);
+ }
+ if (ul.has_color()) {
+ builder->underline(ul);
+ }
+ if (ol.has_color()) {
+ builder->overline(ol);
+ }
+ if (font > 0) {
+ builder->font(font);
+ }
+ if (padding) {
+ builder->spacing(padding);
+ }
+
+ builder->node(prefix);
+
+ if (bg.has_color()) {
+ builder->background(bg);
+ }
+ if (fg.has_color()) {
+ builder->foreground(fg);
+ }
+ if (ul.has_color()) {
+ builder->underline(ul);
+ }
+ if (ol.has_color()) {
+ builder->overline(ol);
+ }
+
+ builder->node(output);
+ builder->node(suffix);
+
+ if (padding) {
+ builder->spacing(padding);
+ }
+ if (font > 0) {
+ builder->font_close();
+ }
+ if (ol.has_color()) {
+ builder->overline_close();
+ }
+ if (ul.has_color()) {
+ builder->underline_close();
+ }
+ if (fg.has_color()) {
+ builder->foreground_close();
+ }
+ if (bg.has_color()) {
+ builder->background_close();
+ }
+ if (margin) {
+ builder->spacing(margin);
+ }
+
+ return builder->flush();
+ }
+
+ // }}}
+ // module_formatter {{{
+
+ void module_formatter::add_value(string&& name, string&& value, vector<string>&& tags, vector<string>&& whitelist) {
+ const auto formatdef = [&](const string& param, const auto& fallback) {
+ return m_conf.get("settings", "format-" + param, fallback);
+ };
+
+ auto format = make_unique<module_format>();
+ format->value = move(value);
+ format->fg = m_conf.get(m_modname, name + "-foreground", formatdef("foreground", format->fg));
+ format->bg = m_conf.get(m_modname, name + "-background", formatdef("background", format->bg));
+ format->ul = m_conf.get(m_modname, name + "-underline", formatdef("underline", format->ul));
+ format->ol = m_conf.get(m_modname, name + "-overline", formatdef("overline", format->ol));
+ format->ulsize = m_conf.get(m_modname, name + "-underline-size", formatdef("underline-size", format->ulsize));
+ format->olsize = m_conf.get(m_modname, name + "-overline-size", formatdef("overline-size", format->olsize));
+ format->spacing = m_conf.get(m_modname, name + "-spacing", formatdef("spacing", format->spacing));
+ format->padding = m_conf.get(m_modname, name + "-padding", formatdef("padding", format->padding));
+ format->margin = m_conf.get(m_modname, name + "-margin", formatdef("margin", format->margin));
+ format->offset = m_conf.get(m_modname, name + "-offset", formatdef("offset", format->offset));
+ format->font = m_conf.get(m_modname, name + "-font", formatdef("font", format->font));
+
+ try {
+ format->prefix = load_label(m_conf, m_modname, name + "-prefix");
+ } catch (const key_error& err) {
+ // prefix not defined
+ }
+
+ try {
+ format->suffix = load_label(m_conf, m_modname, name + "-suffix");
+ } catch (const key_error& err) {
+ // suffix not defined
+ }
+
+ vector<string> tag_collection;
+ tag_collection.reserve(tags.size() + whitelist.size());
+ tag_collection.insert(tag_collection.end(), tags.begin(), tags.end());
+ tag_collection.insert(tag_collection.end(), whitelist.begin(), whitelist.end());
+
+ size_t start, end;
+ while ((start = value.find('<')) != string::npos && (end = value.find('>', start)) != string::npos) {
+ if (start > 0) {
+ value.erase(0, start);
+ end -= start;
+ start = 0;
+ }
+ string tag{value.substr(start, end + 1)};
+ if (find(tag_collection.begin(), tag_collection.end(), tag) == tag_collection.end()) {
+ throw undefined_format_tag(tag + " is not a valid format tag for \"" + name + "\"");
+ }
+ value.erase(0, tag.size());
+ }
+
+ m_formats.insert(make_pair(move(name), move(format)));
+ }
+
+ void module_formatter::add(string name, string fallback, vector<string>&& tags, vector<string>&& whitelist) {
+ string value = m_conf.get(m_modname, name, move(fallback));
+ add_value(move(name), move(value), forward<vector<string>>(tags), forward<vector<string>>(whitelist));
+ }
+
+ void module_formatter::add_optional(string name, vector<string>&& tags, vector<string>&& whitelist) {
+ if (m_conf.has(m_modname, name)) {
+ string value = m_conf.get(m_modname, name);
+ add_value(move(name), move(value), move(tags), move(whitelist));
+ }
+ }
+
+ bool module_formatter::has(const string& tag, const string& format_name) {
+ auto format = m_formats.find(format_name);
+ if (format == m_formats.end()) {
+ return false;
+ }
+ return format->second->value.find(tag) != string::npos;
+ }
+
+ bool module_formatter::has(const string& tag) {
+ for (auto&& format : m_formats) {
+ if (format.second->value.find(tag) != string::npos) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool module_formatter::has_format(const string& format_name) {
+ return m_formats.find(format_name) != m_formats.end();
+ }
+
+ shared_ptr<module_format> module_formatter::get(const string& format_name) {
+ auto format = m_formats.find(format_name);
+ if (format == m_formats.end()) {
+ throw undefined_format("Format \"" + format_name + "\" has not been added");
+ }
+ return format->second;
+ }
+
+ // }}}
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/meta/factory.hpp"
+
+#include "modules/meta/all.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+
+ /**
+ * Function pointer for creating a module.
+ */
+ using factory_fun = module_t (*)(const bar_settings&, string&&, const config&);
+ using factory_map = map<string, factory_fun>;
+
+ /**
+ * Creates a factory function for constructing a module.
+ *
+ * @tparam M name of the module class
+ */
+ template <typename M>
+ static constexpr factory_fun get_factory() {
+ return [](const bar_settings& bar, string&& module_name, const config& config) -> module_t {
+ return make_shared<M>(bar, move(module_name), config);
+ };
+ }
+
+ /**
+ * Creates an entry for the factories map.
+ *
+ * Each entry is a pair containing the module type and the factory function.
+ *
+ * @tparam M name of the module class
+ */
+ template <typename M>
+ static factory_map::value_type map_entry() {
+ return std::make_pair(std::string(M::TYPE), get_factory<M>());
+ }
+
+ template<const char* module_type>
+ static factory_map::value_type map_entry_unsupported() {
+ return {
+ module_type,
+ [](const bar_settings&, string&&, const config&) -> module_t {
+ throw application_error("No built-in support for '" + string(module_type) + "'");
+ }
+ };
+ }
+
+ /**
+ * Factory function for each module type.
+ */
+ static const factory_map factories = {
+ map_entry<counter_module>(),
+ map_entry<backlight_module>(),
+ map_entry<battery_module>(),
+ map_entry<bspwm_module>(),
+ map_entry<cpu_module>(),
+ map_entry<date_module>(),
+#if ENABLE_CURL
+ map_entry<github_module>(),
+#else
+ map_entry_unsupported<GITHUB_TYPE>(),
+#endif
+ map_entry<fs_module>(),
+ map_entry<memory_module>(),
+#if ENABLE_I3
+ map_entry<i3_module>(),
+#else
+ map_entry_unsupported<I3_TYPE>(),
+#endif
+#if ENABLE_MPD
+ map_entry<mpd_module>(),
+#else
+ map_entry_unsupported<MPD_TYPE>(),
+#endif
+#if ENABLE_ALSA
+ map_entry<alsa_module>(),
+#else
+ map_entry_unsupported<ALSA_TYPE>(),
+#endif
+#if ENABLE_PULSEAUDIO
+ map_entry<pulseaudio_module>(),
+#else
+ map_entry_unsupported<PULSEAUDIO_TYPE>(),
+#endif
+#if ENABLE_NETWORK
+ map_entry<network_module>(),
+#else
+ map_entry_unsupported<NETWORK_TYPE>(),
+#endif
+ map_entry<temperature_module>(),
+ map_entry<xbacklight_module>(),
+#if ENABLE_XKEYBOARD
+ map_entry<xkeyboard_module>(),
+#else
+ map_entry_unsupported<XKEYBOARD_TYPE>(),
+#endif
+ map_entry<xwindow_module>(),
+ map_entry<xworkspaces_module>(),
+ map_entry<tray_module>(),
+ map_entry<text_module>(),
+ map_entry<script_module>(),
+ map_entry<menu_module>(),
+ map_entry<ipc_module>(),
+ };
+
+ module_t make_module(string&& type, const bar_settings& bar, string module_name, const logger& log, const config& config) {
+ string actual_type = type;
+
+ if (type == "internal/volume") {
+ log.warn("internal/volume is deprecated, use %s instead", ALSA_TYPE);
+ actual_type = ALSA_TYPE;
+ }
+
+ auto it = factories.find(actual_type);
+ if (it != factories.end()) {
+ return it->second(bar, std::move(module_name), config);
+ } else {
+ throw application_error("Unknown module: " + type);
+ }
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/mpd.hpp"
+
+#include <csignal>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<mpd_module>;
+
+ mpd_module::mpd_module(const bar_settings& bar, string name_, const config& config)
+ : event_module<mpd_module>(bar, move(name_), config) {
+ m_router->register_action(EVENT_PLAY, [this]() { action_play(); });
+ m_router->register_action(EVENT_PAUSE, [this]() { action_pause(); });
+ m_router->register_action(EVENT_STOP, [this]() { action_stop(); });
+ m_router->register_action(EVENT_PREV, [this]() { action_prev(); });
+ m_router->register_action(EVENT_NEXT, [this]() { action_next(); });
+ m_router->register_action(EVENT_REPEAT, [this]() { action_repeat(); });
+ m_router->register_action(EVENT_SINGLE, [this]() { action_single(); });
+ m_router->register_action(EVENT_RANDOM, [this]() { action_random(); });
+ m_router->register_action(EVENT_CONSUME, [this]() { action_consume(); });
+ m_router->register_action_with_data(EVENT_SEEK, [this](const std::string& data) { action_seek(data); });
+
+ m_host = m_conf.get(name(), "host", m_host);
+ m_port = m_conf.get(name(), "port", m_port);
+ m_pass = m_conf.get(name(), "password", m_pass);
+ m_synctime = m_conf.get(name(), "interval", m_synctime);
+
+ // Add formats and elements {{{
+ auto format_online = m_conf.get<string>(name(), FORMAT_ONLINE, TAG_LABEL_SONG);
+ for (auto&& format : {FORMAT_PLAYING, FORMAT_PAUSED, FORMAT_STOPPED}) {
+ m_formatter->add(format, format_online,
+ {TAG_BAR_PROGRESS, TAG_TOGGLE, TAG_TOGGLE_STOP, TAG_LABEL_SONG, TAG_LABEL_TIME, TAG_ICON_RANDOM,
+ TAG_ICON_REPEAT, TAG_ICON_REPEAT_ONE, TAG_ICON_SINGLE, TAG_ICON_PREV, TAG_ICON_STOP, TAG_ICON_PLAY,
+ TAG_ICON_PAUSE, TAG_ICON_NEXT, TAG_ICON_SEEKB, TAG_ICON_SEEKF, TAG_ICON_CONSUME});
+
+ auto mod_format = m_formatter->get(format);
+
+ mod_format->fg = m_conf.get(name(), FORMAT_ONLINE + "-foreground"s, mod_format->fg);
+ mod_format->bg = m_conf.get(name(), FORMAT_ONLINE + "-background"s, mod_format->bg);
+ mod_format->ul = m_conf.get(name(), FORMAT_ONLINE + "-underline"s, mod_format->ul);
+ mod_format->ol = m_conf.get(name(), FORMAT_ONLINE + "-overline"s, mod_format->ol);
+ mod_format->ulsize = m_conf.get(name(), FORMAT_ONLINE + "-underline-size"s, mod_format->ulsize);
+ mod_format->olsize = m_conf.get(name(), FORMAT_ONLINE + "-overline-size"s, mod_format->olsize);
+ mod_format->spacing = m_conf.get(name(), FORMAT_ONLINE + "-spacing"s, mod_format->spacing);
+ mod_format->padding = m_conf.get(name(), FORMAT_ONLINE + "-padding"s, mod_format->padding);
+ mod_format->margin = m_conf.get(name(), FORMAT_ONLINE + "-margin"s, mod_format->margin);
+ mod_format->offset = m_conf.get(name(), FORMAT_ONLINE + "-offset"s, mod_format->offset);
+ mod_format->font = m_conf.get(name(), FORMAT_ONLINE + "-font"s, mod_format->font);
+
+ try {
+ mod_format->prefix = load_label(m_conf, name(), FORMAT_ONLINE + "-prefix"s);
+ } catch (const key_error& err) {
+ // format-online-prefix not defined
+ }
+
+ try {
+ mod_format->suffix = load_label(m_conf, name(), FORMAT_ONLINE + "-suffix"s);
+ } catch (const key_error& err) {
+ // format-online-suffix not defined
+ }
+ }
+
+ m_formatter->add(FORMAT_OFFLINE, "", {TAG_LABEL_OFFLINE});
+
+ m_icons = std::make_shared<iconset>();
+
+ if (m_formatter->has(TAG_ICON_PLAY) || m_formatter->has(TAG_TOGGLE) || m_formatter->has(TAG_TOGGLE_STOP)) {
+ m_icons->add("play", load_label(m_conf, name(), TAG_ICON_PLAY));
+ }
+ if (m_formatter->has(TAG_ICON_PAUSE) || m_formatter->has(TAG_TOGGLE)) {
+ m_icons->add("pause", load_label(m_conf, name(), TAG_ICON_PAUSE));
+ }
+ if (m_formatter->has(TAG_ICON_STOP) || m_formatter->has(TAG_TOGGLE_STOP)) {
+ m_icons->add("stop", load_label(m_conf, name(), TAG_ICON_STOP));
+ }
+ if (m_formatter->has(TAG_ICON_PREV)) {
+ m_icons->add("prev", load_label(m_conf, name(), TAG_ICON_PREV));
+ }
+ if (m_formatter->has(TAG_ICON_NEXT)) {
+ m_icons->add("next", load_label(m_conf, name(), TAG_ICON_NEXT));
+ }
+ if (m_formatter->has(TAG_ICON_SEEKB)) {
+ m_icons->add("seekb", load_label(m_conf, name(), TAG_ICON_SEEKB));
+ }
+ if (m_formatter->has(TAG_ICON_SEEKF)) {
+ m_icons->add("seekf", load_label(m_conf, name(), TAG_ICON_SEEKF));
+ }
+ if (m_formatter->has(TAG_ICON_RANDOM)) {
+ m_icons->add("random", load_label(m_conf, name(), TAG_ICON_RANDOM));
+ }
+ if (m_formatter->has(TAG_ICON_REPEAT)) {
+ m_icons->add("repeat", load_label(m_conf, name(), TAG_ICON_REPEAT));
+ }
+
+ if (m_formatter->has(TAG_ICON_SINGLE)) {
+ m_icons->add("single", load_label(m_conf, name(), TAG_ICON_SINGLE));
+ } else if (m_formatter->has(TAG_ICON_REPEAT_ONE)) {
+ m_conf.warn_deprecated(name(), "icon-repeatone", "icon-single");
+
+ m_icons->add("single", load_label(m_conf, name(), TAG_ICON_REPEAT_ONE));
+ }
+
+ if (m_formatter->has(TAG_ICON_CONSUME)) {
+ m_icons->add("consume", load_label(m_conf, name(), TAG_ICON_CONSUME));
+ }
+
+ if (m_formatter->has(TAG_LABEL_SONG)) {
+ m_label_song = load_optional_label(m_conf, name(), TAG_LABEL_SONG, "%artist% - %title%");
+ }
+ if (m_formatter->has(TAG_LABEL_TIME)) {
+ m_label_time = load_optional_label(m_conf, name(), TAG_LABEL_TIME, "%elapsed% / %total%");
+ }
+ if (m_formatter->has(TAG_ICON_RANDOM) || m_formatter->has(TAG_ICON_REPEAT) ||
+ m_formatter->has(TAG_ICON_REPEAT_ONE) || m_formatter->has(TAG_ICON_SINGLE) ||
+ m_formatter->has(TAG_ICON_CONSUME)) {
+ m_toggle_on_color = m_conf.get(name(), "toggle-on-foreground", rgba{});
+ m_toggle_off_color = m_conf.get(name(), "toggle-off-foreground", rgba{});
+ }
+ if (m_formatter->has(TAG_LABEL_OFFLINE, FORMAT_OFFLINE)) {
+ m_label_offline = load_label(m_conf, name(), TAG_LABEL_OFFLINE);
+ }
+ if (m_formatter->has(TAG_BAR_PROGRESS)) {
+ m_bar_progress = load_progressbar(m_bar, m_conf, name(), TAG_BAR_PROGRESS);
+ }
+
+ // }}}
+
+ m_lastsync = chrono::steady_clock::now();
+
+ try {
+ m_mpd = std::make_unique<mpdconnection>(m_log, m_host, m_port, m_pass);
+ m_mpd->connect();
+ m_status = m_mpd->get_status();
+ } catch (const mpd_exception& err) {
+ m_log.err("%s: %s", name(), err.what());
+ m_mpd.reset();
+ }
+ }
+
+ void mpd_module::teardown() {
+ m_mpd.reset();
+ }
+
+ inline bool mpd_module::connected() const {
+ return m_mpd && m_mpd->connected();
+ }
+
+ void mpd_module::idle() {
+ if (connected()) {
+ m_quick_attempts = 0;
+ sleep(80ms);
+ } else {
+ sleep(m_quick_attempts++ < 5 ? 0.5s : 2s);
+ }
+ }
+
+ bool mpd_module::has_event() {
+ bool def = false;
+
+ if (!connected() && m_statebroadcasted == mpd::connection_state::CONNECTED) {
+ def = true;
+ } else if (connected() && m_statebroadcasted == mpd::connection_state::DISCONNECTED) {
+ def = true;
+ }
+
+ try {
+ if (!m_mpd) {
+ m_mpd = std::make_unique<mpdconnection>(m_log, m_host, m_port, m_pass);
+ }
+ if (!connected()) {
+ m_mpd->connect();
+ }
+ } catch (const mpd_exception& err) {
+ m_log.err("%s: %s", name(), err.what());
+ m_mpd.reset();
+ return def;
+ }
+
+ if (!connected()) {
+ return def;
+ }
+
+ if (!m_status) {
+ m_status = m_mpd->get_status_safe();
+ }
+
+ try {
+ m_mpd->idle();
+
+ int idle_flags = 0;
+ if ((idle_flags = m_mpd->noidle()) != 0) {
+ // Update status on every event
+ m_status->update(idle_flags, m_mpd.get());
+ return true;
+ }
+ } catch (const mpd_exception& err) {
+ m_log.err("%s: %s", name(), err.what());
+ m_mpd.reset();
+ return def;
+ }
+
+ if ((m_label_time || m_bar_progress) && m_status->match_state(mpdstate::PLAYING)) {
+ auto now = chrono::steady_clock::now();
+ auto diff = now - m_lastsync;
+
+ if (chrono::duration_cast<chrono::milliseconds>(diff).count() > m_synctime * 1000) {
+ m_lastsync = now;
+ return true;
+ }
+ }
+
+ return def;
+ }
+
+ bool mpd_module::update() {
+ if (connected()) {
+ m_statebroadcasted = mpd::connection_state::CONNECTED;
+ } else if (!connected() && m_statebroadcasted != mpd::connection_state::DISCONNECTED) {
+ m_statebroadcasted = mpd::connection_state::DISCONNECTED;
+ } else if (!connected()) {
+ return false;
+ }
+
+ if (!m_status) {
+ if (connected() && (m_status = m_mpd->get_status_safe())) {
+ return false;
+ }
+ }
+
+ if (m_status && m_status->match_state(mpdstate::PLAYING)) {
+ // Always update the status while playing
+ m_status->update(-1, m_mpd.get());
+ }
+
+ string artist;
+ string album_artist;
+ string album;
+ string title;
+ string date;
+ string elapsed_str;
+ string total_str;
+
+ try {
+ if (m_status) {
+ elapsed_str = m_status->get_formatted_elapsed();
+ total_str = m_status->get_formatted_total();
+ }
+
+ if (m_mpd) {
+ auto song = m_mpd->get_song();
+
+ if (song) {
+ artist = song->get_artist();
+ album_artist = song->get_album_artist();
+ album = song->get_album();
+ title = song->get_title();
+ date = song->get_date();
+ }
+ }
+ } catch (const mpd_exception& err) {
+ m_log.err("%s: %s", name(), err.what());
+ m_mpd.reset();
+ }
+
+ if (m_label_song) {
+ m_label_song->reset_tokens();
+ m_label_song->replace_token("%artist%", !artist.empty() ? artist : "untitled artist");
+ m_label_song->replace_token("%album-artist%", !album_artist.empty() ? album_artist : "untitled album artist");
+ m_label_song->replace_token("%album%", !album.empty() ? album : "untitled album");
+ m_label_song->replace_token("%title%", !title.empty() ? title : "untitled track");
+ m_label_song->replace_token("%date%", !date.empty() ? date : "unknown date");
+ }
+
+ if (m_label_time) {
+ m_label_time->reset_tokens();
+ m_label_time->replace_token("%elapsed%", elapsed_str);
+ m_label_time->replace_token("%total%", total_str);
+ }
+
+ if (m_icons->has("random")) {
+ m_icons->get("random")->m_foreground = m_status && m_status->random() ? m_toggle_on_color : m_toggle_off_color;
+ }
+ if (m_icons->has("repeat")) {
+ m_icons->get("repeat")->m_foreground = m_status && m_status->repeat() ? m_toggle_on_color : m_toggle_off_color;
+ }
+ if (m_icons->has("single")) {
+ m_icons->get("single")->m_foreground = m_status && m_status->single() ? m_toggle_on_color : m_toggle_off_color;
+ }
+ if (m_icons->has("consume")) {
+ m_icons->get("consume")->m_foreground = m_status && m_status->consume() ? m_toggle_on_color : m_toggle_off_color;
+ }
+
+ return true;
+ }
+
+ string mpd_module::get_format() const {
+ if (!connected()) {
+ return FORMAT_OFFLINE;
+ } else if (m_status->match_state(mpdstate::PLAYING)) {
+ return FORMAT_PLAYING;
+ } else if (m_status->match_state(mpdstate::PAUSED)) {
+ return FORMAT_PAUSED;
+ } else {
+ return FORMAT_STOPPED;
+ }
+ }
+
+ string mpd_module::get_output() {
+ if (m_status && m_status->get_queuelen() == 0) {
+ m_log.info("%s: Hiding module since queue is empty", name());
+ return "";
+ } else {
+ return event_module::get_output();
+ }
+ }
+
+ bool mpd_module::build(builder* builder, const string& tag) const {
+ bool is_playing = m_status && m_status->match_state(mpdstate::PLAYING);
+ bool is_paused = m_status && m_status->match_state(mpdstate::PAUSED);
+ bool is_stopped = m_status && m_status->match_state(mpdstate::STOPPED);
+
+ if (tag == TAG_LABEL_SONG && !is_stopped) {
+ builder->node(m_label_song);
+ } else if (tag == TAG_LABEL_TIME && !is_stopped) {
+ builder->node(m_label_time);
+ } else if (tag == TAG_BAR_PROGRESS && !is_stopped) {
+ builder->node(m_bar_progress->output(!m_status ? 0 : m_status->get_elapsed_percentage()));
+ } else if (tag == TAG_LABEL_OFFLINE) {
+ builder->node(m_label_offline);
+ } else if (tag == TAG_ICON_RANDOM) {
+ builder->action(mousebtn::LEFT, *this, EVENT_RANDOM, "", m_icons->get("random"));
+ } else if (tag == TAG_ICON_REPEAT) {
+ builder->action(mousebtn::LEFT, *this, EVENT_REPEAT, "", m_icons->get("repeat"));
+ } else if (tag == TAG_ICON_REPEAT_ONE || tag == TAG_ICON_SINGLE) {
+ builder->action(mousebtn::LEFT, *this, EVENT_SINGLE, "", m_icons->get("single"));
+ } else if (tag == TAG_ICON_CONSUME) {
+ builder->action(mousebtn::LEFT, *this, EVENT_CONSUME, "", m_icons->get("consume"));
+ } else if (tag == TAG_ICON_PREV) {
+ builder->action(mousebtn::LEFT, *this, EVENT_PREV, "", m_icons->get("prev"));
+ } else if ((tag == TAG_ICON_STOP || tag == TAG_TOGGLE_STOP) && (is_playing || is_paused)) {
+ builder->action(mousebtn::LEFT, *this, EVENT_STOP, "", m_icons->get("stop"));
+ } else if ((tag == TAG_ICON_PAUSE || tag == TAG_TOGGLE) && is_playing) {
+ builder->action(mousebtn::LEFT, *this, EVENT_PAUSE, "", m_icons->get("pause"));
+ } else if ((tag == TAG_ICON_PLAY || tag == TAG_TOGGLE || tag == TAG_TOGGLE_STOP) && !is_playing) {
+ builder->action(mousebtn::LEFT, *this, EVENT_PLAY, "", m_icons->get("play"));
+ } else if (tag == TAG_ICON_NEXT) {
+ builder->action(mousebtn::LEFT, *this, EVENT_NEXT, "", m_icons->get("next"));
+ } else if (tag == TAG_ICON_SEEKB) {
+ builder->action(mousebtn::LEFT, *this, EVENT_SEEK, "-5"s, m_icons->get("seekb"));
+ } else if (tag == TAG_ICON_SEEKF) {
+ builder->action(mousebtn::LEFT, *this, EVENT_SEEK, "+5"s, m_icons->get("seekf"));
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+/**
+ * Small macro to create a temporary mpd connection for the action handlers.
+ *
+ * We have to create a separate mpd instance because actions run in the
+ * controller thread and the `m_mpd` pointer is used in the module thread.
+ */
+#define MPD_CONNECT() \
+ auto mpd = std::make_unique<mpdconnection>(m_log, m_host, m_port, m_pass); \
+ mpd->connect(); \
+ auto status = mpd->get_status()
+
+ void mpd_module::action_play() {
+ MPD_CONNECT();
+ if (!status->match_state(mpdstate::PLAYING)) {
+ mpd->play();
+ }
+ }
+
+ void mpd_module::action_pause() {
+ MPD_CONNECT();
+
+ if (!status->match_state(mpdstate::PAUSED)) {
+ mpd->pause(true);
+ }
+ }
+
+ void mpd_module::action_stop() {
+ MPD_CONNECT();
+
+ if (!status->match_state(mpdstate::STOPPED)) {
+ mpd->stop();
+ }
+ }
+
+ void mpd_module::action_prev() {
+ MPD_CONNECT();
+
+ if (!status->match_state(mpdstate::STOPPED)) {
+ mpd->prev();
+ }
+ }
+
+ void mpd_module::action_next() {
+ MPD_CONNECT();
+
+ if (!status->match_state(mpdstate::STOPPED)) {
+ mpd->next();
+ }
+ }
+
+ void mpd_module::action_repeat() {
+ MPD_CONNECT();
+ mpd->set_repeat(!status->repeat());
+ }
+
+ void mpd_module::action_single() {
+ MPD_CONNECT();
+ mpd->set_single(!status->single());
+ }
+
+ void mpd_module::action_random() {
+ MPD_CONNECT();
+ mpd->set_random(!status->random());
+ }
+
+ void mpd_module::action_consume() {
+ MPD_CONNECT();
+ mpd->set_consume(!status->consume());
+ }
+
+ void mpd_module::action_seek(const string& data) {
+ MPD_CONNECT();
+
+ int percentage = 0;
+ if (data.empty()) {
+ return;
+ } else if (data[0] == '+') {
+ percentage = status->get_elapsed_percentage() + std::strtol(data.substr(1).c_str(), nullptr, 10);
+ } else if (data[0] == '-') {
+ percentage = status->get_elapsed_percentage() - std::strtol(data.substr(1).c_str(), nullptr, 10);
+ } else {
+ percentage = std::strtol(data.c_str(), nullptr, 10);
+ }
+ mpd->seek(status->get_songid(), status->get_seek_position(percentage));
+ }
+
+#undef MPD_CONNECT
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/network.hpp"
+
+#include "drawtypes/animation.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<network_module>;
+
+ network_module::network_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<network_module>(bar, move(name_), config) {
+ // Load configuration values
+ m_interface = m_conf.get(name(), "interface", m_interface);
+
+ if (m_interface.empty()) {
+ std::string type = m_conf.get(name(), "interface-type");
+ if (type == "wired") {
+ m_interface = net::find_wired_interface();
+ if (!m_interface.empty()) {
+ m_log.notice("%s: Discovered wired interface %s", name(), m_interface);
+ }
+ } else if (type == "wireless") {
+ m_interface = net::find_wireless_interface();
+ if (!m_interface.empty()) {
+ m_log.notice("%s: Discovered wireless interface %s", name(), m_interface);
+ }
+ } else {
+ throw module_error("Invalid interface type '" + type + "'");
+ }
+
+ if (m_interface.empty()) {
+ throw module_error("No interface found for type '" + type + "'");
+ }
+ }
+
+ if (m_interface.empty()) {
+ throw module_error("Missing 'interface' or 'interface-type'");
+ }
+
+ if (!net::is_interface_valid(m_interface)) {
+ throw module_error("Invalid network interface \"" + m_interface + "\"");
+ }
+
+ auto canonical = net::get_canonical_interface(m_interface);
+
+ if (canonical.second) {
+ m_log.info(
+ "%s: Replacing given interface '%s' with its canonical name '%s'", name(), m_interface, canonical.first);
+ m_interface = canonical.first;
+ }
+
+ m_ping_nth_update = m_conf.get(name(), "ping-interval", m_ping_nth_update);
+ m_udspeed_minwidth = m_conf.get(name(), "udspeed-minwidth", m_udspeed_minwidth);
+ m_accumulate = m_conf.get(name(), "accumulate-stats", m_accumulate);
+ set_interval(1s);
+ m_unknown_up = m_conf.get<bool>(name(), "unknown-as-up", false);
+ m_udspeed_unit = m_conf.get<string>(name(), "speed-unit", m_udspeed_unit);
+
+ m_conf.warn_deprecated(name(), "udspeed-minwidth", "%downspeed:min:max% and %upspeed:min:max%");
+
+ // Add formats
+ m_formatter->add(FORMAT_CONNECTED, TAG_LABEL_CONNECTED, {TAG_RAMP_SIGNAL, TAG_RAMP_QUALITY, TAG_LABEL_CONNECTED});
+ m_formatter->add(FORMAT_DISCONNECTED, TAG_LABEL_DISCONNECTED, {TAG_LABEL_DISCONNECTED});
+
+ // Create elements for format-connected
+ if (m_formatter->has(TAG_RAMP_SIGNAL, FORMAT_CONNECTED)) {
+ m_ramp_signal = load_ramp(m_conf, name(), TAG_RAMP_SIGNAL);
+ }
+ if (m_formatter->has(TAG_RAMP_QUALITY, FORMAT_CONNECTED)) {
+ m_ramp_quality = load_ramp(m_conf, name(), TAG_RAMP_QUALITY);
+ }
+ if (m_formatter->has(TAG_LABEL_CONNECTED, FORMAT_CONNECTED)) {
+ m_label[connection_state::CONNECTED] =
+ load_optional_label(m_conf, name(), TAG_LABEL_CONNECTED, "%ifname% %local_ip%");
+ }
+
+ // Create elements for format-disconnected
+ if (m_formatter->has(TAG_LABEL_DISCONNECTED, FORMAT_DISCONNECTED)) {
+ m_label[connection_state::DISCONNECTED] = load_optional_label(m_conf, name(), TAG_LABEL_DISCONNECTED, "");
+ m_label[connection_state::DISCONNECTED]->reset_tokens();
+ m_label[connection_state::DISCONNECTED]->replace_token("%ifname%", m_interface);
+ }
+
+ // Create elements for format-packetloss if we are told to test connectivity
+ if (m_ping_nth_update > 0) {
+ m_formatter->add(FORMAT_PACKETLOSS, TAG_LABEL_CONNECTED,
+ {TAG_ANIMATION_PACKETLOSS, TAG_LABEL_PACKETLOSS, TAG_LABEL_CONNECTED});
+
+ if (m_formatter->has(TAG_LABEL_PACKETLOSS, FORMAT_PACKETLOSS)) {
+ m_label[connection_state::PACKETLOSS] = load_optional_label(m_conf, name(), TAG_LABEL_PACKETLOSS, "");
+ }
+ if (m_formatter->has(TAG_ANIMATION_PACKETLOSS, FORMAT_PACKETLOSS)) {
+ m_animation_packetloss = load_animation(m_conf, name(), TAG_ANIMATION_PACKETLOSS);
+ }
+ }
+
+ // Get an intstance of the network interface
+ if (net::is_wireless_interface(m_interface)) {
+ m_wireless = std::make_unique<net::wireless_network>(m_interface);
+ m_wireless->set_unknown_up(m_unknown_up);
+ } else {
+ m_wired = std::make_unique<net::wired_network>(m_interface);
+ m_wired->set_unknown_up(m_unknown_up);
+ };
+
+ // We only need to start the subthread if the packetloss animation is used
+ if (m_animation_packetloss) {
+ m_threads.emplace_back(thread(&network_module::subthread_routine, this));
+ }
+ }
+
+ void network_module::teardown() {
+ m_wireless.reset();
+ m_wired.reset();
+ }
+
+ bool network_module::update() {
+ net::network* network =
+ m_wireless ? static_cast<net::network*>(m_wireless.get()) : static_cast<net::network*>(m_wired.get());
+
+ if (!network->query(m_accumulate)) {
+ m_log.warn("%s: Failed to query interface '%s'", name(), m_interface);
+ m_connected = false;
+ return false;
+ }
+
+ try {
+ if (m_wireless) {
+ m_signal = m_wireless->signal();
+ m_quality = m_wireless->quality();
+ }
+ } catch (const net::network_error& err) {
+ m_log.warn("%s: Error getting interface data (%s)", name(), err.what());
+ }
+
+ m_connected = network->connected();
+
+ // Ignore the first run
+ if (m_counter == -1) {
+ m_counter = 0;
+ } else if (m_ping_nth_update > 0 && m_connected && (++m_counter % m_ping_nth_update) == 0) {
+ m_packetloss = !network->ping();
+ m_counter = 0;
+ }
+
+ auto upspeed = network->upspeed(m_udspeed_minwidth, m_udspeed_unit);
+ auto downspeed = network->downspeed(m_udspeed_minwidth, m_udspeed_unit);
+ auto netspeed = network->netspeed(m_udspeed_minwidth, m_udspeed_unit);
+
+ // Update label contents
+ const auto replace_tokens = [&](label_t& label) {
+ label->reset_tokens();
+ label->replace_token("%ifname%", m_interface);
+ label->replace_token("%local_ip%", network->ip());
+ label->replace_token("%mac%", network->mac());
+ label->replace_token("%local_ip6%", network->ip6());
+ label->replace_token("%upspeed%", upspeed);
+ label->replace_token("%downspeed%", downspeed);
+ label->replace_token("%netspeed%", netspeed);
+
+ if (m_wired) {
+ label->replace_token("%linkspeed%", m_wired->linkspeed());
+ } else if (m_wireless) {
+ label->replace_token("%essid%", m_wireless->essid());
+ label->replace_token("%signal%", to_string(m_signal));
+ label->replace_token("%quality%", to_string(m_quality));
+ }
+ };
+
+ if (m_label[connection_state::CONNECTED]) {
+ replace_tokens(m_label[connection_state::CONNECTED]);
+ }
+ if (m_label[connection_state::PACKETLOSS]) {
+ replace_tokens(m_label[connection_state::PACKETLOSS]);
+ }
+ if (m_label[connection_state::DISCONNECTED]) {
+ replace_tokens(m_label[connection_state::DISCONNECTED]);
+ }
+
+ return true;
+ }
+
+ string network_module::get_format() const {
+ if (!m_connected) {
+ return FORMAT_DISCONNECTED;
+ } else if (m_packetloss && m_ping_nth_update > 0) {
+ return FORMAT_PACKETLOSS;
+ } else {
+ return FORMAT_CONNECTED;
+ }
+ }
+
+ bool network_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_CONNECTED) {
+ builder->node(m_label.at(connection_state::CONNECTED));
+ } else if (tag == TAG_LABEL_DISCONNECTED) {
+ builder->node(m_label.at(connection_state::DISCONNECTED));
+ } else if (tag == TAG_LABEL_PACKETLOSS) {
+ builder->node(m_label.at(connection_state::PACKETLOSS));
+ } else if (tag == TAG_ANIMATION_PACKETLOSS) {
+ builder->node(m_animation_packetloss->get());
+ } else if (tag == TAG_RAMP_SIGNAL) {
+ builder->node(m_ramp_signal->get_by_percentage(m_signal));
+ } else if (tag == TAG_RAMP_QUALITY) {
+ builder->node(m_ramp_quality->get_by_percentage(m_quality));
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void network_module::subthread_routine() {
+ const chrono::milliseconds framerate{m_animation_packetloss->framerate()};
+
+ while (running()) {
+ auto now = chrono::steady_clock::now();
+ if (m_connected && m_packetloss) {
+ m_animation_packetloss->increment();
+ broadcast();
+ }
+
+ now += framerate;
+ this_thread::sleep_until(now);
+ }
+
+ m_log.trace("%s: Reached end of network subthread", name());
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/pulseaudio.hpp"
+
+#include "adapters/pulseaudio.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "settings.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<pulseaudio_module>;
+
+ pulseaudio_module::pulseaudio_module(const bar_settings& bar, string name_, const config& config)
+ : event_module<pulseaudio_module>(bar, move(name_), config) {
+ if (m_handle_events) {
+ m_router->register_action(EVENT_DEC, [this]() { action_dec(); });
+ m_router->register_action(EVENT_INC, [this]() { action_inc(); });
+ m_router->register_action(EVENT_TOGGLE, [this]() { action_toggle(); });
+ }
+
+ // Load configuration values
+ m_interval = m_conf.get(name(), "interval", m_interval);
+
+ auto sink_name = m_conf.get(name(), "sink", ""s);
+ bool m_max_volume = m_conf.get(name(), "use-ui-max", true);
+ m_reverse_scroll = m_conf.get(name(), "reverse-scroll", false);
+
+ try {
+ m_pulseaudio = std::make_unique<pulseaudio>(m_log, move(sink_name), m_max_volume);
+ } catch (const pulseaudio_error& err) {
+ throw module_error(err.what());
+ }
+
+ // Add formats and elements
+ m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
+ m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
+
+ if (m_formatter->has(TAG_BAR_VOLUME)) {
+ m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
+ }
+ if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
+ m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
+ m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_RAMP_VOLUME)) {
+ m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME);
+ }
+ }
+
+ void pulseaudio_module::teardown() {
+ m_pulseaudio.reset();
+ }
+
+ bool pulseaudio_module::has_event() {
+ // Poll for mixer and control events
+ try {
+ if (m_pulseaudio->wait()) {
+ return true;
+ }
+ } catch (const pulseaudio_error& e) {
+ m_log.err("%s: %s", name(), e.what());
+ }
+ return false;
+ }
+
+ bool pulseaudio_module::update() {
+ // Consume pending events
+ m_pulseaudio->process_events();
+
+ // Get volume and mute state
+ m_volume = 100;
+ m_decibels = PA_DECIBEL_MININFTY;
+ m_muted = false;
+
+ try {
+ if (m_pulseaudio) {
+ m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f;
+ m_decibels = m_pulseaudio->get_decibels();
+ m_muted = m_muted || m_pulseaudio->is_muted();
+ }
+ } catch (const pulseaudio_error& err) {
+ m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what());
+ }
+
+ // Replace label tokens
+ if (m_label_volume) {
+ m_label_volume->reset_tokens();
+ m_label_volume->replace_token("%percentage%", to_string(m_volume));
+ m_label_volume->replace_token("%decibels%", string_util::floating_point(m_decibels, 2, true));
+ }
+
+ if (m_label_muted) {
+ m_label_muted->reset_tokens();
+ m_label_muted->replace_token("%percentage%", to_string(m_volume));
+ m_label_muted->replace_token("%decibels%", string_util::floating_point(m_decibels, 2, true));
+ }
+
+ return true;
+ }
+
+ string pulseaudio_module::get_format() const {
+ return m_muted ? FORMAT_MUTED : FORMAT_VOLUME;
+ }
+
+ string pulseaudio_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapper
+ // with the cmd handlers
+ string output{module::get_output()};
+
+ if (m_handle_events) {
+ auto click_middle = m_conf.get(name(), "click-middle", ""s);
+ auto click_right = m_conf.get(name(), "click-right", ""s);
+
+ if (!click_middle.empty()) {
+ m_builder->action(mousebtn::MIDDLE, click_middle);
+ }
+
+ if (!click_right.empty()) {
+ m_builder->action(mousebtn::RIGHT, click_right);
+ }
+
+ m_builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "");
+ if (!m_reverse_scroll) {
+ m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+ } else {
+ m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_DEC, "");
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_INC, "");
+ }
+ }
+
+ m_builder->node(output);
+
+ return m_builder->flush();
+ }
+
+ bool pulseaudio_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_BAR_VOLUME) {
+ builder->node(m_bar_volume->output(m_volume));
+ } else if (tag == TAG_RAMP_VOLUME) {
+ builder->node(m_ramp_volume->get_by_percentage(m_volume));
+ } else if (tag == TAG_LABEL_VOLUME) {
+ builder->node(m_label_volume);
+ } else if (tag == TAG_LABEL_MUTED) {
+ builder->node(m_label_muted);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void pulseaudio_module::action_inc() {
+ m_pulseaudio->inc_volume(m_interval);
+ }
+
+ void pulseaudio_module::action_dec() {
+ m_pulseaudio->inc_volume(-m_interval);
+ }
+
+ void pulseaudio_module::action_toggle() {
+ m_pulseaudio->toggle_mute();
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/script.hpp"
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ script_module::script_module(const bar_settings& bar, string name_, const config& config)
+ : module<script_module>(bar, move(name_), config)
+ , m_tail(m_conf.get(name(), "tail", false))
+ , m_interval_success(m_conf.get<script_runner::interval>(name(), "interval", m_tail ? 0s : 5s))
+ , m_interval_fail(m_conf.get<script_runner::interval>(name(), "interval-fail", m_interval_success))
+ , m_interval_if(m_conf.get<script_runner::interval>(name(), "interval-if", m_interval_success))
+ , m_runner([this](const auto& data) { handle_runner_update(data); }, m_conf.get(name(), "exec", ""s),
+ m_conf.get(name(), "exec-if", ""s), m_tail, m_interval_success, m_interval_fail,
+ m_conf.get_with_prefix(name(), "env-")) {
+ // Load configured click handlers
+ m_actions[mousebtn::LEFT] = m_conf.get(name(), "click-left", ""s);
+ m_actions[mousebtn::MIDDLE] = m_conf.get(name(), "click-middle", ""s);
+ m_actions[mousebtn::RIGHT] = m_conf.get(name(), "click-right", ""s);
+ m_actions[mousebtn::DOUBLE_LEFT] = m_conf.get(name(), "double-click-left", ""s);
+ m_actions[mousebtn::DOUBLE_MIDDLE] = m_conf.get(name(), "double-click-middle", ""s);
+ m_actions[mousebtn::DOUBLE_RIGHT] = m_conf.get(name(), "double-click-right", ""s);
+ m_actions[mousebtn::SCROLL_UP] = m_conf.get(name(), "scroll-up", ""s);
+ m_actions[mousebtn::SCROLL_DOWN] = m_conf.get(name(), "scroll-down", ""s);
+
+ // Setup formatting
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%output%");
+ }
+
+ m_formatter->add_optional(FORMAT_FAIL, {TAG_LABEL_FAIL});
+ if (m_formatter->has(TAG_LABEL_FAIL)) {
+ m_label_fail = load_optional_label(m_conf, name(), TAG_LABEL_FAIL, "%output%");
+ }
+ }
+
+ /**
+ * Start the module worker
+ */
+ void script_module::start() {
+ this->module::start();
+ m_mainthread = thread([&] {
+ try {
+ while (running()) {
+ script_runner::interval sleep_time;
+ if (m_runner.check_condition()) {
+ sleep_time = m_runner.process();
+ } else {
+ m_runner.clear_output();
+ sleep_time = std::max(m_interval_if, script_runner::interval(1s));
+ }
+
+ if (m_runner.is_stopping()) {
+ break;
+ }
+
+ sleep(sleep_time);
+ }
+ } catch (const exception& err) {
+ halt(err.what());
+ }
+ });
+ }
+
+ /**
+ * Stop the module worker by terminating any running commands
+ */
+ void script_module::stop() {
+ m_runner.stop();
+ wakeup();
+
+ module::stop();
+ }
+
+ /**
+ * Generate module output
+ */
+ string script_module::get_format() const {
+ if (m_exit_status != 0 && m_conf.has(name(), FORMAT_FAIL)) {
+ return FORMAT_FAIL;
+ }
+ return DEFAULT_FORMAT;
+ }
+
+ string script_module::get_output() {
+ auto data = [this] {
+ std::lock_guard<std::mutex> lk(m_data_mutex);
+ return m_data;
+ }();
+
+ m_exit_status = data.exit_status;
+
+ if (data.output.empty() && m_exit_status == 0) {
+ return "";
+ }
+
+ if (m_label) {
+ m_label->reset_tokens();
+ m_label->replace_token("%output%", data.output);
+ }
+
+ if (m_label_fail) {
+ m_label_fail->reset_tokens();
+ m_label_fail->replace_token("%output%", data.output);
+ }
+
+ string cnt{to_string(data.counter)};
+ string output{module::get_output()};
+
+ for (const auto& a : m_actions) {
+ auto btn = a.first;
+ auto action = a.second;
+
+ if (!action.empty()) {
+ auto action_replaced = string_util::replace_all(action, "%counter%", cnt);
+
+ /*
+ * The pid token is only for tailed commands.
+ * If the command is not specified or running, replacement is unnecessary as well
+ */
+ if (data.pid != -1) {
+ action_replaced = string_util::replace_all(action_replaced, "%pid%", to_string(data.pid));
+ }
+ m_builder->action(btn, action_replaced);
+ }
+ }
+
+ m_builder->node(output);
+
+ return m_builder->flush();
+ }
+
+ /**
+ * Output format tags
+ */
+ bool script_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else if (tag == TAG_LABEL_FAIL) {
+ builder->node(m_label_fail);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ void script_module::handle_runner_update(const script_runner::data& data) {
+ {
+ std::lock_guard<std::mutex> lk(m_data_mutex);
+ m_data = data;
+ }
+
+ broadcast();
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/temperature.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+#include <cmath>
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<temperature_module>;
+
+ temperature_module::temperature_module(const bar_settings& bar, string name_, const config& config)
+ : timer_module<temperature_module>(bar, move(name_), config) {
+ m_zone = m_conf.get(name(), "thermal-zone", 0);
+ m_zone_type = m_conf.get(name(), "zone-type", ""s);
+ m_path = m_conf.get(name(), "hwmon-path", ""s);
+ m_tempbase = m_conf.get(name(), "base-temperature", 0);
+ m_tempwarn = m_conf.get(name(), "warn-temperature", 80);
+ set_interval(1s);
+ m_units = m_conf.get(name(), "units", m_units);
+
+ if (!m_zone_type.empty()) {
+ bool zone_found = false;
+ vector<string> zone_paths = file_util::glob(PATH_THERMAL_ZONE_WILDCARD);
+ vector<string> available_zones;
+ for (auto &z: zone_paths) {
+ string zone_file = z + "/type";
+ string z_zone_type = string_util::strip_trailing_newline(file_util::contents(zone_file));
+ available_zones.push_back(z_zone_type);
+ if (z_zone_type == m_zone_type) {
+ m_path = z + "/temp";
+ zone_found = true;
+ break;
+ }
+ }
+
+ if (!zone_found) {
+ throw module_error("zone-type '" + m_zone_type + "' was not found, available zone types: " + string_util::join(available_zones, ", "));
+ }
+ }
+
+ if (m_path.empty()) {
+ m_path = string_util::replace(PATH_TEMPERATURE_INFO, "%zone%", to_string(m_zone));
+ }
+
+ if (!file_util::exists(m_path)) {
+ throw module_error("The file '" + m_path + "' does not exist");
+ }
+
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_RAMP});
+ m_formatter->add(FORMAT_WARN, TAG_LABEL_WARN, {TAG_LABEL_WARN, TAG_RAMP});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label[temp_state::NORMAL] = load_optional_label(m_conf, name(), TAG_LABEL, "%temperature-c%");
+ }
+ if (m_formatter->has(TAG_LABEL_WARN)) {
+ m_label[temp_state::WARN] = load_optional_label(m_conf, name(), TAG_LABEL_WARN, "%temperature-c%");
+ }
+ if (m_formatter->has(TAG_RAMP)) {
+ m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+ }
+
+ // Deprecation warning for the %temperature% token
+ if((m_label[temp_state::NORMAL] && m_label[temp_state::NORMAL]->has_token("%temperature%")) ||
+ ((m_label[temp_state::WARN] && m_label[temp_state::WARN]->has_token("%temperature%")))) {
+ m_log.warn("%s: The token `%%temperature%%` is deprecated, use `%%temperature-c%%` instead.", name());
+ }
+ }
+
+ bool temperature_module::update() {
+ float temp = float(std::strtol(file_util::contents(m_path).c_str(), nullptr, 10)) / 1000.0;
+ m_temp = std::lround( temp );
+ int temp_f = std::lround( (temp * 1.8) + 32.0 );
+ int temp_k = std::lround( temp + 273.15 );
+
+ string temp_c_string = to_string(m_temp);
+ string temp_f_string = to_string(temp_f);
+ string temp_k_string = to_string(temp_k);
+
+ // Add units if `units = true` in config
+ if(m_units) {
+ temp_c_string += "°C";
+ temp_f_string += "°F";
+ temp_k_string += "K";
+ }
+
+ const auto replace_tokens = [&](label_t& label) {
+ label->reset_tokens();
+ label->replace_token("%temperature-f%", temp_f_string);
+ label->replace_token("%temperature-c%", temp_c_string);
+ label->replace_token("%temperature-k%", temp_k_string);
+
+ // DEPRECATED: Will be removed in later release
+ label->replace_token("%temperature%", temp_c_string);
+ };
+
+ if (m_label[temp_state::NORMAL]) {
+ replace_tokens(m_label[temp_state::NORMAL]);
+ }
+ if (m_label[temp_state::WARN]) {
+ replace_tokens(m_label[temp_state::WARN]);
+ }
+
+ return true;
+ }
+
+ string temperature_module::get_format() const {
+ if (m_temp >= m_tempwarn) {
+ return FORMAT_WARN;
+ } else {
+ return DEFAULT_FORMAT;
+ }
+ }
+
+ bool temperature_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label.at(temp_state::NORMAL));
+ } else if (tag == TAG_LABEL_WARN) {
+ builder->node(m_label.at(temp_state::WARN));
+ } else if (tag == TAG_RAMP) {
+ builder->node(m_ramp->get_by_percentage_with_borders(m_temp, m_tempbase, m_tempwarn));
+ } else {
+ return false;
+ }
+ return true;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/text.hpp"
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<text_module>;
+
+ text_module::text_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<text_module>(bar, move(name_), config) {
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+ m_formatter->add_optional("content", {});
+
+ if (m_formatter->has_format("content")) {
+ m_conf.warn_deprecated(name(), "content", "format");
+
+ if (m_formatter->get("content")->value.empty()) {
+ throw module_error(name() + ".content is empty or undefined");
+ }
+
+ m_format = "content";
+ } else {
+ m_format = DEFAULT_FORMAT;
+
+ if (m_formatter->has(TAG_LABEL, DEFAULT_FORMAT)) {
+ m_label = load_label(m_conf, name(), TAG_LABEL);
+ }
+ }
+ }
+
+ string text_module::get_format() const {
+ return m_format;
+ }
+
+ string text_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapper
+ // with the cmd handlers
+ string output{module::get_output()};
+
+ auto click_left = m_conf.get(name(), "click-left", ""s);
+ auto click_middle = m_conf.get(name(), "click-middle", ""s);
+ auto click_right = m_conf.get(name(), "click-right", ""s);
+ auto scroll_up = m_conf.get(name(), "scroll-up", ""s);
+ auto scroll_down = m_conf.get(name(), "scroll-down", ""s);
+
+ if (!click_left.empty()) {
+ m_builder->action(mousebtn::LEFT, click_left);
+ }
+ if (!click_middle.empty()) {
+ m_builder->action(mousebtn::MIDDLE, click_middle);
+ }
+ if (!click_right.empty()) {
+ m_builder->action(mousebtn::RIGHT, click_right);
+ }
+ if (!scroll_up.empty()) {
+ m_builder->action(mousebtn::SCROLL_UP, scroll_up);
+ }
+ if (!scroll_down.empty()) {
+ m_builder->action(mousebtn::SCROLL_DOWN, scroll_down);
+ }
+
+ m_builder->node(output);
+
+ return m_builder->flush();
+ }
+
+ bool text_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/tray.hpp"
+
+#include "modules/meta/base.inl"
+#include "x11/background_manager.hpp"
+
+POLYBAR_NS
+namespace modules {
+ template class module<tray_module>;
+
+ tray_module::tray_module(const bar_settings& bar_settings, string name_, const config& config)
+ : static_module<tray_module>(bar_settings, move(name_), config)
+ , m_tray(connection::make(), signal_emitter::make(), m_log, bar_settings, [&] { this->broadcast(); }) {
+ m_formatter->add(DEFAULT_FORMAT, TAG_TRAY, {TAG_TRAY});
+ }
+
+ string tray_module::get_format() const {
+ return DEFAULT_FORMAT;
+ }
+
+ void tray_module::start() {
+ m_tray.setup(m_conf, name());
+ this->static_module<tray_module>::start();
+ }
+
+ bool tray_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_TRAY) {
+ builder->control(tags::controltag::t);
+ extent_val offset_extent = {extent_type::PIXEL, static_cast<float>(m_tray.get_width())};
+ builder->offset(offset_extent);
+ return true;
+ }
+ return false;
+ }
+
+} // namespace modules
+POLYBAR_NS_END
--- /dev/null
+#include "modules/xbacklight.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/math.hpp"
+#include "x11/connection.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<xbacklight_module>;
+
+ /**
+ * Construct module
+ */
+ xbacklight_module::xbacklight_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<xbacklight_module>(bar, move(name_), config), m_connection(connection::make()) {
+ m_router->register_action(EVENT_INC, [this]() { action_inc(); });
+ m_router->register_action(EVENT_DEC, [this]() { action_dec(); });
+
+ auto output = m_conf.get(name(), "output", m_bar.monitor->name);
+
+ auto monitors = randr_util::get_monitors(m_connection, bar.monitor_strict, false);
+
+ m_output = randr_util::match_monitor(monitors, output, bar.monitor_exact);
+
+ // If we didn't get a match we stop the module
+ if (!m_output) {
+ throw module_error("No matching output found for \"" + output + "\", stopping module...");
+ }
+
+ // Get flag to check if we should add scroll handlers for changing value
+ m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+
+ // Query randr for the backlight max and min value
+ try {
+ auto& backlight = m_output->backlight;
+ randr_util::get_backlight_range(m_connection, m_output, backlight);
+ randr_util::get_backlight_value(m_connection, m_output, backlight);
+ } catch (const exception& err) {
+ m_log.err("%s: Could not get data (err: %s)", name(), err.what());
+ throw module_error("Not supported for \"" + m_output->name + "\"");
+ }
+
+ // Create window that will proxy all RandR notify events
+ // clang-format off
+ m_proxy = winspec(m_connection)
+ << cw_size(1, 1)
+ << cw_pos(-1, -1)
+ << cw_flush(true);
+ // clang-format on
+
+ // Connect with the event registry and make sure we get
+ // notified when a RandR output property gets modified
+ m_connection.select_input_checked(m_proxy, XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+
+ // Add formats and elements
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+ }
+ if (m_formatter->has(TAG_BAR)) {
+ m_progressbar = load_progressbar(m_bar, m_conf, name(), TAG_BAR);
+ }
+ if (m_formatter->has(TAG_RAMP)) {
+ m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+ }
+ }
+
+ /**
+ * Handler for XCB_RANDR_NOTIFY events
+ */
+ void xbacklight_module::handle(const evt::randr_notify& evt) {
+ if (evt->subCode != XCB_RANDR_NOTIFY_OUTPUT_PROPERTY) {
+ return;
+ } else if (evt->u.op.status != XCB_PROPERTY_NEW_VALUE) {
+ return;
+ } else if (evt->u.op.window != m_proxy) {
+ return;
+ } else if (evt->u.op.output != m_output->output) {
+ return;
+ } else if (evt->u.op.atom != m_output->backlight.atom) {
+ return;
+ } else {
+ update();
+ }
+ }
+
+ /**
+ * Query the RandR extension for the new values
+ */
+ void xbacklight_module::update() {
+ auto& bl = m_output->backlight;
+ randr_util::get_backlight_value(m_connection, m_output, bl);
+ m_percentage = math_util::nearest_5(math_util::percentage<double>(bl.val, bl.min, bl.max));
+
+ // Update label tokens
+ if (m_label) {
+ m_label->reset_tokens();
+ m_label->replace_token("%percentage%", to_string(m_percentage));
+ }
+
+ // Emit a broadcast notification so that
+ // the new data will be drawn to the bar
+ broadcast();
+ }
+
+ /**
+ * Generate the module output
+ */
+ string xbacklight_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapped
+ // with the cmd handlers
+ string output{module::get_output()};
+
+ if (m_scroll) {
+ m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+ }
+
+ m_builder->node(output);
+
+ m_builder->action_close();
+ m_builder->action_close();
+
+ return m_builder->flush();
+ }
+
+ /**
+ * Output content as defined in the config
+ */
+ bool xbacklight_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_BAR) {
+ builder->node(m_progressbar->output(m_percentage));
+ } else if (tag == TAG_RAMP) {
+ builder->node(m_ramp->get_by_percentage(m_percentage));
+ } else if (tag == TAG_LABEL) {
+ builder->node(m_label);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ void xbacklight_module::action_inc() {
+ change_value(5);
+ }
+
+ void xbacklight_module::action_dec() {
+ change_value(-5);
+ }
+
+ void xbacklight_module::change_value(int value_mod) {
+ m_log.info("%s: Changing value by %i%", name(), value_mod);
+ int rounded = math_util::cap<double>(m_percentage + value_mod, 0.0, 100.0) + 0.5;
+
+ const int values[1]{math_util::percentage_to_value<int>(rounded, m_output->backlight.max)};
+
+ m_connection.change_output_property_checked(
+ m_output->output, m_output->backlight.atom, XCB_ATOM_INTEGER, 32, XCB_PROP_MODE_REPLACE, 1, values);
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/xkeyboard.hpp"
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<xkeyboard_module>;
+
+ // clang-format off
+ static const keyboard::indicator::type INDICATOR_TYPES[] {
+ keyboard::indicator::type::CAPS_LOCK,
+ keyboard::indicator::type::NUM_LOCK,
+ keyboard::indicator::type::SCROLL_LOCK
+ };
+ // clang-format on
+
+ /**
+ * Construct module
+ */
+ xkeyboard_module::xkeyboard_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<xkeyboard_module>(bar, move(name_), config), m_connection(connection::make()) {
+ m_router->register_action(EVENT_SWITCH, [this]() { action_switch(); });
+
+ // Setup extension
+ // clang-format off
+ m_connection.xkb().select_events_checked(XCB_XKB_ID_USE_CORE_KBD,
+ XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+ XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
+ XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0,
+ XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+ XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
+ XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0, 0, nullptr);
+ // clang-format on
+
+ // Create keyboard object
+ query_keyboard();
+
+ // Load config values
+ m_blacklist = m_conf.get_list(name(), "blacklist", {});
+
+ // load layout icons
+ parse_icons();
+
+ // Add formats and elements
+ m_formatter->add(DEFAULT_FORMAT, FORMAT_DEFAULT, {TAG_LABEL_LAYOUT, TAG_LABEL_INDICATOR});
+
+ if (m_formatter->has(TAG_LABEL_LAYOUT)) {
+ m_layout = load_optional_label(m_conf, name(), TAG_LABEL_LAYOUT, "%layout%");
+ }
+
+ if (m_formatter->has(TAG_LABEL_INDICATOR)) {
+ m_conf.warn_deprecated(name(), "label-indicator", "label-indicator-on");
+ // load an empty label if 'label-indicator-off' is not explicitly specified so
+ // no existing user configs are broken (who expect nothing to be shown when indicator is off)
+ m_indicator_state_off = load_optional_label(m_conf, name(), "label-indicator-off"s, ""s);
+
+ if (m_conf.has(name(), "label-indicator-on"s)) {
+ m_indicator_state_on = load_optional_label(m_conf, name(), "label-indicator-on"s, "%name%"s);
+ } else {
+ // if 'label-indicator-on' is not explicitly specified, use 'label-indicator'
+ // as to not break existing user configs
+ m_indicator_state_on = load_optional_label(m_conf, name(), TAG_LABEL_INDICATOR, "%name%"s);
+ }
+
+ // load indicator icons
+ m_indicator_icons_off = std::make_shared<iconset>();
+ m_indicator_icons_on = std::make_shared<iconset>();
+
+ auto icon_pair = string_util::tokenize(m_conf.get(name(), DEFAULT_INDICATOR_ICON, ""s), ';');
+ if (icon_pair.size() == 2) {
+ m_indicator_icons_off->add(DEFAULT_INDICATOR_ICON, std::make_shared<label>(icon_pair[0]));
+ m_indicator_icons_on->add(DEFAULT_INDICATOR_ICON, std::make_shared<label>(icon_pair[1]));
+ } else {
+ m_indicator_icons_off->add(DEFAULT_INDICATOR_ICON, std::make_shared<label>(""s));
+ m_indicator_icons_on->add(DEFAULT_INDICATOR_ICON, std::make_shared<label>(""s));
+ }
+
+ for (const auto& it : m_conf.get_list<string>(name(), "indicator-icon", {})) {
+ auto icon_triple = string_util::tokenize(it, ';');
+ if (icon_triple.size() == 3) {
+ auto const indicator_str = string_util::lower(icon_triple[0]);
+ m_indicator_icons_off->add(indicator_str, std::make_shared<label>(icon_triple[1]));
+ m_indicator_icons_on->add(indicator_str, std::make_shared<label>(icon_triple[2]));
+ }
+ }
+
+ for (auto it : INDICATOR_TYPES) {
+ const auto& indicator_str = m_keyboard->indicator_name(it);
+ auto key_name = string_util::replace(string_util::lower(indicator_str), " "s, ""s);
+ const auto indicator_key_on = "label-indicator-on-"s + key_name;
+ const auto indicator_key_off = "label-indicator-off-"s + key_name;
+
+ if (m_conf.has(name(), indicator_key_on)) {
+ m_indicator_on_labels.emplace(it, load_label(m_conf, name(), indicator_key_on));
+ }
+ if (m_conf.has(name(), indicator_key_off)) {
+ m_indicator_off_labels.emplace(it, load_label(m_conf, name(), indicator_key_off));
+ }
+ }
+ }
+ }
+
+ /**
+ * Update labels with extension data
+ */
+ void xkeyboard_module::update() {
+ if (m_layout) {
+ m_layout->reset_tokens();
+ m_layout->replace_token("%name%", m_keyboard->group_name(m_keyboard->current()));
+ m_layout->replace_token("%variant%", m_keyboard->variant_name(m_keyboard->current()));
+
+ auto const current_layout = m_keyboard->layout_name(m_keyboard->current());
+ auto const current_variant = m_keyboard->variant_name(m_keyboard->current());
+
+ auto icon = m_layout_icons->get(current_layout, current_variant);
+
+ m_layout->replace_token("%icon%", icon->get());
+ m_layout->replace_token("%layout%", current_layout);
+ m_layout->replace_token("%number%", to_string(m_keyboard->current()));
+ }
+
+ if (m_formatter->has(TAG_LABEL_INDICATOR)) {
+ m_indicators.clear();
+
+ for (auto it : INDICATOR_TYPES) {
+ const auto& indicator_str = m_keyboard->indicator_name(it);
+
+ if (blacklisted(indicator_str)) {
+ continue;
+ }
+
+ auto indicator_on = m_keyboard->on(it);
+ auto& indicator_labels = indicator_on ? m_indicator_on_labels : m_indicator_off_labels;
+ auto& indicator_icons = indicator_on ? m_indicator_icons_on : m_indicator_icons_off;
+ auto& indicator_state = indicator_on ? m_indicator_state_on : m_indicator_state_off;
+
+ label_t indicator;
+ if (indicator_labels.find(it) != indicator_labels.end()) {
+ indicator = indicator_labels[it]->clone();
+ } else {
+ indicator = indicator_state->clone();
+ }
+
+ auto icon = indicator_icons->get(string_util::lower(indicator_str), DEFAULT_INDICATOR_ICON);
+
+ indicator->replace_token("%name%", indicator_str);
+ indicator->replace_token("%icon%", icon->get());
+ m_indicators.emplace(it, move(indicator));
+ }
+ }
+
+ // Trigger redraw
+ broadcast();
+ }
+
+ /**
+ * Build module output and wrap it in a click handler use
+ * to cycle between configured layout groups
+ */
+ string xkeyboard_module::get_output() {
+ string output{module::get_output()};
+
+ if (m_keyboard && m_keyboard->size() > 1) {
+ m_builder->action(mousebtn::LEFT, *this, EVENT_SWITCH, "");
+ m_builder->node(output);
+ m_builder->action_close();
+ } else {
+ m_builder->node(output);
+ }
+
+ return m_builder->flush();
+ }
+
+ /**
+ * Map format tags to content
+ */
+ bool xkeyboard_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_LAYOUT) {
+ builder->node(m_layout);
+ } else if (tag == TAG_LABEL_INDICATOR && !m_indicators.empty()) {
+ size_t n{0};
+ for (auto&& indicator : m_indicators) {
+ if (*indicator.second) {
+ if (n++) {
+ builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
+ }
+ builder->node(indicator.second);
+ }
+ }
+ return n > 0;
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle input command
+ */
+ void xkeyboard_module::action_switch() {
+ size_t current_group = m_keyboard->current() + 1;
+
+ if (current_group >= m_keyboard->size()) {
+ current_group = 0;
+ }
+
+ xkb_util::switch_layout(m_connection, XCB_XKB_ID_USE_CORE_KBD, current_group);
+ m_keyboard->current(current_group);
+ m_connection.flush();
+
+ update();
+ }
+
+ /**
+ * Create keyboard object by querying current extension data
+ */
+ bool xkeyboard_module::query_keyboard() {
+ try {
+ auto layouts = xkb_util::get_layouts(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+ auto indicators = xkb_util::get_indicators(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+ auto current_group = xkb_util::get_current_group(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+ m_keyboard = std::make_unique<keyboard>(move(layouts), move(indicators), current_group);
+ return true;
+ } catch (const exception& err) {
+ throw module_error("Failed to query keyboard, err: " + string{err.what()});
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if the indicator has been blacklisted by the user
+ */
+ bool xkeyboard_module::blacklisted(const string& indicator_name) {
+ for (auto&& i : m_blacklist) {
+ if (string_util::compare(i, indicator_name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Handler for XCB_XKB_NEW_KEYBOARD_NOTIFY events
+ */
+ void xkeyboard_module::handle(const evt::xkb_new_keyboard_notify& evt) {
+ if (evt->changed & XCB_XKB_NKN_DETAIL_KEYCODES && m_xkb_newkb_notify.allow(evt->time)) {
+ query_keyboard();
+ update();
+ }
+ }
+
+ /**
+ * Handler for XCB_XKB_STATE_NOTIFY events
+ */
+ void xkeyboard_module::handle(const evt::xkb_state_notify& evt) {
+ if (m_keyboard && evt->changed & XCB_XKB_STATE_PART_GROUP_STATE && m_xkb_state_notify.allow(evt->time)) {
+ m_keyboard->current(evt->group);
+ update();
+ }
+ }
+
+ /**
+ * Handler for XCB_XKB_INDICATOR_STATE_NOTIFY events
+ */
+ void xkeyboard_module::handle(const evt::xkb_indicator_state_notify& evt) {
+ if (m_keyboard && m_xkb_indicator_notify.allow(evt->time)) {
+ m_keyboard->set(m_connection.xkb().get_state(XCB_XKB_ID_USE_CORE_KBD)->lockedMods);
+ update();
+ }
+ }
+
+ void xkeyboard_module::parse_icons() {
+ m_layout_icons = make_shared<layouticonset>(load_optional_label(m_conf, name(), DEFAULT_LAYOUT_ICON, ""s));
+
+ for (const auto& it : m_conf.get_list<string>(name(), "layout-icon", {})) {
+ auto vec = string_util::tokenize(it, ';');
+
+ size_t size = vec.size();
+ if (size != 2 && size != 3) {
+ m_log.warn("%s: Malformed layout-icon '%s'", name(), it);
+ continue;
+ }
+
+ const string& layout = vec[0];
+
+ if (layout.empty()) {
+ m_log.warn("%s: layout-icon '%s' is invalid: there must always be a layout defined", name(), it);
+ continue;
+ }
+
+ const string& variant = size == 2 ? layouticonset::VARIANT_ANY : vec[1];
+ const string& icon = vec.back();
+
+ if (layout == layouticonset::VARIANT_ANY && variant == layouticonset::VARIANT_ANY) {
+ m_log.warn("%s: Using '%s' for layout-icon means declaring a default icon, use 'layout-icon-default' instead",
+ name(), it);
+ continue;
+ }
+
+ define_layout_icon(it, layout, variant, std::make_shared<label>(icon));
+ }
+ }
+
+ void xkeyboard_module::define_layout_icon(
+ const string& entry, const string& layout, const string& variant, label_t&& icon) {
+ if (m_layout_icons->contains(layout, variant)) {
+ m_log.warn(
+ "%s: An equivalent matching is already defined for '%s;%s' => ignoring '%s'", name(), layout, variant, entry);
+ } else if (!m_layout_icons->add(layout, variant, std::forward<label_t>(icon))) {
+ m_log.err(
+ "%s: '%s' cannot be added to internal structure. This case should never happen and must be reported as a bug",
+ name(), entry);
+ }
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/xwindow.hpp"
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+ template class module<xwindow_module>;
+
+ /**
+ * Wrapper used to update the event mask of the
+ * currently active to enable title tracking
+ */
+ active_window::active_window(xcb_connection_t* conn, xcb_window_t win) : m_connection(conn), m_window(win) {
+ if (m_window != XCB_NONE) {
+ const unsigned int mask{XCB_EVENT_MASK_PROPERTY_CHANGE};
+ xcb_change_window_attributes(m_connection, m_window, XCB_CW_EVENT_MASK, &mask);
+ }
+ }
+
+ /**
+ * Deconstruct window object
+ */
+ active_window::~active_window() {
+ if (m_window != XCB_NONE) {
+ const unsigned int mask{XCB_EVENT_MASK_NO_EVENT};
+ xcb_change_window_attributes(m_connection, m_window, XCB_CW_EVENT_MASK, &mask);
+ }
+ }
+
+ /**
+ * Check if current window matches passed value
+ */
+ bool active_window::match(const xcb_window_t win) const {
+ return m_window == win;
+ }
+
+ /**
+ * Get the title by returning the first non-empty value of:
+ * _NET_WM_NAME
+ * _NET_WM_VISIBLE_NAME
+ * WM_NAME
+ */
+ string active_window::title() const {
+ string title;
+
+ if (!(title = ewmh_util::get_wm_name(m_window)).empty()) {
+ return title;
+ } else if (!(title = ewmh_util::get_visible_name(m_window)).empty()) {
+ return title;
+ } else if (!(title = icccm_util::get_wm_name(m_connection, m_window)).empty()) {
+ return title;
+ } else {
+ return "";
+ }
+ }
+
+ string active_window::instance_name() const {
+ return icccm_util::get_wm_class(m_connection, m_window).first;
+ }
+
+ string active_window::class_name() const {
+ return icccm_util::get_wm_class(m_connection, m_window).second;
+ }
+
+ /**
+ * Construct module
+ */
+ xwindow_module::xwindow_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<xwindow_module>(bar, move(name_), config), m_connection(connection::make()) {
+ // Initialize ewmh atoms
+ ewmh_util::initialize();
+
+ // Check if the WM supports _NET_ACTIVE_WINDOW
+ if (!ewmh_util::supports(_NET_ACTIVE_WINDOW)) {
+ throw module_error("The WM does not list _NET_ACTIVE_WINDOW as a supported hint");
+ }
+
+ // Add formats and elements
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+
+ if (m_formatter->has(TAG_LABEL)) {
+ m_statelabels.emplace(state::ACTIVE, load_optional_label(m_conf, name(), "label", "%title%"));
+ m_statelabels.emplace(state::EMPTY, load_optional_label(m_conf, name(), "label-empty", ""));
+ }
+ }
+
+ /**
+ * Handler for XCB_PROPERTY_NOTIFY events
+ */
+ void xwindow_module::handle(const evt::property_notify& evt) {
+ if (evt->atom == _NET_ACTIVE_WINDOW) {
+ reset_active_window();
+ update();
+ } else if (evt->atom == _NET_CURRENT_DESKTOP) {
+ reset_active_window();
+ update();
+ } else if (evt->atom == _NET_WM_NAME || evt->atom == _NET_WM_VISIBLE_NAME || evt->atom == WM_NAME ||
+ evt->atom == WM_CLASS) {
+ update();
+ } else {
+ return;
+ }
+
+ broadcast();
+ }
+
+ void xwindow_module::reset_active_window() {
+ m_active.reset();
+ }
+
+ /**
+ * Update the currently active window and query its title
+ */
+ void xwindow_module::update() {
+ if (!m_active) {
+ xcb_window_t win = ewmh_util::get_active_window();
+ if (win != XCB_NONE) {
+ m_active = make_unique<active_window>(m_connection, win);
+ }
+ }
+
+ if (!m_statelabels.empty()) {
+ if (m_active) {
+ m_label = m_statelabels.at(state::ACTIVE)->clone();
+ m_label->reset_tokens();
+ m_label->replace_token("%title%", m_active->title());
+ m_label->replace_token("%instance%", m_active->instance_name());
+ m_label->replace_token("%class%", m_active->class_name());
+ } else {
+ m_label = m_statelabels.at(state::EMPTY)->clone();
+ }
+ }
+ }
+
+ /**
+ * Output content as defined in the config
+ */
+ bool xwindow_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL && m_label) {
+ builder->node(m_label);
+ return true;
+ }
+ return false;
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include "modules/xworkspaces.hpp"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/math.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/icccm.hpp"
+
+POLYBAR_NS
+
+/**
+ * Defines a lexicographical order on position
+ */
+bool operator<(const position& a, const position& b) {
+ return std::make_tuple(a.x, a.y) < std::make_tuple(b.x, b.y);
+}
+
+namespace modules {
+ template class module<xworkspaces_module>;
+
+ /**
+ * Construct module
+ */
+ xworkspaces_module::xworkspaces_module(const bar_settings& bar, string name_, const config& config)
+ : static_module<xworkspaces_module>(bar, move(name_), config)
+ , m_connection(connection::make())
+ , m_ewmh(ewmh_util::initialize()) {
+ m_router->register_action_with_data(EVENT_FOCUS, [this](const std::string& data) { action_focus(data); });
+ m_router->register_action(EVENT_NEXT, [this]() { action_next(); });
+ m_router->register_action(EVENT_PREV, [this]() { action_prev(); });
+
+ // Load config values
+ m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+ m_click = m_conf.get(name(), "enable-click", m_click);
+ m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+ m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
+ m_group_by_monitor = m_conf.get(name(), "group-by-monitor", m_group_by_monitor);
+
+ // Add formats and elements
+ m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE, TAG_LABEL_MONITOR});
+
+ if (m_formatter->has(TAG_LABEL_MONITOR)) {
+ m_monitorlabel = load_optional_label(m_conf, name(), "label-monitor", DEFAULT_LABEL_MONITOR);
+ if (m_monitorlabel && !m_group_by_monitor) {
+ throw module_error("Cannot use label-monitor when not grouping by monitor");
+ }
+ }
+
+ if (m_formatter->has(TAG_LABEL_STATE)) {
+ // clang-format off
+ m_labels.insert(make_pair(
+ desktop_state::ACTIVE, load_optional_label(m_conf, name(), "label-active", DEFAULT_LABEL_STATE)));
+ m_labels.insert(make_pair(
+ desktop_state::OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL_STATE)));
+ m_labels.insert(make_pair(
+ desktop_state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL_STATE)));
+ m_labels.insert(make_pair(
+ desktop_state::EMPTY, load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL_STATE)));
+ // clang-format on
+ }
+
+ m_icons = std::make_shared<iconset>();
+ m_icons->add(DEFAULT_ICON, std::make_shared<label>(m_conf.get(name(), DEFAULT_ICON, ""s)));
+
+ int i = 0;
+ for (const auto& workspace : m_conf.get_list<string>(name(), "icon", {})) {
+ auto vec = string_util::tokenize(workspace, ';');
+ if (vec.size() == 2) {
+ m_icons->add(vec[0], std::make_shared<label>(vec[1]));
+ } else {
+ m_log.err(
+ "%s: Ignoring icon-%d because it has %s semicolons", name(), i, vec.size() > 2 ? "too many" : "too few");
+ }
+
+ i++;
+ }
+
+ // Get list of monitors
+ m_monitors = randr_util::get_monitors(m_connection, false);
+
+ // Get desktop details
+ m_desktop_names = get_desktop_names();
+ update_current_desktop();
+
+ rebuild_desktops();
+
+ // Get _NET_CLIENT_LIST
+ rebuild_clientlist();
+ rebuild_desktop_states();
+ }
+
+ void xworkspaces_module::update_current_desktop() {
+ m_current_desktop = ewmh_util::get_current_desktop();
+ }
+
+ /**
+ * Handler for XCB_PROPERTY_NOTIFY events
+ */
+ void xworkspaces_module::handle(const evt::property_notify& evt) {
+ if (evt->atom == m_ewmh->_NET_CLIENT_LIST || evt->atom == m_ewmh->_NET_WM_DESKTOP) {
+ rebuild_clientlist();
+ rebuild_desktop_states();
+ } else if (evt->atom == m_ewmh->_NET_DESKTOP_NAMES || evt->atom == m_ewmh->_NET_NUMBER_OF_DESKTOPS ||
+ evt->atom == m_ewmh->_NET_DESKTOP_VIEWPORT) {
+ m_desktop_names = get_desktop_names();
+ rebuild_desktops();
+ rebuild_clientlist();
+ rebuild_desktop_states();
+ } else if (evt->atom == m_ewmh->_NET_CURRENT_DESKTOP) {
+ update_current_desktop();
+ rebuild_desktop_states();
+ } else if (evt->atom == WM_HINTS) {
+ rebuild_urgent_hints();
+ rebuild_desktop_states();
+ } else {
+ return;
+ }
+
+ broadcast();
+ }
+
+ /**
+ * Rebuild the list of managed clients
+ */
+ void xworkspaces_module::rebuild_clientlist() {
+ vector<xcb_window_t> newclients = ewmh_util::get_client_list();
+ std::sort(newclients.begin(), newclients.end());
+
+ for (auto&& client : newclients) {
+ if (m_clients.count(client) == 0) {
+ try {
+ // new client: listen for changes (wm_hint or desktop)
+ m_connection.ensure_event_mask(client, XCB_EVENT_MASK_PROPERTY_CHANGE);
+ } catch (const xpp::x::error::window& e) {
+ /*
+ * The "new client" may have already disappeared between reading the
+ * client list and setting the event mask.
+ * This is not a severe issue and it will eventually correct itself
+ * when a new _NET_CLIENT_LIST value is set.
+ */
+ m_log.info("%s: New client window no longer exists, ignoring...");
+ }
+ }
+ }
+
+ // rebuild entire mapping of clients to desktops
+ m_clients.clear();
+ m_windows.clear();
+ for (auto&& client : newclients) {
+ auto desk = ewmh_util::get_desktop_from_window(client);
+ m_clients[client] = desk;
+ m_windows[desk]++;
+ }
+
+ rebuild_urgent_hints();
+ }
+
+ /**
+ * Goes through all clients and updates the urgent hints on the desktop they are on.
+ */
+ void xworkspaces_module::rebuild_urgent_hints() {
+ m_urgent_desktops.assign(m_desktop_names.size(), false);
+ for (auto&& client : ewmh_util::get_client_list()) {
+ uint32_t desk = ewmh_util::get_desktop_from_window(client);
+ /*
+ * EWMH allows for 0xFFFFFFFF to be returned here, which means the window
+ * should appear on all desktops.
+ *
+ * We don't take those windows into account for the urgency hint because
+ * it would mark all workspaces as urgent.
+ */
+ if (desk < m_urgent_desktops.size()) {
+ m_urgent_desktops[desk] = m_urgent_desktops[desk] || icccm_util::get_wm_urgency(m_connection, client);
+ }
+ }
+ }
+
+ /**
+ * Rebuild the desktop tree
+ *
+ * This requires m_desktop_names to have an up-to-date value
+ */
+ void xworkspaces_module::rebuild_desktops() {
+ m_viewports.clear();
+
+ /*
+ * Stores the _NET_DESKTOP_VIEWPORT hint
+ *
+ * For WMs that don't support that hint, or if the user explicitly
+ * disables support via the configuration file, we store an empty
+ * vector.
+ *
+ * The vector will be padded/reduced to _NET_NUMBER_OF_DESKTOPS.
+ * All desktops which aren't explicitly assigned a postion will be
+ * assigned (0, 0)
+ *
+ * We use this to map workspaces to viewports, desktop i is at position
+ * ws_positions[i].
+ */
+ vector<position> ws_positions = m_group_by_monitor ? ewmh_util::get_desktop_viewports() : vector<position>();
+
+ auto num_desktops = m_desktop_names.size();
+
+ /*
+ * Not all desktops were assigned a viewport, add (0, 0) for all missing
+ * desktops.
+ */
+ if (ws_positions.size() < num_desktops) {
+ auto num_insert = num_desktops - ws_positions.size();
+ ws_positions.reserve(num_desktops);
+ std::fill_n(std::back_inserter(ws_positions), num_insert, position{0, 0});
+ }
+
+ /*
+ * If there are too many workspace positions, trim to fit the number of desktops.
+ */
+ if (ws_positions.size() > num_desktops) {
+ ws_positions.erase(ws_positions.begin() + num_desktops, ws_positions.end());
+ }
+
+ /*
+ * There must be as many workspace positions as desktops because the indices
+ * into ws_positions are also used to index into m_desktop_names.
+ */
+ assert(ws_positions.size() == num_desktops);
+
+ /*
+ * The list of viewports is the set of unique positions in ws_positions
+ * Using a set automatically removes duplicates.
+ */
+ std::set<position> viewports(ws_positions.begin(), ws_positions.end());
+
+ for (auto&& viewport_pos : viewports) {
+ /*
+ * If pin-workspaces is set, we only add the viewport if it's in the
+ * monitor the bar is on.
+ * Generally viewport_pos is the same as the top-left coordinate of the
+ * monitor but we use `contains` here as a safety in case it isn't exactly.
+ */
+ if (!m_pinworkspaces || m_bar.monitor->contains(viewport_pos)) {
+ auto viewport = make_unique<struct viewport>();
+ viewport->state = viewport_state::UNFOCUSED;
+ viewport->pos = viewport_pos;
+
+ for (auto&& m : m_monitors) {
+ if (m->contains(viewport->pos)) {
+ viewport->name = m->name;
+ viewport->state = viewport_state::FOCUSED;
+ }
+ }
+
+ viewport->label = [&] {
+ label_t label;
+ if (m_monitorlabel) {
+ label = m_monitorlabel->clone();
+ label->reset_tokens();
+ label->replace_token("%name%", viewport->name);
+ }
+ return label;
+ }();
+
+ /*
+ * Search for all desktops on this viewport and store them in the
+ * desktop list of the viewport.
+ */
+ for (size_t i = 0; i < ws_positions.size(); i++) {
+ auto&& ws_pos = ws_positions[i];
+ if (ws_pos == viewport_pos) {
+ viewport->desktops.emplace_back(make_unique<struct desktop>(i, desktop_state::EMPTY, label_t{}));
+ }
+ }
+
+ m_viewports.emplace_back(move(viewport));
+ }
+ }
+ }
+
+ /**
+ * Update active state of current desktops
+ */
+ void xworkspaces_module::rebuild_desktop_states() {
+ std::set<unsigned int> occupied_desks;
+ for (auto&& c : m_clients) {
+ occupied_desks.insert(c.second);
+ }
+
+ for (auto&& v : m_viewports) {
+ for (auto&& d : v->desktops) {
+ if (m_urgent_desktops[d->index]) {
+ d->state = desktop_state::URGENT;
+ } else if (d->index == m_current_desktop) {
+ d->state = desktop_state::ACTIVE;
+ } else if (occupied_desks.count(d->index) > 0) {
+ d->state = desktop_state::OCCUPIED;
+ } else {
+ d->state = desktop_state::EMPTY;
+ }
+
+ d->label = m_labels.at(d->state)->clone();
+ d->label->reset_tokens();
+ d->label->replace_token("%index%", to_string(d->index + 1));
+ d->label->replace_token("%name%", m_desktop_names[d->index]);
+ d->label->replace_token("%nwin%", to_string(m_windows[d->index]));
+ d->label->replace_token("%icon%", m_icons->get(m_desktop_names[d->index], DEFAULT_ICON)->get());
+ }
+ }
+ }
+
+ vector<string> xworkspaces_module::get_desktop_names() {
+ vector<string> names = ewmh_util::get_desktop_names();
+ unsigned int desktops_number = ewmh_util::get_number_of_desktops();
+ if (desktops_number == names.size()) {
+ return names;
+ } else if (desktops_number < names.size()) {
+ names.erase(names.begin() + desktops_number, names.end());
+ return names;
+ }
+ for (unsigned int i = names.size(); i < desktops_number; i++) {
+ names.insert(names.end(), to_string(i + 1));
+ }
+ return names;
+ }
+
+ /**
+ * Fetch and parse data
+ */
+ void xworkspaces_module::update() {}
+
+ /**
+ * Generate module output
+ */
+ string xworkspaces_module::get_output() {
+ // Get the module output early so that
+ // the format prefix/suffix also gets wrapped
+ // with the cmd handlers
+ string output;
+ for (m_index = 0; m_index < m_viewports.size(); m_index++) {
+ if (m_index > 0) {
+ m_builder->spacing(m_formatter->get(DEFAULT_FORMAT)->spacing);
+ }
+ output += module::get_output();
+ }
+
+ if (m_scroll) {
+ m_builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
+ m_builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
+ }
+
+ m_builder->node(output);
+
+ m_builder->action_close();
+ m_builder->action_close();
+
+ return m_builder->flush();
+ }
+
+ /**
+ * Output content as defined in the config
+ */
+ bool xworkspaces_module::build(builder* builder, const string& tag) const {
+ if (tag == TAG_LABEL_MONITOR) {
+ if (m_viewports[m_index]->state != viewport_state::NONE) {
+ builder->node(m_viewports[m_index]->label);
+ return true;
+ } else {
+ return false;
+ }
+ } else if (tag == TAG_LABEL_STATE) {
+ unsigned int added_states = 0;
+ for (auto&& desktop : m_viewports[m_index]->desktops) {
+ if (desktop->label.get()) {
+ if (m_click && desktop->state != desktop_state::ACTIVE) {
+ builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, to_string(desktop->index), desktop->label);
+ } else {
+ builder->node(desktop->label);
+ }
+ added_states++;
+ }
+ }
+ return added_states > 0;
+ } else {
+ return false;
+ }
+ }
+
+ void xworkspaces_module::action_focus(const string& data) {
+ focus_desktop(std::strtoul(data.c_str(), nullptr, 10));
+ }
+
+ void xworkspaces_module::action_next() {
+ focus_direction(true);
+ }
+
+ void xworkspaces_module::action_prev() {
+ focus_direction(false);
+ }
+
+ /**
+ * Focuses either the next or previous desktop.
+ *
+ * Will wrap around at the ends and go in the order the desktops are displayed.
+ */
+ void xworkspaces_module::focus_direction(bool next) {
+ unsigned int current_desktop{ewmh_util::get_current_desktop()};
+ int current_index = -1;
+
+ /*
+ * Desktop indices in the order they are displayed.
+ */
+ vector<unsigned int> indices;
+
+ for (auto&& viewport : m_viewports) {
+ for (auto&& desktop : viewport->desktops) {
+ if (current_desktop == desktop->index) {
+ current_index = indices.size();
+ }
+
+ indices.emplace_back(desktop->index);
+ }
+ }
+
+ if (current_index == -1) {
+ m_log.err("%s: Current desktop (%u) not found in list of desktops", name(), current_desktop);
+ return;
+ }
+
+ int offset = next ? 1 : -1;
+
+ int new_index = (current_index + offset + indices.size()) % indices.size();
+ focus_desktop(indices.at(new_index));
+ }
+
+ void xworkspaces_module::focus_desktop(unsigned new_desktop) {
+ unsigned int current_desktop{ewmh_util::get_current_desktop()};
+ if (new_desktop != current_desktop) {
+ m_log.info("%s: Requesting change to desktop #%u", name(), new_desktop);
+ ewmh_util::change_current_desktop(new_desktop);
+ } else {
+ m_log.info("%s: Ignoring change to current desktop", name());
+ }
+ }
+} // namespace modules
+
+POLYBAR_NS_END
--- /dev/null
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <deque>
+
+#include "common.hpp"
+#include "components/eventloop.hpp"
+#include "ipc/decoder.hpp"
+#include "ipc/encoder.hpp"
+#include "ipc/msg.hpp"
+#include "ipc/util.hpp"
+#include "modules/ipc.hpp"
+#include "utils/actions.hpp"
+#include "utils/file.hpp"
+
+using namespace std;
+using namespace polybar;
+using namespace eventloop;
+
+static const char* exec = nullptr;
+static constexpr auto USAGE = "<command=(action|cmd)> <payload> [...]";
+static constexpr auto USAGE_HOOK = "hook <module-name> <hook-index>";
+
+void display(const string& msg) {
+ fprintf(stdout, "%s\n", msg.c_str());
+}
+
+void error(const string& msg) {
+ throw std::runtime_error(msg);
+}
+
+void uv_error(int status, int pid, const string& msg) {
+ fprintf(stderr, "%s: %s (PID: %d)\n", msg.c_str(), uv_strerror(status), pid);
+}
+
+void usage(FILE* f, const string& parameters) {
+ fprintf(f, "Usage: %s [-p pid] %s\n", exec, parameters.c_str());
+}
+
+void remove_socket(const string& handle) {
+ if (unlink(handle.c_str()) == -1) {
+ error("Could not remove stale ipc channel: "s + strerror(errno));
+ } else {
+ display("Removed stale ipc channel: " + handle);
+ }
+}
+
+bool validate_type(const string& type) {
+ return (type == "action" || type == "cmd" || type == "hook");
+}
+
+static vector<string> get_sockets() {
+ auto sockets = file_util::glob(ipc::get_glob_socket_path());
+
+ auto new_end = std::remove_if(sockets.begin(), sockets.end(), [](const auto& path) {
+ int pid = ipc::get_pid_from_socket(path);
+
+ if (pid <= 0) {
+ return true;
+ }
+
+ if (!file_util::exists("/proc/" + to_string(pid))) {
+ remove_socket(path);
+ return true;
+ }
+
+ return false;
+ });
+
+ sockets.erase(new_end, sockets.end());
+
+ return sockets;
+}
+
+static void on_write(PipeHandle& conn, ipc::decoder& dec, int pid) {
+ conn.read_start(
+ [&](const auto& e) {
+ try {
+ if (!dec.closed()) {
+ dec.on_read(reinterpret_cast<const uint8_t*>(e.data), e.len);
+ }
+ } catch (const ipc::decoder::error& e) {
+ conn.close();
+ }
+ },
+ [&]() { conn.close(); },
+ [&, pid](const auto& e) {
+ conn.close();
+ uv_error(e.status, pid, "There was an error while reading polybar's response");
+ });
+}
+
+static void on_connection(PipeHandle& conn, ipc::decoder& dec, int pid, const ipc::type_t type, const string& payload) {
+ const auto data = ipc::encode(type, payload);
+ conn.write(
+ data, [&, pid]() { on_write(conn, dec, pid); },
+ [&, pid](const auto& e) {
+ conn.close();
+ uv_error(e.status, pid, "There was an error while sending the IPC message.");
+ });
+}
+
+static std::pair<ipc::type_t, string> parse_message(deque<string> args) {
+ // Validate args
+ const string ipc_type{args.front()};
+ args.pop_front();
+ string ipc_payload{args.front()};
+ args.pop_front();
+
+ if (!validate_type(ipc_type)) {
+ error("\"" + ipc_type + "\" is not a valid message type.");
+ }
+
+ ipc::type_t type = ipc::TYPE_ERR;
+
+ /*
+ * Check hook specific args
+ *
+ * The hook type is deprecated. Its contents are translated into a hook action.
+ */
+ if (ipc_type == "hook") {
+ if (args.size() != 1) {
+ usage(stderr, USAGE_HOOK);
+ throw std::runtime_error("Mismatched number of arguments for hook, expected 1, got "s + to_string(args.size()));
+ } else {
+ if (ipc_payload.find("module/") == 0) {
+ ipc_payload.erase(0, strlen("module/"));
+ }
+
+ // Hook commands use 1-indexed hooks but actions use 0-indexed ones
+ int hook_index = std::stoi(args.front()) - 1;
+ args.pop_front();
+
+ type = to_integral(ipc::v0::ipc_type::ACTION);
+ ipc_payload =
+ actions_util::get_action_string(ipc_payload, polybar::modules::ipc_module::EVENT_HOOK, to_string(hook_index));
+
+ fprintf(stderr,
+ "Warning: Using IPC hook commands is deprecated, use the hook action on the ipc module: %s %s \"%s\"\n", exec,
+ "action", ipc_payload.c_str());
+ }
+ }
+
+ if (ipc_type == "action") {
+ type = to_integral(ipc::v0::ipc_type::ACTION);
+ /**
+ * Alternatively polybar-msg action <module name> <action> <data>
+ * is also accepted
+ */
+ if (!args.empty()) {
+ string name = ipc_payload;
+ string action = args.front();
+ args.pop_front();
+ string data{};
+ if (!args.empty()) {
+ data = args.front();
+ args.pop_front();
+ }
+
+ ipc_payload = actions_util::get_action_string(name, action, data);
+ }
+ }
+
+ if (ipc_type == "cmd") {
+ type = to_integral(ipc::v0::ipc_type::CMD);
+ }
+
+ if (!args.empty()) {
+ error("Too many arguments");
+ }
+
+ assert(type != ipc::TYPE_ERR);
+ return {type, ipc_payload};
+}
+
+int run(int argc, char** argv) {
+ deque<string> args{argv + 1, argv + argc};
+
+ string socket_path;
+
+ auto help_pos = find_if(args.begin(), args.end(), [](const string& a) { return a == "-h" || a == "--help"; });
+ if (help_pos != args.end()) {
+ usage(stdout, USAGE);
+ return EXIT_SUCCESS;
+ }
+
+ /* If -p <pid> is passed, check if the process is running and that
+ * a valid channel socket is available
+ */
+ if (args.size() >= 2 && args[0].compare(0, 2, "-p") == 0) {
+ auto& pid_string = args[1];
+ socket_path = ipc::get_socket_path(pid_string);
+ if (!file_util::exists("/proc/" + pid_string)) {
+ error("No process with pid " + pid_string);
+ } else if (!file_util::exists(socket_path)) {
+ error("No channel available for pid " + pid_string);
+ }
+
+ args.pop_front();
+ args.pop_front();
+ }
+
+ // If no pid was given, search for all open sockets.
+ auto sockets = socket_path.empty() ? get_sockets() : vector<string>{socket_path};
+
+ // Get availble channel sockets
+ if (sockets.empty()) {
+ error("No active ipc channels");
+ }
+
+ if (args.size() < 2) {
+ usage(stderr, USAGE);
+ return EXIT_FAILURE;
+ }
+
+ string payload;
+ ipc::type_t type;
+ std::tie(type, payload) = parse_message(args);
+ string type_str = type == to_integral(ipc::v0::ipc_type::ACTION) ? "action" : "command";
+
+ bool success = true;
+
+ loop loop;
+
+ logger null_logger{loglevel::NONE};
+
+ /*
+ * Store all decoders in vector so that they're alive for the whole eventloop.
+ */
+ vector<ipc::decoder> decoders;
+
+ for (auto&& channel : sockets) {
+ int pid = ipc::get_pid_from_socket(channel);
+ assert(pid > 0);
+
+ decoders.emplace_back(
+ null_logger, [pid, channel, &payload, &type_str, &success](uint8_t, ipc::type_t type, const auto& response) {
+ switch (type) {
+ case ipc::TYPE_OK:
+ printf("Successfully wrote %s '%s' to PID %d\n", type_str.c_str(), payload.c_str(), pid);
+ break;
+ case ipc::TYPE_ERR: {
+ string err_str{response.begin(), response.end()};
+ fprintf(stderr, "%s: Failed to write %s '%s' to PID %d (reason: %s)\n", exec, type_str.c_str(),
+ payload.c_str(), pid, err_str.c_str());
+ success = false;
+ break;
+ }
+ default:
+ fprintf(stderr, "%s: Got back unrecognized message type %d from PID %d\n", exec, type, pid);
+ success = false;
+ break;
+ }
+ });
+
+ /*
+ * Index to decoder is captured because reference can be invalidated due to the vector being modified.
+ */
+ auto idx = decoders.size() - 1;
+
+ auto conn = loop.handle<PipeHandle>();
+ conn->connect(
+ channel,
+ [&handle = *conn, &decoders, pid, type, payload, channel, idx]() {
+ on_connection(handle, decoders[idx], pid, type, payload);
+ },
+ [&](const auto& e) {
+ fprintf(stderr, "%s: Failed to connect to '%s' (err: '%s')\n", exec, channel.c_str(), uv_strerror(e.status));
+ success = false;
+ });
+ }
+
+ try {
+ loop.run();
+ } catch (const exception& e) {
+ throw std::runtime_error("Uncaught exception in eventloop: "s + e.what());
+ }
+
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int main(int argc, char** argv) {
+ exec = argv[0];
+ try {
+ return run(argc, argv);
+ } catch (const std::exception& e) {
+ fprintf(stderr, "%s: %s\n", exec, e.what());
+ return EXIT_FAILURE;
+ }
+}
--- /dev/null
+#include "settings.hpp"
+
+const char* const APP_NAME{"@PROJECT_NAME@"};
+const char* const APP_VERSION{"@APP_VERSION@"};
+
+const int SINK_PRIORITY_BAR{1};
+const int SINK_PRIORITY_SCREEN{2};
+const int SINK_PRIORITY_TRAY{3};
+const int SINK_PRIORITY_MODULE{4};
+
+const char* const ALSA_SOUNDCARD{"@SETTING_ALSA_SOUNDCARD@"};
+const char* const BSPWM_SOCKET_PATH{"@SETTING_BSPWM_SOCKET_PATH@"};
+const char* const BSPWM_STATUS_PREFIX{"@SETTING_BSPWM_STATUS_PREFIX@"};
+const char* const CONNECTION_TEST_IP{"@SETTING_CONNECTION_TEST_IP@"};
+const char* const PATH_ADAPTER{"@SETTING_PATH_ADAPTER@"};
+const char* const PATH_BACKLIGHT{"@SETTING_PATH_BACKLIGHT@"};
+const char* const PATH_BATTERY{"@SETTING_PATH_BATTERY@"};
+const char* const PATH_CPU_INFO{"@SETTING_PATH_CPU_INFO@"};
+const char* const PATH_MEMORY_INFO{"@SETTING_PATH_MEMORY_INFO@"};
+const char* const PATH_MESSAGING_FIFO{"@SETTING_PATH_MESSAGING_FIFO@"};
+const char* const PATH_TEMPERATURE_INFO{"@SETTING_PATH_TEMPERATURE_INFO@"};
+const char* const PATH_THERMAL_ZONE_WILDCARD{"@SETTING_PATH_THERMAL_ZONE_WILDCARD@"};
+const char* const WIRELESS_LIB{"@WIRELESS_LIB@"};
+
+bool version_details(const std::vector<std::string>& args) {
+ for (auto&& arg : args) {
+ if (arg.compare(0, 3, "-vv") == 0)
+ return true;
+ }
+ return false;
+}
+
+// clang-format off
+void print_build_info(bool extended) {
+ printf("%s %s\n\n", APP_NAME, APP_VERSION);
+ printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork(%s) %cpulseaudio %cxkeyboard\n",
+ (ENABLE_ALSA ? '+' : '-'),
+ (ENABLE_CURL ? '+' : '-'),
+ (ENABLE_I3 ? '+' : '-'),
+ (ENABLE_MPD ? '+' : '-'),
+ (ENABLE_NETWORK ? '+' : '-'),
+ WIRELESS_LIB,
+ (ENABLE_PULSEAUDIO ? '+' : '-'),
+ (ENABLE_XKEYBOARD ? '+' : '-'));
+ if (extended) {
+ printf("\n");
+ printf("X extensions: %crandr (%cmonitors) %ccomposite %cxkb %cxrm %cxcursor\n",
+ (WITH_XRANDR ? '+' : '-'),
+ (WITH_XRANDR_MONITORS ? '+' : '-'),
+ (WITH_XCOMPOSITE ? '+' : '-'),
+ (WITH_XKB ? '+' : '-'),
+ (WITH_XRM ? '+' : '-'),
+ (WITH_XCURSOR ? '+' : '-'));
+ printf("\n");
+ printf("Build type: @CMAKE_BUILD_TYPE@\n");
+ printf("Compiler: @CMAKE_CXX_COMPILER@\n");
+ printf("Compiler flags: @CMAKE_CXX_FLAGS@ ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}} @cxx_flags_str@\n");
+ printf("Linker flags: @CMAKE_EXE_LINKER_FLAGS@ ${CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE_UPPER}} @cxx_linker_flags_str@\n");
+ }
+}
+// clang-format on
+
+// vim:ft=cpp
--- /dev/null
+#include "tags/action_context.hpp"
+
+#include <cassert>
+
+POLYBAR_NS
+
+namespace tags {
+
+ void action_context::reset() {
+ m_action_blocks.clear();
+ }
+
+ action_t action_context::action_open(mousebtn btn, const string&& cmd, alignment align, double x) {
+ action_t id = m_action_blocks.size();
+ m_action_blocks.emplace_back(std::move(cmd), btn, align, true);
+ set_start(id, x);
+ return id;
+ }
+
+ std::pair<action_t, mousebtn> action_context::action_close(mousebtn btn, alignment align, double x) {
+ for (auto it = m_action_blocks.rbegin(); it != m_action_blocks.rend(); it++) {
+ if (it->is_open && it->align == align && (btn == mousebtn::NONE || it->button == btn)) {
+ it->is_open = false;
+
+ // Converts a reverse iterator into an index
+ action_t id = std::distance(m_action_blocks.begin(), it.base()) - 1;
+ set_end(id, x);
+ return {id, it->button};
+ }
+ }
+
+ return {NO_ACTION, mousebtn::NONE};
+ }
+
+ void action_context::set_start(action_t id, double x) {
+ m_action_blocks[id].start_x = x;
+ }
+
+ void action_context::set_end(action_t id, double x) {
+ /*
+ * Only ever increase the end position.
+ * A larger end position may have been set before.
+ */
+ m_action_blocks[id].end_x = std::max(m_action_blocks[id].end_x, x);
+ }
+
+ void action_context::compensate_for_negative_move(alignment a, double old_x, double new_x) {
+ assert(new_x < old_x);
+ for (auto& block : m_action_blocks) {
+ if (block.is_open && block.align == a) {
+ // Move back the start position if a smaller position is observed
+ if (block.start_x > new_x) {
+ block.start_x = new_x;
+ }
+
+ // Move forward the end position if a larger position is observed
+ if (old_x > block.end_x) {
+ block.end_x = old_x;
+ }
+ }
+ }
+ }
+
+ void action_context::set_alignment_start(const alignment a, const double x) {
+ m_align_start[a] = x;
+ }
+
+ std::map<mousebtn, tags::action_t> action_context::get_actions(int x) const {
+ std::map<mousebtn, tags::action_t> buttons;
+
+ for (int i = static_cast<int>(mousebtn::NONE); i < static_cast<int>(mousebtn::BTN_COUNT); i++) {
+ buttons[static_cast<mousebtn>(i)] = tags::NO_ACTION;
+ }
+
+ for (action_t id = 0; (unsigned)id < m_action_blocks.size(); id++) {
+ auto action = m_action_blocks[id];
+ mousebtn btn = action.button;
+
+ // Higher IDs are higher in the action stack.
+ if (id > buttons[btn] && action.test(m_align_start.at(action.align), x)) {
+ buttons[action.button] = id;
+ }
+ }
+
+ return buttons;
+ }
+
+ action_t action_context::has_action(mousebtn btn, int x) const {
+ return get_actions(x)[btn];
+ }
+
+ string action_context::get_action(action_t id) const {
+ assert(id >= 0 && (unsigned)id < num_actions());
+
+ return m_action_blocks[id].cmd;
+ }
+
+ bool action_context::has_double_click() const {
+ for (auto&& a : m_action_blocks) {
+ if (a.button == mousebtn::DOUBLE_LEFT || a.button == mousebtn::DOUBLE_MIDDLE ||
+ a.button == mousebtn::DOUBLE_RIGHT) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ size_t action_context::num_actions() const {
+ return m_action_blocks.size();
+ }
+
+ size_t action_context::num_unclosed() const {
+ size_t num = 0;
+
+ for (const auto& a : m_action_blocks) {
+ if (a.is_open) {
+ num++;
+ }
+ }
+
+ return num;
+ }
+
+ const std::vector<action_block>& action_context::get_blocks() const {
+ return m_action_blocks;
+ }
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#include "tags/context.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+ static rgba get_color(color_value c, rgba fallback) {
+ if (c.type == color_type::RESET) {
+ return fallback;
+ } else {
+ return c.val;
+ }
+ }
+
+ context::context(const bar_settings& settings) : m_settings(settings) {
+ reset();
+ }
+
+ void context::reset() {
+ apply_reset();
+ m_align = alignment::NONE;
+ }
+
+ void context::apply_bg(color_value c) {
+ m_bg = get_color(c, m_settings.background);
+ }
+
+ void context::apply_fg(color_value c) {
+ m_fg = get_color(c, m_settings.foreground);
+ }
+
+ void context::apply_ol(color_value c) {
+ m_ol = get_color(c, m_settings.overline.color);
+ }
+
+ void context::apply_ul(color_value c) {
+ m_ul = get_color(c, m_settings.underline.color);
+ }
+
+ void context::apply_font(int font) {
+ m_font = std::max(font, 0);
+ }
+
+ void context::apply_reverse() {
+ std::swap(m_bg, m_fg);
+ }
+
+ void context::apply_alignment(alignment align) {
+ m_align = align;
+ }
+
+ void context::apply_attr(attr_activation act, attribute attr) {
+ if (attr == attribute::NONE) {
+ return;
+ }
+
+ bool& current = attr == attribute::OVERLINE ? m_attr_overline : m_attr_underline;
+
+ switch (act) {
+ case attr_activation::ON:
+ current = true;
+ break;
+ case attr_activation::OFF:
+ current = false;
+ break;
+ case attr_activation::TOGGLE:
+ current = !current;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void context::store_tray_position(int x_pos) {
+ m_relative_tray_position = std::make_pair(get_alignment(), x_pos);
+ }
+
+ void context::apply_reset() {
+ m_bg = m_settings.background;
+ m_fg = m_settings.foreground;
+ m_ul = m_settings.underline.color;
+ m_ol = m_settings.overline.color;
+ m_font = 0;
+ m_attr_overline = false;
+ m_attr_underline = false;
+ }
+
+ rgba context::get_bg() const {
+ return m_bg;
+ }
+
+ rgba context::get_fg() const {
+ return m_fg;
+ }
+
+ rgba context::get_ol() const {
+ return m_ol;
+ }
+
+ rgba context::get_ul() const {
+ return m_ul;
+ }
+
+ int context::get_font() const {
+ return m_font;
+ }
+
+ bool context::has_overline() const {
+ return m_attr_overline;
+ }
+
+ bool context::has_underline() const {
+ return m_attr_underline;
+ }
+
+ alignment context::get_alignment() const {
+ return m_align;
+ }
+
+ std::pair<alignment, int> context::get_relative_tray_position() const {
+ return m_relative_tray_position;
+ }
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#include "tags/dispatch.hpp"
+
+#include <algorithm>
+
+#include "components/renderer.hpp"
+#include "events/signal.hpp"
+#include "settings.hpp"
+#include "tags/parser.hpp"
+#include "utils/color.hpp"
+#include "components/logger.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+ /**
+ * Create instance
+ */
+ dispatch::make_type dispatch::make(action_context& action_ctxt) {
+ return std::make_unique<dispatch>(logger::make(), action_ctxt);
+ }
+
+ /**
+ * Construct parser instance
+ */
+ dispatch::dispatch(const logger& logger, action_context& action_ctxt) : m_log(logger), m_action_ctxt(action_ctxt) {}
+
+ /**
+ * Process input string
+ */
+ void dispatch::parse(const bar_settings& bar, renderer_interface& renderer, const string&& data) {
+ tags::parser p;
+ p.set(std::move(data));
+
+ m_action_ctxt.reset();
+ m_ctxt = make_unique<context>(bar);
+
+ while (p.has_next_element()) {
+ tags::element el;
+ try {
+ el = p.next_element();
+ } catch (const tags::error& e) {
+ m_log.err("Parser error (reason: %s)", e.what());
+ continue;
+ }
+
+ alignment old_alignment = m_ctxt->get_alignment();
+ double old_x = old_alignment == alignment::NONE ? 0 : renderer.get_x(*m_ctxt);
+
+ if (el.is_tag) {
+ switch (el.tag_data.type) {
+ case tags::tag_type::FORMAT:
+ switch (el.tag_data.subtype.format) {
+ case tags::syntaxtag::A:
+ handle_action(renderer, el.tag_data.action.btn, el.tag_data.action.closing, std::move(el.data));
+ break;
+ case tags::syntaxtag::B:
+ m_ctxt->apply_bg(el.tag_data.color);
+ break;
+ case tags::syntaxtag::F:
+ m_ctxt->apply_fg(el.tag_data.color);
+ break;
+ case tags::syntaxtag::T:
+ m_ctxt->apply_font(el.tag_data.font);
+ break;
+ case tags::syntaxtag::O:
+ handle_offset(renderer, el.tag_data.offset);
+ break;
+ case tags::syntaxtag::R:
+ m_ctxt->apply_reverse();
+ break;
+ case tags::syntaxtag::o:
+ m_ctxt->apply_ol(el.tag_data.color);
+ break;
+ case tags::syntaxtag::u:
+ m_ctxt->apply_ul(el.tag_data.color);
+ break;
+ case tags::syntaxtag::P:
+ handle_control(renderer, el.tag_data.ctrl);
+ break;
+ case tags::syntaxtag::l:
+ handle_alignment(renderer, alignment::LEFT);
+ break;
+ case tags::syntaxtag::r:
+ handle_alignment(renderer, alignment::RIGHT);
+ break;
+ case tags::syntaxtag::c:
+ handle_alignment(renderer, alignment::CENTER);
+ break;
+ default:
+ throw runtime_error(
+ "Unrecognized tag format: " + to_string(static_cast<int>(el.tag_data.subtype.format)));
+ }
+ break;
+ case tags::tag_type::ATTR:
+ m_ctxt->apply_attr(el.tag_data.subtype.activation, el.tag_data.attr);
+ break;
+ }
+ } else {
+ handle_text(renderer, std::move(el.data));
+ }
+
+ if (old_alignment == m_ctxt->get_alignment()) {
+ double new_x = renderer.get_x(*m_ctxt);
+ if (new_x < old_x) {
+ m_action_ctxt.compensate_for_negative_move(old_alignment, old_x, new_x);
+ }
+ }
+ }
+
+ /*
+ * After rendering, we need to tell the action context about the position
+ * of the alignment blocks so that it can do intersection tests.
+ */
+ for (auto a : {alignment::LEFT, alignment::CENTER, alignment::RIGHT}) {
+ m_action_ctxt.set_alignment_start(a, renderer.get_alignment_start(a));
+ }
+ renderer.apply_tray_position(*m_ctxt);
+
+ auto num_unclosed = m_action_ctxt.num_unclosed();
+
+ if (num_unclosed != 0) {
+ throw runtime_error(to_string(num_unclosed) + " unclosed action block(s)");
+ }
+ }
+
+ /**
+ * Process text contents
+ */
+ void dispatch::handle_text(renderer_interface& renderer, string&& data) {
+#ifdef DEBUG_WHITESPACE
+ string::size_type p;
+ while ((p = data.find(' ')) != string::npos) {
+ data.replace(p, 1, "-"s);
+ }
+#endif
+
+ renderer.render_text(*m_ctxt, std::move(data));
+ }
+
+ void dispatch::handle_action(renderer_interface& renderer, mousebtn btn, bool closing, const string&& cmd) {
+ if (closing) {
+ m_action_ctxt.action_close(btn, m_ctxt->get_alignment(), renderer.get_x(*m_ctxt));
+ } else {
+ m_action_ctxt.action_open(btn, std::move(cmd), m_ctxt->get_alignment(), renderer.get_x(*m_ctxt));
+ }
+ }
+
+ void dispatch::handle_offset(renderer_interface& renderer, extent_val offset) {
+ renderer.render_offset(*m_ctxt, offset);
+ }
+
+ void dispatch::handle_alignment(renderer_interface& renderer, alignment a) {
+ m_ctxt->apply_alignment(a);
+ renderer.change_alignment(*m_ctxt);
+ }
+
+ void dispatch::handle_control(renderer_interface& renderer, controltag ctrl) {
+ switch (ctrl) {
+ case controltag::R:
+ m_ctxt->apply_reset();
+ break;
+ case controltag::t:
+ m_ctxt->store_tray_position(renderer.get_x(*m_ctxt));
+ break;
+ default:
+ throw runtime_error("Unrecognized polybar control tag: " + to_string(static_cast<int>(ctrl)));
+ }
+ }
+
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#include "tags/parser.hpp"
+
+#include <cassert>
+#include <cctype>
+
+#include "utils/units.hpp"
+
+POLYBAR_NS
+
+namespace tags {
+
+ bool parser::has_next_element() {
+ return buf_pos < buf.size() || has_next();
+ }
+
+ element parser::next_element() {
+ if (!has_next_element()) {
+ throw std::runtime_error("tag parser: No next element");
+ }
+
+ if (buf_pos >= buf.size()) {
+ parse_step();
+ }
+
+ if (buf_pos >= buf.size()) {
+ throw std::runtime_error("tag parser: No next element. THIS IS A BUG. (Context: '" + input + "')");
+ }
+
+ element e = buf[buf_pos];
+ buf_pos++;
+
+ if (buf_pos == buf.size()) {
+ buf.clear();
+ buf_pos = 0;
+ }
+
+ return e;
+ }
+
+ format_string parser::parse() {
+ format_string parsed;
+
+ while (has_next_element()) {
+ parsed.push_back(next_element());
+ }
+
+ return parsed;
+ }
+
+ /**
+ * Performs a single parse step.
+ *
+ * This means it will parse text until the next tag is reached or it will
+ * parse an entire %{...} tag.
+ */
+ void parser::parse_step() {
+ char c;
+
+ /*
+ * If we have already parsed text, we can stop if we reach a tag.
+ */
+ bool text_parsed = false;
+
+ size_t start_pos = pos;
+
+ try {
+ while ((c = next())) {
+ // TODO here we could think about how to escape an action tag
+ if (c == '%' && has_next() && peek() == '{') {
+ /*
+ * If we have already parsed text, encountering a tag opening means
+ * we can stop parsing now because we parsed at least one entire
+ * element (the text up to the beginning of the tag).
+ */
+ if (text_parsed) {
+ // Put back the '%'
+ revert();
+ break;
+ }
+
+ consume('{');
+ consume_space();
+ parse_tag();
+ break;
+ } else {
+ push_char(c);
+ text_parsed = true;
+ }
+ }
+ } catch (error& e) {
+ e.set_context(input.substr(start_pos, pos - start_pos));
+ throw;
+ }
+ }
+
+ void parser::set(const string&& input) {
+ this->input = std::move(input);
+ pos = 0;
+ buf.clear();
+ buf_pos = 0;
+ }
+
+ bool parser::has_next() const {
+ return pos < input.size();
+ }
+
+ char parser::next() {
+ char c = peek();
+ pos++;
+ return c;
+ }
+
+ char parser::peek() const {
+ if (!has_next()) {
+ return EOL;
+ }
+
+ return input[pos];
+ }
+
+ /**
+ * Puts back a single character in the input string.
+ */
+ void parser::revert() {
+ assert(pos > 0);
+ pos--;
+ }
+
+ void parser::consume(char c) {
+ char n = next();
+ if (n != c) {
+ throw tags::token_error(n, c);
+ }
+ }
+
+ void parser::consume_space() {
+ while (peek() == ' ') {
+ next();
+ }
+ }
+
+ /**
+ * Parses an entire %{....} tag.
+ *
+ * '%' and '{' were already consumed and we are currently on the first character
+ * inside the tag.
+ * At the end of this method, we should be on the closing '}' character (not
+ * yet consumed).
+ */
+
+ void parser::parse_tag() {
+ if (!has_next()) {
+ throw token_error(EOL, "Formatting tag content");
+ }
+
+ while (has_next()) {
+ parse_single_tag_content();
+
+ int p = peek();
+
+ if (p != ' ' && p != '}') {
+ throw tag_end_error(p);
+ } else {
+ /**
+ * Consume whitespace between elements inside the tag
+ */
+ consume_space();
+
+ if (peek() == '}') {
+ consume('}');
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a single element inside a formatting tag.
+ *
+ * For example it would parse the foreground part of the following tag:
+ *
+ * %{F#ff0000 B#ff0000}
+ * ^ ^
+ * | - Pointer at the end
+ * |
+ * - Pointer at the start
+ */
+ void parser::parse_single_tag_content() {
+ char c = next();
+
+ /**
+ * %{U...} is a special case because it produces over and underline tags.
+ */
+ if (c == 'U') {
+ element e{};
+ e.is_tag = true;
+ e.tag_data.type = tag_type::FORMAT;
+ e.tag_data.subtype.format = syntaxtag::u;
+ e.tag_data.color = parse_color();
+ buf.emplace_back(e);
+
+ e.tag_data.subtype.format = syntaxtag::o;
+ buf.emplace_back(e);
+ return;
+ }
+
+ tag_type type;
+ tag_subtype sub;
+
+ switch (c) {
+ // clang-format off
+ case 'B': sub.format = syntaxtag::B; break;
+ case 'F': sub.format = syntaxtag::F; break;
+ case 'u': sub.format = syntaxtag::u; break;
+ case 'o': sub.format = syntaxtag::o; break;
+ case 'T': sub.format = syntaxtag::T; break;
+ case 'R': sub.format = syntaxtag::R; break;
+ case 'O': sub.format = syntaxtag::O; break;
+ case 'P': sub.format = syntaxtag::P; break;
+ case 'A': sub.format = syntaxtag::A; break;
+ case 'l': sub.format = syntaxtag::l; break;
+ case 'c': sub.format = syntaxtag::c; break;
+ case 'r': sub.format = syntaxtag::r; break;
+
+ case '+': sub.activation = attr_activation::ON; break;
+ case '-': sub.activation = attr_activation::OFF; break;
+ case '!': sub.activation = attr_activation::TOGGLE; break;
+
+ // clang-format on
+
+ default:
+ throw unrecognized_tag(c);
+ }
+
+ switch (c) {
+ case 'B':
+ case 'F':
+ case 'u':
+ case 'o':
+ case 'T':
+ case 'R':
+ case 'O':
+ case 'P':
+ case 'A':
+ case 'l':
+ case 'c':
+ case 'r':
+ type = tag_type::FORMAT;
+ break;
+
+ case '+':
+ case '-':
+ case '!':
+ type = tag_type::ATTR;
+ break;
+
+ default:
+ throw unrecognized_tag(c);
+ }
+
+ tag tag_data{};
+ tag_data.type = type;
+ tag_data.subtype = sub;
+
+ element e{};
+ e.is_tag = true;
+
+ switch (c) {
+ case 'B':
+ case 'F':
+ case 'u':
+ case 'o':
+ tag_data.color = parse_color();
+ break;
+ case 'T':
+ tag_data.font = parse_fontindex();
+ break;
+ case 'O':
+ tag_data.offset = parse_offset();
+ break;
+ case 'P':
+ tag_data.ctrl = parse_control();
+ break;
+ case 'A':
+ std::tie(tag_data.action, e.data) = parse_action();
+ break;
+
+ case '+':
+ case '-':
+ case '!':
+ tag_data.attr = parse_attribute();
+ break;
+ }
+
+ e.tag_data = tag_data;
+ buf.emplace_back(e);
+ }
+
+ color_value parser::parse_color() {
+ string s = get_tag_value();
+
+ color_value ret;
+
+ if (s.empty() || s == "-") {
+ ret.type = color_type::RESET;
+ } else {
+ rgba c{s};
+
+ if (!c.has_color()) {
+ throw color_error(s);
+ }
+
+ ret.type = color_type::COLOR;
+ ret.val = c;
+ }
+
+ return ret;
+ }
+
+ int parser::parse_fontindex() {
+ string s = get_tag_value();
+
+ if (s.empty() || s == "-") {
+ return 0;
+ }
+
+ try {
+ size_t ptr;
+ int ret = std::stoi(s, &ptr, 10);
+
+ if (ret < 0) {
+ return 0;
+ }
+
+ if (ptr != s.size()) {
+ throw font_error(s, "Font index contains non-number characters");
+ }
+
+ return ret;
+ } catch (const std::exception& err) {
+ throw font_error(s, err.what());
+ }
+ }
+
+ extent_val parser::parse_offset() {
+ string s = get_tag_value();
+
+ if (s.empty()) {
+ return ZERO_PX_EXTENT;
+ }
+
+ try {
+ return units_utils::parse_extent(string{s});
+ } catch (const std::exception& err) {
+ throw offset_error(s, err.what());
+ }
+ }
+
+ controltag parser::parse_control() {
+ string s = get_tag_value();
+
+ if (s.empty()) {
+ throw control_error(s, "Control tag is empty");
+ }
+
+ switch (s[0]) {
+ case 'R':
+ if (s.size() != 1) {
+ throw control_error(s, "Control tag R has extra data");
+ }
+
+ return controltag::R;
+ case 't':
+ return controltag::t;
+ default:
+ throw control_error(s);
+ }
+ }
+
+ std::pair<action_value, string> parser::parse_action() {
+ mousebtn btn = parse_action_btn();
+
+ action_value ret;
+
+ string cmd;
+
+ if (has_next() && peek() == ':') {
+ ret.btn = btn == mousebtn::NONE ? mousebtn::LEFT : btn;
+ ret.closing = false;
+ cmd = parse_action_cmd();
+ } else {
+ ret.btn = btn;
+ ret.closing = true;
+ }
+
+ return {ret, cmd};
+ }
+
+ /**
+ * Parses the button index after starting an action tag.
+ *
+ * May return mousebtn::NONE if no button was given.
+ */
+ mousebtn parser::parse_action_btn() {
+ if (has_next()) {
+ if (isdigit(peek())) {
+ char c = next();
+ int num = c - '0';
+
+ if (num < static_cast<int>(mousebtn::NONE) || num >= static_cast<int>(mousebtn::BTN_COUNT)) {
+ throw btn_error(string{c});
+ }
+
+ return static_cast<mousebtn>(num);
+ }
+ }
+
+ return mousebtn::NONE;
+ }
+
+ /**
+ * Starts at ':' and parses a complete action string.
+ *
+ * Returns the parsed action string with without escaping backslashes.
+ *
+ * Afterwards the parsers will be at the character immediately after the
+ * closing colon.
+ */
+ string parser::parse_action_cmd() {
+ consume(':');
+
+ string s;
+
+ char prev = EOL;
+
+ while (has_next()) {
+ char c = next();
+
+ if (c == ':') {
+ if (prev == '\\') {
+ s.pop_back();
+ s.push_back(c);
+ } else {
+ break;
+ }
+ } else {
+ s.push_back(c);
+ }
+
+ prev = c;
+ }
+
+ return s;
+ }
+
+ attribute parser::parse_attribute() {
+ char c;
+ switch (c = next()) {
+ case 'u':
+ return attribute::UNDERLINE;
+ case 'o':
+ return attribute::OVERLINE;
+ default:
+ throw unrecognized_attr(c);
+ }
+ }
+
+ void parser::push_char(char c) {
+ if (!buf.empty() && buf_pos < buf.size() && !buf.back().is_tag) {
+ buf.back().data += c;
+ } else {
+ buf.emplace_back(string{c});
+ }
+ }
+
+ void parser::push_text(string&& text) {
+ if (text.empty()) {
+ return;
+ }
+
+ if (!buf.empty() && buf_pos < buf.size() && !buf.back().is_tag) {
+ buf.back().data += text;
+ } else {
+ buf.emplace_back(std::move(text));
+ }
+ }
+
+ /**
+ * Will read up until the end of the tag value.
+ *
+ * Afterwards the parser will be at the character directly after the tag
+ * value.
+ *
+ * This function just reads until it encounters a space or a closing curly
+ * bracket, so it is not useful for tag values that can contain these
+ * characters (e.g. action tags).
+ */
+ string parser::get_tag_value() {
+ string s;
+
+ while (has_next() && peek() != ' ' && peek() != '}') {
+ s.push_back(next());
+ }
+
+ return s;
+ }
+} // namespace tags
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/action_router.hpp"
+
+POLYBAR_NS
+
+action_router::entry::entry(callback func) : without(func), with_data(false){}
+action_router::entry::entry(callback_data func) : with(func), with_data(true){}
+action_router::entry::~entry() {
+ if (with_data) {
+ with.~function();
+ } else {
+ without.~function();
+ }
+}
+
+void action_router::register_action(const string& name, callback func) {
+ register_entry(name, func);
+}
+
+void action_router::register_action_with_data(const string& name, callback_data func) {
+ register_entry(name, func);
+}
+
+bool action_router::has_action(const string& name) {
+ return callbacks.find(name) != callbacks.end();
+}
+
+/**
+ * Invokes the given action name on the passed module pointer.
+ *
+ * The action must exist.
+ */
+void action_router::invoke(const string& name, const string& data) {
+ auto it = callbacks.find(name);
+ assert(it != callbacks.end());
+
+ auto& e = it->second;
+
+ if (e.with_data) {
+ e.with(data);
+ } else {
+ e.without();
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/actions.hpp"
+
+#include <cassert>
+#include <stdexcept>
+
+#include "common.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace actions_util {
+ string get_action_string(const modules::module_interface& module, string action, string data) {
+ return get_action_string(module.name_raw(), action, data);
+ }
+
+ string get_action_string(const string& module_name, string action, string data) {
+ string str = "#" + module_name + "." + action;
+ if (!data.empty()) {
+ str += "." + data;
+ }
+
+ return str;
+ }
+
+ std::tuple<string, string, string> parse_action_string(string action_str) {
+ assert(action_str.front() == '#');
+
+ action_str.erase(0, 1);
+
+ auto action_sep = action_str.find('.');
+
+ if (action_sep == string::npos) {
+ throw std::runtime_error("Missing separator between name and action");
+ }
+
+ auto module_name = action_str.substr(0, action_sep);
+
+ if (module_name.empty()) {
+ throw std::runtime_error("The module name must not be empty");
+ }
+
+ auto action = action_str.substr(action_sep + 1);
+ auto data_sep = action.find('.');
+ string data;
+
+ if (data_sep != string::npos) {
+ data = action.substr(data_sep + 1);
+ action.erase(data_sep);
+ }
+
+ if (action.empty()) {
+ throw std::runtime_error("The action name must not be empty");
+ }
+
+ return std::tuple<string, string, string>{module_name, action, data};
+ }
+} // namespace actions_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/bspwm.hpp"
+
+#include <sys/un.h>
+#include <xcb/xcb.h>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+#include "x11/icccm.hpp"
+
+POLYBAR_NS
+
+namespace bspwm_util {
+/**
+ * Returns window against which to restack.
+ *
+ * Bspwm creates one root window per monitor with a `WM_CLASS` value of `root\0Bspwm` and the window taking up the
+ * entire monitor.
+ * For overlapping monitors, stacking polybar above the root window for its monitor, but below the root window for an
+ * overlapping monitor, may cause the upper root window to obstruct polybar, at least in terms of receiving mouse
+ * clicks. Because of that, we simply restack polybar above the topmost root window.
+ */
+restack_util::params get_restack_params(connection& conn) {
+ auto children = conn.query_tree(conn.root()).children();
+
+ xcb_window_t top_root = XCB_NONE;
+
+ // Iteration happens from bottom to top
+ for (xcb_window_t wid : children) {
+ auto [instance_name, class_name] = icccm_util::get_wm_class(conn, wid);
+
+ if (string_util::compare("root", instance_name) && string_util::compare("Bspwm", class_name)) {
+ top_root = wid;
+ }
+ }
+
+ return {top_root, XCB_STACK_MODE_ABOVE};
+}
+
+/**
+ * Get path to the bspwm socket by the following order
+ *
+ * 1. Value of environment variable BSPWM_SOCKET
+ * 2. Value built from the bspwm socket path template
+ * 3. Value of the macro BSPWM_SOCKET_PATH
+ */
+string get_socket_path() {
+ string env_path;
+
+ if (!(env_path = env_util::get("BSPWM_SOCKET")).empty()) {
+ return env_path;
+ }
+
+ struct sockaddr_un sa {};
+ char* host = nullptr;
+ int dsp = 0;
+ int scr = 0;
+
+ if (xcb_parse_display(nullptr, &host, &dsp, &scr) == 0) {
+ return BSPWM_SOCKET_PATH;
+ }
+
+ snprintf(sa.sun_path, sizeof(sa.sun_path), "/tmp/bspwm%s_%i_%i-socket", host, dsp, scr);
+ free(host);
+
+ return sa.sun_path;
+}
+
+/**
+ * Generate a payload object with properly formatted data
+ * ready to be sent to the bspwm ipc controller
+ */
+payload_t make_payload(const string& cmd) {
+ payload_t payload{new payload_t::element_type{}};
+ auto size = sizeof(payload->data);
+ int offset = 0;
+ int chars = 0;
+
+ for (auto&& word : string_util::split(cmd, ' ')) {
+ chars = snprintf(payload->data + offset, size - offset, "%s%c", word.c_str(), 0);
+ payload->len += chars;
+ offset += chars;
+ }
+
+ return payload;
+}
+
+/**
+ * Create an ipc socket connection
+ *
+ * Example usage:
+ * @code cpp
+ * auto ipc = make_connection();
+ * ipc->send(make_payload("desktop -f eDP-1:^1"));
+ * @endcode
+ */
+connection_t make_connection() {
+ return socket_util::make_unix_connection(get_socket_path());
+}
+
+/**
+ * Create a connection and subscribe to events
+ * on the bspwm socket
+ *
+ * Example usage:
+ * @code cpp
+ * auto ipc = make_subscriber();
+ *
+ * while (!ipc->poll(POLLHUP, 0)) {
+ * ssize_t bytes_received = 0;
+ * auto data = ipc->receive(BUFSIZ-1, bytes_received, 0);
+ * std::cout << data << std::endl;
+ * }
+ * @endcode
+ */
+connection_t make_subscriber() {
+ auto conn = make_connection();
+ auto payload = make_payload("subscribe report");
+ if (conn->send(payload->data, payload->len, 0) == 0) {
+ throw system_error("Failed to initialize subscriber");
+ }
+ return conn;
+}
+} // namespace bspwm_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/color.hpp"
+
+#include <algorithm>
+
+POLYBAR_NS
+
+/**
+ * Takes a hex string as input and brings it into a normalized form
+ *
+ * The input can either be only an alpha channel #AA
+ * or any of these forms: #RGB, #ARGB, #RRGGBB, #AARRGGBB
+ *
+ * Colors without alpha channel will get an alpha channel of FF
+ * The input does not have to start with '#'
+ *
+ * @returns Empty string for malformed input, either AA for the alpha only
+ * input or an 8 character string of the expanded form AARRGGBB
+ */
+static string normalize_hex(string hex) {
+ if (hex.length() == 0) {
+ return "";
+ }
+
+ // We remove the hash because it makes processing easier
+ if (hex[0] == '#') {
+ hex.erase(0, 1);
+ }
+
+ // Check that only valid characters are used
+ if (!std::all_of(hex.cbegin(), hex.cend(), isxdigit)) {
+ return "";
+ }
+
+ if (hex.length() == 2) {
+ // We only have an alpha channel
+ return hex;
+ }
+
+ if (hex.length() == 3) {
+ // RGB -> FRGB
+ hex.insert(0, 1, 'f');
+ }
+
+ if (hex.length() == 4) {
+ // ARGB -> AARRGGBB
+ hex = {hex[0], hex[0], hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]};
+ }
+
+ if (hex.length() == 6) {
+ // RRGGBB -> FFRRGGBB
+ hex.insert(0, 2, 'f');
+ }
+
+ if (hex.length() != 8) {
+ return "";
+ }
+
+ return hex;
+}
+
+rgba::rgba() : m_value(0), m_type(type::NONE) {}
+rgba::rgba(uint32_t value, enum type type) : m_value(value), m_type(type) {}
+rgba::rgba(string hex) {
+ hex = normalize_hex(hex);
+
+ if (hex.length() == 0) {
+ m_value = 0;
+ m_type = type::NONE;
+ } else if (hex.length() == 2) {
+ m_value = std::strtoul(hex.c_str(), nullptr, 16) << 24;
+ m_type = type::ALPHA_ONLY;
+ } else {
+ m_value = std::strtoul(hex.c_str(), nullptr, 16);
+ m_type = type::ARGB;
+ }
+}
+
+rgba::operator string() const {
+ char s[10];
+ size_t len = snprintf(s, 10, "#%08x", m_value);
+ return string(s, len);
+}
+
+bool rgba::operator==(const rgba& other) const {
+ if (m_type != other.m_type) {
+ return false;
+ }
+
+ switch (m_type) {
+ case type::NONE:
+ return true;
+ case type::ARGB:
+ return m_value == other.m_value;
+ case type::ALPHA_ONLY:
+ return alpha_i() == other.alpha_i();
+ default:
+ return false;
+ }
+}
+
+bool rgba::operator!=(const rgba& other) const {
+ return !(*this == other);
+}
+
+rgba::operator uint32_t() const {
+ return m_value;
+}
+
+rgba::operator bool() const {
+ return has_color();
+}
+
+uint32_t rgba::value() const {
+ return this->m_value;
+}
+
+enum rgba::type rgba::get_type() const {
+ return m_type;
+}
+
+double rgba::alpha_d() const {
+ return alpha_i() / 255.0;
+}
+
+double rgba::red_d() const {
+ return red_i() / 255.0;
+}
+
+double rgba::green_d() const {
+ return green_i() / 255.0;
+}
+
+double rgba::blue_d() const {
+ return blue_i() / 255.0;
+}
+
+uint8_t rgba::alpha_i() const {
+ return (m_value >> 24) & 0xFF;
+}
+
+uint8_t rgba::red_i() const {
+ return (m_value >> 16) & 0xFF;
+}
+
+uint8_t rgba::green_i() const {
+ return (m_value >> 8) & 0xFF;
+}
+
+uint8_t rgba::blue_i() const {
+ return m_value & 0xFF;
+}
+
+bool rgba::has_color() const {
+ return m_type != type::NONE;
+}
+
+bool rgba::is_transparent() const {
+ return alpha_i() != 0xFF;
+}
+
+/**
+ * Applies the alpha channel of this color to the given color.
+ */
+rgba rgba::apply_alpha_to(rgba other) const {
+ uint32_t val = (other.value() & 0x00FFFFFF) | (((uint32_t)alpha_i()) << 24);
+ return rgba(val, m_type);
+}
+
+/**
+ * If this is an ALPHA_ONLY color, applies this alpha channel to the other
+ * color, otherwise just returns this.
+ *
+ * @returns the new color if this is ALPHA_ONLY or a copy of this otherwise.
+ */
+rgba rgba::try_apply_alpha_to(rgba other) const {
+ if (m_type == type::ALPHA_ONLY) {
+ return apply_alpha_to(other);
+ }
+
+ return *this;
+}
+
+string color_util::simplify_hex(string hex) {
+ // convert #ffrrggbb to #rrggbb
+ if (hex.length() == 9 && std::toupper(hex[1]) == 'F' && std::toupper(hex[2]) == 'F') {
+ hex.erase(1, 2);
+ }
+
+ // convert #rrggbb to #rgb
+ if (hex.length() == 7) {
+ if (hex[1] == hex[2] && hex[3] == hex[4] && hex[5] == hex[6]) {
+ hex = {'#', hex[1], hex[3], hex[5]};
+ }
+ }
+
+ return hex;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/command.hpp"
+
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <csignal>
+#include <cstdlib>
+#include <utility>
+
+#include "errors.hpp"
+#include "utils/file.hpp"
+#include "utils/io.hpp"
+#include "utils/process.hpp"
+#include "utils/string.hpp"
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+POLYBAR_NS
+
+command<output_policy::IGNORED>::command(const logger& logger, string cmd) : m_log(logger), m_cmd(move(cmd)) {}
+
+command<output_policy::IGNORED>::~command() {
+ terminate();
+}
+
+/**
+ * Execute the command
+ */
+int command<output_policy::IGNORED>::exec(bool wait_for_completion) {
+ m_forkpid = process_util::spawn_async([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); });
+ if (wait_for_completion) {
+ auto status = wait();
+ m_forkpid = -1;
+ return status;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+void command<output_policy::IGNORED>::terminate() {
+ if (is_running()) {
+ m_log.trace("command: Sending SIGTERM to running child process (%d)", m_forkpid);
+ killpg(m_forkpid, SIGTERM);
+ wait();
+ }
+ m_forkpid = -1;
+}
+
+/**
+ * Check if command is running
+ */
+bool command<output_policy::IGNORED>::is_running() {
+ return m_forkpid > 0 && process_util::wait_for_completion_nohang(m_forkpid, &m_forkstatus) > -1;
+}
+
+/**
+ * Wait for the child process to finish
+ */
+int command<output_policy::IGNORED>::wait() {
+ do {
+ m_log.trace("command: Waiting for pid %d to finish...", m_forkpid);
+
+ process_util::wait_for_completion(m_forkpid, &m_forkstatus, WCONTINUED | WUNTRACED);
+
+ if (WIFEXITED(m_forkstatus) && m_forkstatus > 0) {
+ m_log.trace("command: Exited with failed status %d", WEXITSTATUS(m_forkstatus));
+ } else if (WIFEXITED(m_forkstatus)) {
+ m_log.trace("command: Exited with status %d", WEXITSTATUS(m_forkstatus));
+ } else if (WIFSIGNALED(m_forkstatus)) {
+ m_log.trace("command: killed by signal %d", WTERMSIG(m_forkstatus));
+ } else if (WIFSTOPPED(m_forkstatus)) {
+ m_log.trace("command: Stopped by signal %d", WSTOPSIG(m_forkstatus));
+ } else if (WIFCONTINUED(m_forkstatus)) {
+ m_log.trace("command: Continued");
+ } else {
+ break;
+ }
+ } while (!WIFEXITED(m_forkstatus) && !WIFSIGNALED(m_forkstatus));
+
+ return WEXITSTATUS(m_forkstatus);
+}
+
+/**
+ * Get command pid
+ */
+pid_t command<output_policy::IGNORED>::get_pid() {
+ return m_forkpid;
+}
+
+/**
+ * Get command exit status
+ */
+int command<output_policy::IGNORED>::get_exit_status() {
+ return WEXITSTATUS(m_forkstatus);
+}
+
+command<output_policy::REDIRECTED>::command(const polybar::logger& logger, std::string cmd)
+ : command<output_policy::IGNORED>(logger, move(cmd)) {
+ if (pipe(m_stdin) != 0) {
+ throw command_error("Failed to allocate input stream");
+ }
+ if (pipe(m_stdout) != 0) {
+ throw command_error("Failed to allocate output stream");
+ }
+}
+
+command<output_policy::REDIRECTED>::~command() {
+ if (m_stdin[PIPE_READ] > 0) {
+ close(m_stdin[PIPE_READ]);
+ }
+ if (m_stdin[PIPE_WRITE] > 0) {
+ close(m_stdin[PIPE_WRITE]);
+ }
+ if (m_stdout[PIPE_READ] > 0) {
+ close(m_stdout[PIPE_READ]);
+ }
+ if (m_stdout[PIPE_WRITE] > 0) {
+ close(m_stdout[PIPE_WRITE]);
+ }
+}
+
+/**
+ * Execute the command
+ */
+int command<output_policy::REDIRECTED>::exec(bool wait_for_completion, const vector<pair<string, string>>& env) {
+ if ((m_forkpid = fork()) == -1) {
+ throw system_error("Failed to fork process");
+ }
+
+ if (process_util::in_forked_process(m_forkpid)) {
+ if (dup2(m_stdin[PIPE_READ], STDIN_FILENO) == -1) {
+ throw command_error("Failed to redirect stdin in child process");
+ }
+ if (dup2(m_stdout[PIPE_WRITE], STDOUT_FILENO) == -1) {
+ throw command_error("Failed to redirect stdout in child process");
+ }
+ if (dup2(m_stdout[PIPE_WRITE], STDERR_FILENO) == -1) {
+ throw command_error("Failed to redirect stderr in child process");
+ }
+
+ // Close file descriptors that won't be used by the child
+ if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+ if ((m_stdin[PIPE_WRITE] = close(m_stdin[PIPE_WRITE])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+ if ((m_stdout[PIPE_READ] = close(m_stdout[PIPE_READ])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+ if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+
+ setpgid(m_forkpid, 0);
+ process_util::exec_sh(m_cmd.c_str(), env);
+ } else {
+ // Close file descriptors that won't be used by the parent
+ if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+ if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1) {
+ throw command_error("Failed to close fd");
+ }
+
+ if (wait_for_completion) {
+ auto status = wait();
+ m_forkpid = -1;
+ return status;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/**
+ * Tail command output
+ *
+ * @note: This is a blocking call and will not
+ * end until the stream is closed
+ */
+void command<output_policy::REDIRECTED>::tail(std::function<void(string)> cb) {
+ io_util::tail(get_stdout(PIPE_READ), cb);
+}
+
+/**
+ * Read a line from the commands output stream
+ */
+string command<output_policy::REDIRECTED>::readline() {
+ if (!m_stdout_reader) {
+ m_stdout_reader = make_unique<fd_stream<std::istream>>(get_stdout(PIPE_READ), false);
+ }
+
+ string s;
+ std::getline(*m_stdout_reader, s);
+ return s;
+}
+
+/**
+ * Get command output channel
+ */
+int command<output_policy::REDIRECTED>::get_stdout(int c) {
+ return m_stdout[c];
+}
+
+/**
+ * Get command input channel
+ */
+int command<output_policy::REDIRECTED>::get_stdin(int c) {
+ return m_stdin[c];
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/concurrency.hpp"
+
+#include <map>
+
+POLYBAR_NS
+
+namespace concurrency_util {
+ size_t thread_id(const thread::id id) {
+ static size_t idx{1_z};
+ static mutex_wrapper<std::map<thread::id, size_t>> ids;
+ std::lock_guard<decltype(ids)> lock(ids);
+ if (ids.find(id) == ids.end()) {
+ ids[id] = idx++;
+ }
+ return ids[id];
+ }
+} // namespace concurrency_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/env.hpp"
+
+#include <cstdlib>
+#include <cstring>
+#include <utility>
+
+POLYBAR_NS
+
+namespace env_util {
+ bool has(const string& var) {
+ const char* env{std::getenv(var.c_str())};
+ return env != nullptr && std::strlen(env) > 0;
+ }
+
+ string get(const string& var, string fallback) {
+ const char* value{std::getenv(var.c_str())};
+ return value != nullptr ? value : move(fallback);
+ }
+} // namespace env_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/file.hpp"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <streambuf>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+// implementation of file_descriptor {{{
+
+file_descriptor::file_descriptor(const string& path, int flags, bool autoclose) : m_autoclose(autoclose) {
+ if ((m_fd = open(path.c_str(), flags)) == -1) {
+ throw system_error("Failed to open file descriptor");
+ }
+}
+
+file_descriptor::file_descriptor(int fd, bool autoclose) : m_fd(fd), m_autoclose(autoclose) {
+ if (m_fd != -1 && !*this) {
+ throw system_error("Given file descriptor (" + to_string(m_fd) + ") is not valid");
+ }
+}
+
+file_descriptor::~file_descriptor() {
+ if (m_autoclose) {
+ close();
+ }
+}
+
+file_descriptor& file_descriptor::operator=(const int fd) {
+ if (m_autoclose) {
+ close();
+ }
+ m_fd = fd;
+ return *this;
+}
+
+file_descriptor::operator int() {
+ return static_cast<const file_descriptor&>(*this);
+}
+file_descriptor::operator int() const {
+ return m_fd;
+}
+
+file_descriptor::operator bool() {
+ return static_cast<const file_descriptor&>(*this);
+}
+file_descriptor::operator bool() const {
+ errno = 0; // reset since fcntl only changes it on error
+ if ((fcntl(m_fd, F_GETFD) == -1) || errno == EBADF) {
+ errno = EBADF;
+ return false;
+ }
+ return true;
+}
+
+void file_descriptor::close() {
+ if (m_fd != -1 && ::close(m_fd) == -1) {
+ throw system_error("Failed to close file descriptor");
+ }
+ m_fd = -1;
+}
+
+// }}}
+// implementation of file_streambuf {{{
+
+fd_streambuf::~fd_streambuf() {
+ close();
+}
+
+fd_streambuf::operator int() {
+ return static_cast<const fd_streambuf&>(*this);
+}
+fd_streambuf::operator int() const {
+ return m_fd;
+}
+
+void fd_streambuf::open(int fd) {
+ if (m_fd) {
+ close();
+ }
+ m_fd = fd;
+ setg(m_in, m_in + BUFSIZE_IN, m_in + BUFSIZE_IN);
+ setp(m_out, m_out + BUFSIZE_OUT - 1);
+}
+
+void fd_streambuf::close() {
+ if (m_fd) {
+ sync();
+ m_fd = -1;
+ }
+}
+
+int fd_streambuf::sync() {
+ if (pbase() != pptr()) {
+ auto size = pptr() - pbase();
+ auto bytes = write(m_fd, m_out, size);
+ if (bytes > 0) {
+ std::copy(pbase() + bytes, pptr(), pbase());
+ setp(pbase(), epptr());
+ pbump(size - bytes);
+ }
+ }
+ return pptr() != epptr() ? 0 : -1;
+}
+
+int fd_streambuf::overflow(int c) {
+ if (!traits_type::eq_int_type(c, traits_type::eof())) {
+ *pptr() = traits_type::to_char_type(c);
+ pbump(1);
+ }
+ return sync() == -1 ? traits_type::eof() : traits_type::not_eof(c);
+}
+
+int fd_streambuf::underflow() {
+ if (gptr() >= egptr()) {
+ int bytes = ::read(m_fd, m_in, BUFSIZE_IN);
+
+ if (bytes <= 0) {
+ setg(eback(), egptr(), egptr());
+ return traits_type::eof();
+ }
+
+ setg(m_in, m_in, m_in + bytes);
+ }
+
+ return traits_type::to_int_type(*gptr());
+}
+
+// }}}
+
+namespace file_util {
+ /**
+ * Checks if the given file exist
+ *
+ * May also return false if the file status cannot be read
+ *
+ * Sets errno when returning false
+ */
+ bool exists(const string& filename) {
+ struct stat buffer {};
+ return stat(filename.c_str(), &buffer) == 0;
+ }
+
+ /**
+ * Checks if the given path exists and is a file
+ */
+ bool is_file(const string& filename) {
+ struct stat buffer {};
+
+ if (stat(filename.c_str(), &buffer) != 0) {
+ return false;
+ }
+
+ return S_ISREG(buffer.st_mode);
+ }
+
+ /**
+ * Checks if the given path exists and is a file
+ */
+ bool is_dir(const string& filename) {
+ struct stat buffer {};
+
+ if (stat(filename.c_str(), &buffer) != 0) {
+ return false;
+ }
+
+ return S_ISDIR(buffer.st_mode);
+ }
+
+ /**
+ * Picks the first existing file out of given entries
+ */
+ string pick(const vector<string>& filenames) {
+ for (auto&& f : filenames) {
+ if (exists(f)) {
+ return f;
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Gets the contents of the given file
+ */
+ string contents(const string& filename) {
+ try {
+ string contents;
+ string line;
+ std::ifstream in(filename, std::ifstream::in);
+ while (std::getline(in, line)) {
+ contents += line + '\n';
+ }
+ return contents;
+ } catch (const std::ifstream::failure& e) {
+ return "";
+ }
+ }
+
+ /**
+ * Writes the contents of the given file
+ */
+ void write_contents(const string& filename, const string& contents) {
+ std::ofstream out(filename, std::ofstream::out);
+ if (!(out << contents)) {
+ throw std::system_error(errno, std::system_category(), "failed to write to " + filename);
+ }
+ }
+
+ /**
+ * Checks if the given file is a named pipe
+ */
+ bool is_fifo(const string& filename) {
+ struct stat buffer {};
+ return stat(filename.c_str(), &buffer) == 0 && S_ISFIFO(buffer.st_mode);
+ }
+
+ /**
+ * Get glob results using given pattern
+ */
+ vector<string> glob(string pattern) {
+ glob_t result{};
+ vector<string> ret;
+
+ // Manually expand tilde to fix builds using versions of libc
+ // that doesn't provide the GLOB_TILDE flag (musl for example)
+ if (pattern[0] == '~') {
+ pattern.replace(0, 1, env_util::get("HOME"));
+ }
+
+ if (::glob(pattern.c_str(), 0, nullptr, &result) == 0) {
+ for (size_t i = 0_z; i < result.gl_pathc; ++i) {
+ ret.emplace_back(result.gl_pathv[i]);
+ }
+ globfree(&result);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Path expansion
+ *
+ * `relative_to` must be a directory
+ */
+ string expand(const string& path, const string& relative_to) {
+ /*
+ * This doesn't capture all cases for absolute paths but the other cases
+ * (tilde and env variable) have the initial '/' character in their
+ * expansion already and will thus not require adding '/' to the beginning.
+ */
+ bool is_absolute = !path.empty() && (path.at(0) == '/');
+ vector<string> p_exploded = string_util::split(path, '/');
+ for (auto& section : p_exploded) {
+ switch (section[0]) {
+ case '$':
+ section = env_util::get(section.substr(1));
+ break;
+ case '~':
+ section = env_util::get("HOME");
+ break;
+ }
+ }
+ string ret = string_util::join(p_exploded, "/");
+ // Don't add an initial slash for relative paths
+ if (ret[0] != '/' && is_absolute) {
+ ret.insert(0, 1, '/');
+ }
+
+ is_absolute = !ret.empty() && (ret.at(0) == '/');
+
+ if (!is_absolute && !relative_to.empty()) {
+ return relative_to + "/" + ret;
+ }
+ return ret;
+ }
+
+ /*
+ * Search for polybar config and returns the path if found
+ */
+ string get_config_path() {
+ const static string suffix = "/polybar/config";
+
+ vector<string> possible_paths;
+
+ if (env_util::has("XDG_CONFIG_HOME")) {
+ auto path = env_util::get("XDG_CONFIG_HOME") + suffix;
+
+ possible_paths.push_back(path);
+ possible_paths.push_back(path + ".ini");
+ }
+
+ if (env_util::has("HOME")) {
+ auto path = env_util::get("HOME") + "/.config" + suffix;
+
+ possible_paths.push_back(path);
+ possible_paths.push_back(path + ".ini");
+ }
+
+ vector<string> xdg_config_dirs;
+
+ /*
+ *Ref: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ */
+ if (env_util::has("XDG_CONFIG_DIRS")) {
+ xdg_config_dirs = string_util::split(env_util::get("XDG_CONFIG_DIRS"), ':');
+ } else {
+ xdg_config_dirs.push_back("/etc/xdg");
+ }
+
+ for (const string& xdg_config_dir : xdg_config_dirs) {
+ possible_paths.push_back(xdg_config_dir + suffix + ".ini");
+ }
+
+ possible_paths.push_back("/etc" + suffix + ".ini");
+
+ for (const string& p : possible_paths) {
+ if (exists(p)) {
+ return p;
+ }
+ }
+
+ return "";
+ }
+
+ /**
+ * Return a list of file names in a directory.
+ */
+ vector<string> list_files(const string& dirname) {
+ vector<string> files;
+ DIR* dir;
+ if ((dir = opendir(dirname.c_str())) != NULL) {
+ struct dirent* ent;
+ while ((ent = readdir(dir)) != NULL) {
+ // Type can be unknown for filesystems that do not support d_type
+ if ((ent->d_type & DT_REG) ||
+ ((ent->d_type & DT_UNKNOWN) && strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0)) {
+ files.push_back(ent->d_name);
+ }
+ }
+ closedir(dir);
+ return files;
+ }
+ throw system_error("Failed to open directory stream for " + dirname);
+ }
+
+ string dirname(const string& path) {
+ const auto pos = path.find_last_of('/');
+ if (pos != string::npos) {
+ return path.substr(0, pos + 1);
+ }
+
+ return string{};
+ }
+} // namespace file_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/http.hpp"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include <sstream>
+
+#include "errors.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+http_downloader::http_downloader(int connection_timeout) {
+ m_curl = curl_easy_init();
+ curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, "deflate");
+ curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, connection_timeout);
+ curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true);
+ curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, true);
+ curl_easy_setopt(m_curl, CURLOPT_USERAGENT, ("polybar/" + string{APP_VERSION}).c_str());
+ curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, http_downloader::write);
+ curl_easy_setopt(m_curl, CURLOPT_FORBID_REUSE, true);
+}
+
+http_downloader::~http_downloader() {
+ curl_easy_cleanup(m_curl);
+}
+
+string http_downloader::get(const string& url, const string& user, const string& password) {
+ std::stringstream out{};
+ curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &out);
+ if (!user.empty()) {
+ curl_easy_setopt(m_curl, CURLOPT_USERNAME, user.c_str());
+ }
+ if (!password.empty()) {
+ curl_easy_setopt(m_curl, CURLOPT_PASSWORD, password.c_str());
+ }
+
+ auto res = curl_easy_perform(m_curl);
+ if (res != CURLE_OK) {
+ throw application_error(curl_easy_strerror(res), res);
+ }
+
+ return out.str();
+}
+
+long http_downloader::response_code() {
+ long code{0};
+ curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
+ return code;
+}
+
+size_t http_downloader::write(void* p, size_t size, size_t bytes, void* stream) {
+ string data{static_cast<const char*>(p), size * bytes};
+ *(static_cast<std::stringstream*>(stream)) << data << '\n';
+ return size * bytes;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/i3.hpp"
+
+#include <xcb/xcb.h>
+
+#include <i3ipc++/ipc.hpp>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/socket.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+
+POLYBAR_NS
+
+namespace i3_util {
+ /**
+ * Get all workspaces for given output
+ */
+ vector<shared_ptr<workspace_t>> workspaces(const connection_t& conn, const string& output, const bool show_urgent) {
+ vector<shared_ptr<workspace_t>> result;
+ for (auto&& ws : conn.get_workspaces()) {
+ if (output.empty() || ws->output == output || (show_urgent && ws->urgent)) {
+ result.emplace_back(forward<decltype(ws)>(ws));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get currently focused workspace
+ */
+ shared_ptr<workspace_t> focused_workspace(const connection_t& conn) {
+ for (auto&& ws : conn.get_workspaces()) {
+ if (ws->focused) {
+ return ws;
+ }
+ }
+ return nullptr;
+ }
+
+ /**
+ * Get main root window
+ */
+ xcb_window_t root_window(connection& conn) {
+ auto children = conn.query_tree(conn.root()).children();
+ const auto wm_name = [&](xcb_connection_t* conn, xcb_window_t win) -> string {
+ string title;
+ if (!(title = ewmh_util::get_wm_name(win)).empty()) {
+ return title;
+ } else if (!(title = icccm_util::get_wm_name(conn, win)).empty()) {
+ return title;
+ } else {
+ return "";
+ }
+ };
+
+ for (auto it = children.begin(); it != children.end(); it++) {
+ if (wm_name(conn, *it) == "i3") {
+ return *it;
+ }
+ }
+
+ return XCB_NONE;
+ }
+
+ /**
+ * Returns window against which to restack.
+ */
+ restack_util::params get_restack_params(connection& conn) {
+ return {root_window(conn), XCB_STACK_MODE_ABOVE};
+ }
+} // namespace i3_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/inotify.hpp"
+
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/memory.hpp"
+
+POLYBAR_NS
+
+/**
+ * Construct inotify watch
+ */
+inotify_watch::inotify_watch(string path) : m_path(move(path)) {}
+
+/**
+ * Deconstruct inotify watch
+ */
+inotify_watch::~inotify_watch() {
+ if (m_wd != -1) {
+ inotify_rm_watch(m_fd, m_wd);
+ }
+ if (m_fd != -1) {
+ close(m_fd);
+ }
+}
+
+inotify_watch::inotify_watch(inotify_watch&& other) noexcept {
+ std::swap(m_path, other.m_path);
+ std::swap(m_wd, other.m_wd);
+ std::swap(m_fd, other.m_fd);
+ std::swap(m_mask, other.m_mask);
+}
+
+inotify_watch& inotify_watch::operator=(inotify_watch&& other) noexcept {
+ std::swap(m_path, other.m_path);
+ std::swap(m_wd, other.m_wd);
+ std::swap(m_fd, other.m_fd);
+ std::swap(m_mask, other.m_mask);
+ return *this;
+}
+
+/**
+ * Attach inotify watch
+ */
+void inotify_watch::attach(int mask) {
+ if (m_fd == -1 && (m_fd = inotify_init()) == -1) {
+ throw system_error("Failed to allocate inotify fd");
+ }
+ if ((m_wd = inotify_add_watch(m_fd, m_path.c_str(), mask)) == -1) {
+ throw system_error("Failed to attach inotify watch");
+ }
+ m_mask |= mask;
+}
+
+/**
+ * Remove inotify watch
+ */
+void inotify_watch::remove(bool force) {
+ if (inotify_rm_watch(m_fd, m_wd) == -1 && !force) {
+ throw system_error("Failed to remove inotify watch");
+ }
+ m_wd = -1;
+ m_mask = 0;
+}
+
+/**
+ * Poll the inotify fd for events
+ *
+ * @brief A wait_ms of -1 blocks until an event is fired
+ */
+bool inotify_watch::poll(int wait_ms) const {
+ if (m_fd == -1) {
+ return false;
+ }
+
+ struct pollfd fds[1];
+ fds[0].fd = m_fd;
+ fds[0].events = POLLIN;
+
+ ::poll(fds, 1, wait_ms);
+
+ return fds[0].revents & POLLIN;
+}
+
+/**
+ * Get the latest inotify event
+ */
+inotify_event inotify_watch::get_event() const {
+ inotify_event event;
+
+ if (m_fd == -1 || m_wd == -1) {
+ return event;
+ }
+
+ event.is_valid = true;
+
+ char buffer[1024];
+ auto bytes = read(m_fd, buffer, 1024);
+ auto len = 0;
+
+ while (len < bytes) {
+ auto* e = reinterpret_cast<::inotify_event*>(&buffer[len]);
+
+ event.filename = e->len ? e->name : m_path;
+ event.wd = e->wd;
+ event.cookie = e->cookie;
+ event.is_dir = e->mask & IN_ISDIR;
+ event.mask |= e->mask;
+
+ len += sizeof(*e) + e->len;
+ }
+
+ return event;
+}
+
+/**
+ * Get watch file path
+ */
+string inotify_watch::path() const {
+ return m_path;
+}
+
+/**
+ * Get the file descriptor associated with the watch
+ */
+int inotify_watch::get_file_descriptor() const {
+ return m_fd;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/io.hpp"
+
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <iomanip>
+
+#include "errors.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace io_util {
+ void tail(int read_fd, const function<void(string)>& callback) {
+ string line;
+ fd_stream<std::istream> in(read_fd, false);
+ while (std::getline(in, line)) {
+ callback(move(line));
+ }
+ }
+
+ bool poll(int fd, short int events, int timeout_ms) {
+ struct pollfd fds[1];
+ fds[0].fd = fd;
+ fds[0].events = events;
+ ::poll(fds, 1, timeout_ms);
+ return fds[0].revents & events;
+ }
+
+ bool poll_read(int fd, int timeout_ms) {
+ return poll(fd, POLLIN, timeout_ms);
+ }
+} // namespace io_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/process.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace process_util {
+ /**
+ * Check if currently in main process
+ */
+ bool in_parent_process(pid_t pid) {
+ return pid != -1 && pid != 0;
+ }
+
+ /**
+ * Check if currently in subprocess
+ */
+ bool in_forked_process(pid_t pid) {
+ return pid == 0;
+ }
+
+ /**
+ * Redirects all io fds (stdin, stdout, stderr) of the current process to /dev/null.
+ */
+ void redirect_stdio_to_dev_null() {
+ auto redirect = [](int fd_to_redirect) {
+ int fd = open("/dev/null", O_WRONLY);
+ if (fd < 0 || dup2(fd, fd_to_redirect) < 0) {
+ throw system_error("Failed to redirect process output");
+ }
+ close(fd);
+ };
+
+ redirect(STDIN_FILENO);
+ redirect(STDOUT_FILENO);
+ redirect(STDERR_FILENO);
+ }
+
+ /**
+ * Forks a child process and executes the given lambda function in it.
+ *
+ * Processes spawned this way need to be waited on by the caller.
+ */
+ pid_t spawn_async(std::function<void()> const& lambda) {
+ pid_t pid = fork();
+ switch (pid) {
+ case -1:
+ throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+ case 0:
+ // Child
+ setsid();
+ umask(0);
+ redirect_stdio_to_dev_null();
+ lambda();
+ _Exit(0);
+ break;
+ default:
+ return pid;
+ }
+ }
+
+ /**
+ * Forks a child process and completely detaches it.
+ *
+ * In the child process, the given lambda function is executed.
+ * We fork twice so that the first forked process can exit and it's child is
+ * reparented to the init process.
+ *
+ * Ref: https://web.archive.org/web/20120914180018/http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
+ *
+ * Use this if you want to run a command and just forget about it.
+ *
+ * @returns The PID of the child process
+ */
+ void fork_detached(std::function<void()> const& lambda) {
+ pid_t pid = fork();
+ switch (pid) {
+ case -1:
+ throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+ case 0:
+ // Child
+ setsid();
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+ case 0:
+ // Child
+ umask(0);
+ redirect_stdio_to_dev_null();
+ lambda();
+ _Exit(0);
+ }
+
+ _Exit(0);
+ default:
+ /*
+ * The first fork immediately exits and we have to collect its exit
+ * status
+ */
+ wait(pid);
+ }
+ }
+
+ /**
+ * Execute command
+ */
+ void exec(char* cmd, char** args) {
+ if (cmd != nullptr) {
+ execvp(cmd, args);
+ throw system_error("execvp() failed");
+ }
+ }
+
+ /**
+ * Execute command using shell
+ */
+ void exec_sh(const char* cmd, const vector<pair<string, string>>& env) {
+ if (cmd != nullptr) {
+ static const string shell{env_util::get("POLYBAR_SHELL", "/bin/sh")};
+
+ for (const auto& kv_pair : env) {
+ setenv(kv_pair.first.data(), kv_pair.second.data(), 1);
+ }
+
+ execlp(shell.c_str(), shell.c_str(), "-c", cmd, nullptr);
+ throw system_error("execlp() failed");
+ }
+ }
+
+ int wait(pid_t pid) {
+ int forkstatus;
+ do {
+ process_util::wait_for_completion(pid, &forkstatus, WCONTINUED | WUNTRACED);
+ } while (!WIFEXITED(forkstatus) && !WIFSIGNALED(forkstatus));
+
+ return WEXITSTATUS(forkstatus);
+ }
+
+ /**
+ * Wait for child process
+ */
+ pid_t wait_for_completion(pid_t process_id, int* status_addr, int waitflags) {
+ int saved_errno = errno;
+ auto retval = waitpid(process_id, status_addr, waitflags);
+ errno = saved_errno;
+ return retval;
+ }
+
+ /**
+ * Wait for child process
+ */
+ pid_t wait_for_completion(int* status_addr, int waitflags) {
+ return wait_for_completion(-1, status_addr, waitflags);
+ }
+
+ /**
+ * Non-blocking wait
+ *
+ * @see wait_for_completion
+ */
+ pid_t wait_for_completion_nohang(pid_t process_id, int* status) {
+ return wait_for_completion(process_id, status, WNOHANG);
+ }
+
+ /**
+ * Non-blocking wait
+ *
+ * @see wait_for_completion
+ */
+ pid_t wait_for_completion_nohang(int* status) {
+ return wait_for_completion_nohang(-1, status);
+ }
+
+ /**
+ * Non-blocking wait
+ *
+ * @see wait_for_completion
+ */
+ pid_t wait_for_completion_nohang() {
+ int status = 0;
+ return wait_for_completion_nohang(-1, &status);
+ }
+
+ /**
+ * Non-blocking wait call which returns pid of any child process
+ *
+ * @see wait_for_completion
+ */
+ bool notify_childprocess() {
+ return wait_for_completion_nohang() > 0;
+ }
+} // namespace process_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/restack.hpp"
+
+POLYBAR_NS
+
+namespace restack_util {
+
+/**
+ * Restacks the given window relative to a given sibling with some stack order (above, below)
+ *
+ * Both windows need to be siblings (have the same parent window).
+ *
+ * @param win The window to restack
+ * @param sibling The window relative to which restacking happens
+ * @param stack_mode The restacking mode (above, below)
+ * @throw Some xpp error if restacking fails
+ */
+void restack_relative(connection& conn, xcb_window_t win, xcb_window_t sibling, xcb_stack_mode_t stack_mode) {
+ const unsigned int value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
+ const std::array<uint32_t, 2> value_list = {sibling, stack_mode};
+ conn.configure_window_checked(win, value_mask, value_list.data());
+}
+
+string stack_mode_to_string(xcb_stack_mode_t mode) {
+ switch (mode) {
+ case XCB_STACK_MODE_ABOVE:
+ return "ABOVE";
+ case XCB_STACK_MODE_BELOW:
+ return "BELOW";
+ case XCB_STACK_MODE_TOP_IF:
+ return "TOP_IF";
+ case XCB_STACK_MODE_BOTTOM_IF:
+ return "BOTTOM_IF";
+ case XCB_STACK_MODE_OPPOSITE:
+ return "OPPOSITE";
+ }
+
+ return "UNKNOWN";
+}
+
+/**
+ * @return true iff the two given windows are sibings (are different windows and have same parent).
+ */
+bool are_siblings(connection& conn, xcb_window_t win, xcb_window_t sibling) {
+ if (win == XCB_NONE || sibling == XCB_NONE || win == sibling) {
+ return false;
+ }
+
+ auto win_tree = conn.query_tree(win);
+ auto sibling_tree = conn.query_tree(sibling);
+ return win_tree->parent == sibling_tree->parent;
+}
+
+/**
+ * "bottom" restack strategy.
+ *
+ * Moves the bar window to the bottom of the window stack
+ */
+params get_bottom_params(connection& conn, xcb_window_t bar_window) {
+ auto children = conn.query_tree(conn.root()).children();
+ if (children.begin() != children.end() && *children.begin() != bar_window) {
+ return {*children.begin(), XCB_STACK_MODE_BELOW};
+ }
+
+ return NONE_PARAMS;
+}
+
+/**
+ * EWMH restack strategy.
+ *
+ * Moves the bar window above the WM meta window (_NET_SUPPORTING_WM_CHECK).
+ * This window is generally towards the bottom of the window stack, but still above other windows that could interfere.
+ *
+ * @see ewmh_util::get_ewmh_meta_window
+ */
+params get_ewmh_params(connection& conn) {
+ if (auto meta_window = ewmh_util::get_ewmh_meta_window(conn.root())) {
+ return {meta_window, XCB_STACK_MODE_ABOVE};
+ }
+
+ return NONE_PARAMS;
+}
+
+/**
+ * Generic restack stratgey.
+ *
+ * Tries to provide the best WM-agnostic restacking.
+ *
+ * Currently tries to the following stratgies in order:
+ * * ewmh
+ * * bottom
+ */
+params get_generic_params(connection& conn, xcb_window_t bar_window) {
+ auto ewmh_params = get_ewmh_params(conn);
+
+ if (are_siblings(conn, bar_window, ewmh_params.first)) {
+ return ewmh_params;
+ }
+
+ auto bottom_params = get_bottom_params(conn, bar_window);
+
+ if (are_siblings(conn, bar_window, bottom_params.first)) {
+ return bottom_params;
+ }
+
+ return NONE_PARAMS;
+}
+} // namespace restack_util
+
+POLYBAR_NS_END
--- /dev/null
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/file.hpp"
+#include "utils/mixins.hpp"
+#include "utils/socket.hpp"
+
+POLYBAR_NS
+
+using std::snprintf;
+using std::strlen;
+
+namespace socket_util {
+ /**
+ * Constructor: establishing socket connection
+ */
+ unix_connection::unix_connection(string&& path) : m_socketpath(path) {
+ struct sockaddr_un socket_addr {};
+ socket_addr.sun_family = AF_UNIX;
+
+ if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ throw system_error("Failed to open unix connection");
+ }
+
+ snprintf(socket_addr.sun_path, sizeof(socket_addr.sun_path), "%s", m_socketpath.c_str());
+ auto len = sizeof(socket_addr);
+
+ if (connect(m_fd, reinterpret_cast<struct sockaddr*>(&socket_addr), len) == -1) {
+ throw system_error("Failed to connect to socket");
+ }
+ }
+
+ /**
+ * Destructor: closes file descriptor
+ */
+ unix_connection::~unix_connection() noexcept {
+ if (m_fd != -1) {
+ close(m_fd);
+ }
+ }
+
+ /**
+ * Close reading end of connection
+ */
+ int unix_connection::disconnect() {
+ return shutdown(m_fd, SHUT_RD);
+ }
+
+ /**
+ * Transmit fixed size data
+ */
+ ssize_t unix_connection::send(const void* data, size_t len, int flags) {
+ ssize_t bytes_sent = 0;
+
+ if ((bytes_sent = ::send(m_fd, data, len, flags)) == -1) {
+ throw system_error("Failed to transmit data");
+ }
+
+ return bytes_sent;
+ }
+
+ /**
+ * Transmit string data
+ */
+ ssize_t unix_connection::send(const string& data, int flags) {
+ return send(data.c_str(), data.length(), flags);
+ }
+
+ /**
+ * Receive data
+ */
+ string unix_connection::receive(const ssize_t receive_bytes, ssize_t* bytes_received, int flags) {
+ char buffer[BUFSIZ];
+
+ if ((*bytes_received = ::recv(m_fd, buffer, receive_bytes, flags)) == -1) {
+ throw system_error("Failed to receive data");
+ } else {
+ buffer[*bytes_received] = 0;
+ }
+
+ return string{buffer};
+ }
+
+ /**
+ * @see receive
+ */
+ string unix_connection::receive(const ssize_t receive_bytes, int flags) {
+ ssize_t bytes{0};
+ return receive(receive_bytes, &bytes, flags);
+ }
+
+ /**
+ * Peek at the specified number of bytes
+ */
+ bool unix_connection::peek(const size_t peek_bytes) {
+ ssize_t bytes_seen{0};
+ receive(peek_bytes, &bytes_seen, MSG_PEEK);
+ return bytes_seen > 0;
+ }
+
+ /**
+ * Poll file descriptor for to check for available data
+ */
+ bool unix_connection::poll(short int events, int timeout_ms) {
+ struct pollfd fds[1];
+ fds[0].fd = m_fd;
+ fds[0].events = events;
+
+ if (::poll(fds, 1, timeout_ms) == -1) {
+ throw system_error("Failed to poll file descriptor");
+ }
+
+ return fds[0].revents & events;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/string.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <iomanip>
+#include <sstream>
+#include <utility>
+
+POLYBAR_NS
+
+namespace string_util {
+
+/**
+ * Prefixes for the leading byte in a UTF8 codepoint
+ */
+static constexpr uint8_t UTF8_LEADING1_PREFIX = 0b00000000;
+static constexpr uint8_t UTF8_LEADING2_PREFIX = 0b11000000;
+static constexpr uint8_t UTF8_LEADING3_PREFIX = 0b11100000;
+static constexpr uint8_t UTF8_LEADING4_PREFIX = 0b11110000;
+
+/**
+ * Masks to extract the prefix from the leading byte in a UTF8 codepoint
+ */
+static constexpr uint8_t UTF8_LEADING1_MASK = 0b10000000;
+static constexpr uint8_t UTF8_LEADING2_MASK = 0b11100000;
+static constexpr uint8_t UTF8_LEADING3_MASK = 0b11110000;
+static constexpr uint8_t UTF8_LEADING4_MASK = 0b11111000;
+
+/**
+ * Prefix for UTF8 continuation bytes
+ */
+static constexpr uint8_t UTF8_CONTINUATION_PREFIX = 0b10000000;
+/**
+ * Mask to extract the UTF8 continuation byte prefix
+ */
+static constexpr uint8_t UTF8_CONTINUATION_MASK = 0b11000000;
+
+/**
+ * Check if haystack contains needle
+ */
+bool contains(const string& haystack, const string& needle) {
+ return haystack.find(needle) != string::npos;
+}
+
+bool ends_with(const string& haystack, const string& suffix) {
+ if (haystack.length() < suffix.length()) {
+ return false;
+ }
+
+ return haystack.compare(haystack.length() - suffix.length(), suffix.length(), suffix) == 0;
+}
+
+/**
+ * Check if haystack contains needle ignoring case
+ */
+bool contains_ignore_case(const string& haystack, const string& needle) {
+ return lower(haystack).find(lower(needle)) != string::npos;
+}
+
+/**
+ * Convert string to uppercase
+ */
+string upper(const string& s) {
+ string str(s);
+ for (auto& c : str) {
+ c = toupper(c);
+ }
+ return str;
+}
+
+/**
+ * Convert string to lowercase
+ */
+string lower(const string& s) {
+ string str(s);
+ for (auto& c : str) {
+ c = tolower(c);
+ }
+ return str;
+}
+
+/**
+ * Test lower case equality
+ */
+bool compare(const string& s1, const string& s2) {
+ return lower(s1) == lower(s2);
+}
+
+/**
+ * Replace first occurrence of needle in haystack
+ */
+string replace(const string& haystack, const string& needle, const string& replacement, size_t start, size_t end) {
+ string str(haystack);
+ string::size_type pos;
+
+ if (needle != replacement && (pos = str.find(needle, start)) != string::npos) {
+ if (end == string::npos || pos < end) {
+ str = str.replace(pos, needle.length(), replacement);
+ }
+ }
+
+ return str;
+}
+
+/**
+ * Replace all occurrences of needle in haystack
+ */
+string replace_all(const string& haystack, const string& needle, const string& replacement, size_t start, size_t end) {
+ string result{haystack};
+ string::size_type pos;
+ while ((pos = result.find(needle, start)) != string::npos && pos < result.length() &&
+ (end == string::npos || pos + needle.length() <= end)) {
+ result.replace(pos, needle.length(), replacement);
+ start = pos + replacement.length();
+ }
+ return result;
+}
+
+/**
+ * Replace all consecutive occurrences of needle in haystack
+ */
+string squeeze(const string& haystack, char needle) {
+ string result = haystack;
+ while (result.find({needle, needle}) != string::npos) {
+ result = replace_all(result, {needle, needle}, {needle});
+ }
+ return result;
+}
+
+/**
+ * Remove all occurrences of needle in haystack
+ */
+string strip(const string& haystack, char needle) {
+ string str(haystack);
+ string::size_type pos;
+ while ((pos = str.find(needle)) != string::npos) {
+ str.erase(pos, 1);
+ }
+ return str;
+}
+
+/**
+ * Remove trailing newline
+ */
+string strip_trailing_newline(const string& haystack) {
+ string str(haystack);
+ if (str[str.length() - 1] == '\n') {
+ str.erase(str.length() - 1, 1);
+ }
+ return str;
+}
+
+/**
+ * Trims all characters that match pred from the left
+ */
+string ltrim(string value, function<bool(char)> pred) {
+ value.erase(value.begin(), find_if(value.begin(), value.end(), std::not_fn(pred)));
+ return value;
+}
+
+/**
+ * Trims all characters that match pred from the right
+ */
+string rtrim(string value, function<bool(char)> pred) {
+ value.erase(find_if(value.rbegin(), value.rend(), std::not_fn(pred)).base(), value.end());
+ return value;
+}
+
+/**
+ * Trims all characters that match pred from both sides
+ */
+string trim(string value, function<bool(char)> pred) {
+ return ltrim(rtrim(move(value), pred), pred);
+}
+
+/**
+ * Remove needle from the start of the string
+ */
+string ltrim(string&& value, const char& needle) {
+ if (value.empty()) {
+ return "";
+ }
+ while (*value.begin() == needle) {
+ value.erase(0, 1);
+ }
+ return forward<string>(value);
+}
+
+/**
+ * Remove needle from the end of the string
+ */
+string rtrim(string&& value, const char& needle) {
+ if (value.empty()) {
+ return "";
+ }
+ while (*(value.end() - 1) == needle) {
+ value.erase(value.length() - 1, 1);
+ }
+ return forward<string>(value);
+}
+
+/**
+ * Remove needle from the start and end of the string
+ */
+string trim(string&& value, const char& needle) {
+ if (value.empty()) {
+ return "";
+ }
+ return rtrim(ltrim(forward<string>(value), needle), needle);
+}
+
+/**
+ * Counts the number of codepoints in a utf8 encoded string.
+ */
+size_t char_len(const string& value) {
+ // utf-8 bytes of the form 10xxxxxx are continuation bytes, so we
+ // simply count the number of bytes not of this form.
+ return std::count_if(
+ value.begin(), value.end(), [](char c) { return (c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_PREFIX; });
+}
+
+/**
+ * Truncates a utf8 string at len number of codepoints. This isn't 100%
+ * matching the user-perceived character count, but it should be close
+ * enough and avoids having to pull in something like ICU to count actual
+ * grapheme clusters.
+ */
+string utf8_truncate(string&& value, size_t len) {
+ if (value.empty()) {
+ return "";
+ }
+
+ // utf-8 bytes of the form 10xxxxxx are continuation bytes, so we
+ // simply jump forward to bytes not of that form and truncate starting
+ // at that byte if we've counted too many codepoints
+ auto it = value.begin();
+ auto end = value.end();
+ for (size_t i = 0; i < len; ++i) {
+ if (it == end)
+ break;
+ ++it;
+ it = std::find_if(it, end, [](char c) { return (c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_PREFIX; });
+ }
+ value.erase(it, end);
+
+ return forward<string>(value);
+}
+
+/**
+ * Given a leading byte of a UTF8 codepoint calculates the number of bytes taken up by the codepoint.
+ *
+ * @returns {len, result} The codepoint is len bytes and result contains the codepoint bits held in the leading byte.
+ */
+static pair<int, uint32_t> utf8_get_len(uint8_t leading) {
+ if ((leading & UTF8_LEADING1_MASK) == UTF8_LEADING1_PREFIX) {
+ return {1, leading & ~UTF8_LEADING1_MASK};
+ } else if ((leading & UTF8_LEADING2_MASK) == UTF8_LEADING2_PREFIX) {
+ return {2, leading & ~UTF8_LEADING2_MASK};
+ } else if ((leading & UTF8_LEADING3_MASK) == UTF8_LEADING3_PREFIX) {
+ return {3, leading & ~UTF8_LEADING3_MASK};
+ } else if ((leading & UTF8_LEADING4_MASK) == UTF8_LEADING4_PREFIX) {
+ return {4, leading & ~UTF8_LEADING4_MASK};
+ } else {
+ return {-1, 0};
+ }
+}
+
+/**
+ * @brief Create a list of UCS-4 codepoint from a utf-8 encoded string
+ *
+ * If invalid utf8 characters are encountered they are skipped until the next valid codepoint and the function will
+ * eventually return false.
+ *
+ * The result_list is always populated with all valid utf8 codepoints.
+ *
+ * @return Whether the string is completely valid utf8
+ */
+bool utf8_to_ucs4(const string& src, unicode_charlist& result_list) {
+ result_list.reserve(src.size());
+ bool has_errors = false;
+ const auto* begin = reinterpret_cast<const uint8_t*>(src.c_str());
+
+ const auto* current = begin;
+ while (*current) {
+ // Number of bytes taken up by this codepoint and the bits contained in the leading byte.
+ auto [len, result] = utf8_get_len(*current);
+ auto offset = current - begin;
+
+ /*
+ * Invalid lengths, this byte is not a valid leading byte.
+ * Skip it.
+ */
+ if (len <= 0 || len > 4) {
+ has_errors = true;
+ current++;
+ continue;
+ }
+
+ const uint8_t* next = current + 1;
+ for (; ((*next & UTF8_CONTINUATION_MASK) == UTF8_CONTINUATION_PREFIX) && (next - current < len); next++) {
+ result = result << 6;
+ result |= *next & ~UTF8_CONTINUATION_MASK;
+ }
+
+ auto actual_len = next - current;
+ current = next;
+
+ if (actual_len != len) {
+ has_errors = true;
+ continue;
+ }
+
+ result_list.push_back(unicode_character{result, static_cast<int>(offset), static_cast<int>(actual_len)});
+ current = next;
+ }
+
+ return !has_errors;
+}
+
+/**
+ * @brief Convert a UCS-4 codepoint to a utf-8 encoded string
+ */
+size_t ucs4_to_utf8(std::array<char, 5>& utf8, uint32_t ucs) {
+ if (ucs <= 0x7f) {
+ utf8[0] = ucs;
+ return 1;
+ } else if (ucs <= 0x07ff) {
+ utf8[0] = ((ucs >> 6) & ~UTF8_LEADING2_MASK) | UTF8_LEADING2_PREFIX;
+ utf8[1] = (ucs & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ return 2;
+ } else if (ucs <= 0xffff) {
+ utf8[0] = ((ucs >> 12) & ~UTF8_LEADING3_MASK) | UTF8_LEADING3_PREFIX;
+ utf8[1] = ((ucs >> 6) & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ utf8[2] = (ucs & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ return 3;
+ } else if (ucs <= 0x10ffff) {
+ utf8[0] = ((ucs >> 18) & ~UTF8_LEADING4_MASK) | UTF8_LEADING4_PREFIX;
+ utf8[1] = ((ucs >> 12) & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ utf8[2] = ((ucs >> 6) & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ utf8[3] = (ucs & ~UTF8_CONTINUATION_MASK) | UTF8_CONTINUATION_PREFIX;
+ return 4;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Join all strings in vector into a single string separated by delim
+ */
+string join(const vector<string>& strs, const string& delim) {
+ string str;
+ for (auto& s : strs) {
+ str += (str.empty() ? "" : delim) + s;
+ }
+ return str;
+}
+
+/**
+ * Explode string by delim, ignore empty tokens
+ */
+vector<string> split(const string& s, char delim) {
+ std::string::size_type pos = 0;
+ std::vector<std::string> result;
+
+ while ((pos = s.find_first_not_of(delim, pos)) != std::string::npos) {
+ auto nextpos = s.find_first_of(delim, pos);
+ result.emplace_back(s.substr(pos, nextpos - pos));
+ pos = nextpos;
+ }
+
+ return result;
+}
+
+/**
+ * Explode string by delim, include empty tokens
+ */
+std::vector<std::string> tokenize(const string& str, char delimiters) {
+ std::vector<std::string> tokens;
+ std::string::size_type lastPos = 0;
+ auto pos = str.find_first_of(delimiters, lastPos);
+
+ while (pos != std::string::npos && lastPos != std::string::npos) {
+ tokens.emplace_back(str.substr(lastPos, pos - lastPos));
+
+ lastPos = pos + 1;
+ pos = str.find_first_of(delimiters, lastPos);
+ }
+
+ tokens.emplace_back(str.substr(lastPos, pos - lastPos));
+ return tokens;
+}
+
+/**
+ * Find the nth occurrence of needle in haystack starting from pos
+ */
+size_t find_nth(const string& haystack, size_t pos, const string& needle, size_t nth) {
+ size_t found_pos = haystack.find(needle, pos);
+ if (1 == nth || string::npos == found_pos) {
+ return found_pos;
+ }
+ return find_nth(haystack, found_pos + 1, needle, nth - 1);
+}
+
+/**
+ * Create a floating point string
+ */
+string floating_point(double value, size_t precision, bool fixed, const string& locale) {
+ std::stringstream ss;
+ ss.imbue(!locale.empty() ? std::locale(locale.c_str()) : std::locale::classic());
+ ss << std::fixed << std::setprecision(precision) << value;
+ return fixed ? ss.str() : replace(ss.str(), ".00", "");
+}
+
+/**
+ * Create a MiB filesize string
+ */
+string filesize_mib(unsigned long long kibibytes, size_t precision, const string& locale) {
+ return floating_point(kibibytes / 1024.0, precision, true, locale) + " MiB";
+}
+
+/**
+ * Create a GiB filesize string
+ */
+string filesize_gib(unsigned long long kibibytes, size_t precision, const string& locale) {
+ return floating_point(kibibytes / 1024.0 / 1024.0, precision, true, locale) + " GiB";
+}
+
+/**
+ * Create a GiB string, if the value in GiB is >= 1.0. Otherwise, create a MiB string.
+ */
+string filesize_gib_mib(
+ unsigned long long kibibytes, size_t precision_mib, size_t precision_gib, const string& locale) {
+ if (kibibytes < 1024 * 1024) {
+ return filesize_mib(kibibytes, precision_mib, locale);
+ } else {
+ return filesize_gib(kibibytes, precision_gib, locale);
+ }
+}
+
+/**
+ * Create a filesize string by converting given bytes to highest unit possible
+ */
+string filesize(unsigned long long bytes, size_t precision, bool fixed, const string& locale) {
+ vector<string> suffixes{"TB", "GB", "MB", "KB"};
+ string suffix{"B"};
+ double value = bytes;
+ while (!suffixes.empty() && value >= 1024.0) {
+ suffix = suffixes.back();
+ suffixes.pop_back();
+ value /= 1024.0;
+ }
+ return floating_point(value, precision, fixed, locale) + " " + suffix;
+}
+
+/**
+ * Compute string hash
+ */
+hash_type hash(const string& src) {
+ return std::hash<string>()(src);
+}
+} // namespace string_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "utils/units.hpp"
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace units_utils {
+
+ /**
+ * Converts points to pixels under the given DPI (PPI).
+ *
+ * 1 pt = 1/72in, so point / 72 * DPI = #pixels
+ */
+ int point_to_pixel(double point, double dpi) {
+ return dpi * point / 72.0;
+ }
+
+ /**
+ * Converts an extent value to a pixel value according to the given DPI (if needed).
+ */
+ int extent_to_pixel(const extent_val size, double dpi) {
+ if (size.type == extent_type::PIXEL) {
+ return size.value;
+ }
+
+ return point_to_pixel(size.value, dpi);
+ }
+
+ /**
+ * Same as extent_to_pixel but is capped below at 0 pixels.
+ */
+ unsigned extent_to_pixel_nonnegative(const extent_val size, double dpi) {
+ return std::max(0, extent_to_pixel(size, dpi));
+ }
+
+ /**
+ * Converts a percentage with offset into pixels
+ */
+ int percentage_with_offset_to_pixel(percentage_with_offset g_format, double max, double dpi) {
+ int offset_pixel = extent_to_pixel(g_format.offset, dpi);
+
+ return static_cast<int>(math_util::percentage_to_value<double, double>(g_format.percentage, max) + offset_pixel);
+ }
+
+ /**
+ * Same as percentage_with_offset_to_pixel but capped below at 0 pixels
+ */
+ unsigned percentage_with_offset_to_pixel_nonnegative(percentage_with_offset g_format, double max, double dpi) {
+ return std::max<int>(0, percentage_with_offset_to_pixel(g_format, max, dpi));
+ }
+
+ extent_type parse_extent_unit(const string& str) {
+ if (!str.empty()) {
+ if (str == "px") {
+ return extent_type::PIXEL;
+ } else if (str == "pt") {
+ return extent_type::POINT;
+ } else {
+ throw std::runtime_error("Unrecognized unit '" + str + "'");
+ }
+ } else {
+ return extent_type::PIXEL;
+ }
+ }
+
+ extent_val parse_extent(const string& str) {
+ size_t pos;
+ auto size_value = std::stof(str, &pos);
+
+ string unit = string_util::trim(str.substr(pos));
+ extent_type type = parse_extent_unit(unit);
+
+ // Pixel values should be integers
+ if (type == extent_type::PIXEL) {
+ size_value = std::trunc(size_value);
+ }
+
+ return {type, size_value};
+ }
+
+ string extent_to_string(extent_val extent) {
+ std::stringstream ss;
+
+ switch (extent.type) {
+ case extent_type::POINT:
+ ss << extent.value << "pt";
+ break;
+ case extent_type::PIXEL:
+ ss << static_cast<int>(extent.value) << "px";
+ break;
+ }
+
+ return ss.str();
+ }
+
+ spacing_type parse_spacing_unit(const string& str) {
+ if (!str.empty()) {
+ if (str == "px") {
+ return spacing_type::PIXEL;
+ } else if (str == "pt") {
+ return spacing_type::POINT;
+ } else {
+ throw std::runtime_error("Unrecognized unit '" + str + "'");
+ }
+ } else {
+ return spacing_type::SPACE;
+ }
+ }
+
+ spacing_val parse_spacing(const string& str) {
+ size_t pos;
+ auto size_value = std::stof(str, &pos);
+
+ if (size_value < 0) {
+ throw runtime_error(sstream() << "value '" << str << "' must not be negative");
+ }
+
+ spacing_type type;
+
+ string unit = string_util::trim(str.substr(pos));
+ if (!unit.empty()) {
+ if (unit == "px") {
+ type = spacing_type::PIXEL;
+ size_value = std::trunc(size_value);
+ } else if (unit == "pt") {
+ type = spacing_type::POINT;
+ } else {
+ throw runtime_error("Unrecognized unit '" + unit + "'");
+ }
+ } else {
+ type = spacing_type::SPACE;
+ size_value = std::trunc(size_value);
+ }
+
+ return {type, size_value};
+ }
+
+ /**
+ * Creates an extent from a spacing value.
+ *
+ * @param spacing Value to convert, must not be SPACE
+ */
+ extent_val spacing_to_extent(spacing_val spacing) {
+ extent_type t;
+
+ switch (spacing.type) {
+ case spacing_type::POINT:
+ t = extent_type::POINT;
+ break;
+ case spacing_type::PIXEL:
+ t = extent_type::PIXEL;
+ break;
+ default:
+ throw std::runtime_error("spacing_to_extent: Illegal type: " + to_string(to_integral(spacing.type)));
+ }
+
+ return {t, spacing.value};
+ }
+
+} // namespace units_utils
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/atoms.hpp"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_atom.h>
+
+xcb_atom_t _NET_SUPPORTED;
+xcb_atom_t _NET_CURRENT_DESKTOP;
+xcb_atom_t _NET_ACTIVE_WINDOW;
+xcb_atom_t _NET_WM_NAME;
+xcb_atom_t _NET_WM_DESKTOP;
+xcb_atom_t _NET_WM_VISIBLE_NAME;
+xcb_atom_t _NET_WM_WINDOW_TYPE;
+xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
+xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
+xcb_atom_t _NET_WM_PID;
+xcb_atom_t _NET_WM_STATE;
+xcb_atom_t _NET_WM_STATE_STICKY;
+xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
+xcb_atom_t _NET_WM_STATE_ABOVE;
+xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
+xcb_atom_t _NET_WM_STRUT;
+xcb_atom_t _NET_WM_STRUT_PARTIAL;
+xcb_atom_t WM_PROTOCOLS;
+xcb_atom_t WM_DELETE_WINDOW;
+xcb_atom_t _XEMBED;
+xcb_atom_t _XEMBED_INFO;
+xcb_atom_t MANAGER;
+xcb_atom_t WM_STATE;
+xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
+xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
+xcb_atom_t _NET_SYSTEM_TRAY_VISUAL;
+xcb_atom_t _NET_SYSTEM_TRAY_COLORS;
+xcb_atom_t WM_TAKE_FOCUS;
+xcb_atom_t Backlight;
+xcb_atom_t BACKLIGHT;
+xcb_atom_t _XROOTPMAP_ID;
+xcb_atom_t _XSETROOT_ID;
+xcb_atom_t ESETROOT_PMAP_ID;
+xcb_atom_t _COMPTON_SHADOW;
+xcb_atom_t _NET_WM_WINDOW_OPACITY;
+xcb_atom_t WM_HINTS;
+xcb_atom_t WM_NAME;
+xcb_atom_t WM_CLASS;
+
+// clang-format off
+std::array<cached_atom, 38> ATOMS = {{
+ {"_NET_SUPPORTED", _NET_SUPPORTED},
+ {"_NET_CURRENT_DESKTOP", _NET_CURRENT_DESKTOP},
+ {"_NET_ACTIVE_WINDOW", _NET_ACTIVE_WINDOW},
+ {"_NET_WM_NAME", _NET_WM_NAME},
+ {"_NET_WM_DESKTOP", _NET_WM_DESKTOP},
+ {"_NET_WM_VISIBLE_NAME", _NET_WM_VISIBLE_NAME},
+ {"_NET_WM_WINDOW_TYPE", _NET_WM_WINDOW_TYPE},
+ {"_NET_WM_WINDOW_TYPE_DOCK", _NET_WM_WINDOW_TYPE_DOCK},
+ {"_NET_WM_WINDOW_TYPE_NORMAL", _NET_WM_WINDOW_TYPE_NORMAL},
+ {"_NET_WM_PID", _NET_WM_PID},
+ {"_NET_WM_STATE", _NET_WM_STATE},
+ {"_NET_WM_STATE_STICKY", _NET_WM_STATE_STICKY},
+ {"_NET_WM_STATE_SKIP_TASKBAR", _NET_WM_STATE_SKIP_TASKBAR},
+ {"_NET_WM_STATE_ABOVE", _NET_WM_STATE_ABOVE},
+ {"_NET_WM_STATE_MAXIMIZED_VERT", _NET_WM_STATE_MAXIMIZED_VERT},
+ {"_NET_WM_STRUT", _NET_WM_STRUT},
+ {"_NET_WM_STRUT_PARTIAL", _NET_WM_STRUT_PARTIAL},
+ {"WM_PROTOCOLS", WM_PROTOCOLS},
+ {"WM_DELETE_WINDOW", WM_DELETE_WINDOW},
+ {"_XEMBED", _XEMBED},
+ {"_XEMBED_INFO", _XEMBED_INFO},
+ {"MANAGER", MANAGER},
+ {"WM_STATE", WM_STATE},
+ {"_NET_SYSTEM_TRAY_OPCODE", _NET_SYSTEM_TRAY_OPCODE},
+ {"_NET_SYSTEM_TRAY_ORIENTATION", _NET_SYSTEM_TRAY_ORIENTATION},
+ {"_NET_SYSTEM_TRAY_VISUAL", _NET_SYSTEM_TRAY_VISUAL},
+ {"_NET_SYSTEM_TRAY_COLORS", _NET_SYSTEM_TRAY_COLORS},
+ {"WM_TAKE_FOCUS", WM_TAKE_FOCUS},
+ {"Backlight", Backlight},
+ {"BACKLIGHT", BACKLIGHT},
+ {"_XROOTPMAP_ID", _XROOTPMAP_ID},
+ {"_XSETROOT_ID", _XSETROOT_ID},
+ {"ESETROOT_PMAP_ID", ESETROOT_PMAP_ID},
+ {"_COMPTON_SHADOW", _COMPTON_SHADOW},
+ {"_NET_WM_WINDOW_OPACITY", _NET_WM_WINDOW_OPACITY},
+ {"WM_HINTS", WM_HINTS},
+ {"WM_NAME", WM_NAME},
+ {"WM_CLASS", WM_CLASS},
+}};
+// clang-format on
--- /dev/null
+#include "x11/background_manager.hpp"
+
+#include <cassert>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "events/signal.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+background_manager& background_manager::make() {
+ return *factory_util::singleton<background_manager>(connection::make(), signal_emitter::make(), logger::make());
+}
+
+background_manager::background_manager(connection& conn, signal_emitter& sig, const logger& log)
+ : m_connection(conn), m_sig(sig), m_log(log) {
+ m_sig.attach(this);
+}
+
+background_manager::~background_manager() {
+ m_sig.detach(this);
+ free_resources();
+}
+
+std::shared_ptr<bg_slice> background_manager::observe(xcb_rectangle_t rect, xcb_window_t window) {
+ // allocate a slice
+ auto slice = std::shared_ptr<bg_slice>(new bg_slice(m_connection, m_log, rect, window));
+
+ // if the slice is empty, don't add to slices
+ if (slice->m_rect.width == 0 || slice->m_rect.height == 0) {
+ return slice;
+ }
+
+ activate();
+
+ m_slices.push_back(slice);
+
+ update_slice(*slice);
+ return slice;
+}
+
+void background_manager::activate() {
+ attach();
+}
+
+void background_manager::deactivate() {
+ detach();
+ free_resources();
+}
+
+/**
+ * Attaches a listener to listen for background changes on the root window.
+ */
+void background_manager::attach() {
+ if (!m_attached) {
+ // make sure that we receive a notification when the background changes
+ m_connection.ensure_event_mask(m_connection.root(), XCB_EVENT_MASK_PROPERTY_CHANGE);
+ m_connection.flush();
+ m_connection.attach_sink(this, SINK_PRIORITY_SCREEN);
+ m_attached = true;
+ }
+}
+
+/**
+ * Stops listening for background changes
+ */
+void background_manager::detach() {
+ if (m_attached) {
+ m_connection.detach_sink(this, SINK_PRIORITY_SCREEN);
+ m_attached = false;
+ }
+}
+
+void background_manager::free_resources() {
+ clear_pixmap();
+}
+
+/**
+ * Changes required when the background may have changed.
+ *
+ * 1. Delete cached pixmaps
+ * 2. Update all slices (loads new pixmaps on demand)
+ * 3. Notify about new background
+ */
+void background_manager::on_background_change() {
+ clear_pixmap();
+ m_pixmap_load_failed = false;
+
+ for (auto it = m_slices.begin(); it != m_slices.end();) {
+ auto slice = it->lock();
+ if (!slice) {
+ it = m_slices.erase(it);
+ continue;
+ }
+
+ update_slice(*slice);
+ it++;
+ }
+
+ // if there are no active slices, deactivate
+ if (m_slices.empty()) {
+ m_log.trace("background_manager: deactivating because there are no slices to observe");
+ deactivate();
+ return;
+ }
+
+ m_sig.emit(signals::ui::update_background());
+}
+
+void background_manager::update_slice(bg_slice& slice) {
+ ensure_pixmap();
+
+ if (has_pixmap()) {
+ slice.copy(m_pixmap, m_pixmap_depth, m_pixmap_geom, m_visual);
+ } else {
+ slice.clear();
+ }
+}
+
+void background_manager::handle(const evt::property_notify& evt) {
+ if (evt->atom == _XROOTPMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID) {
+ m_log.trace("background_manager: root pixmap change");
+ on_background_change();
+ }
+}
+
+bool background_manager::on(const signals::ui::update_geometry&) {
+ m_log.trace("background_manager: update_geometry");
+ on_background_change();
+ return false;
+}
+
+bool background_manager::has_pixmap() const {
+ return m_pixmap != XCB_NONE;
+}
+
+void background_manager::ensure_pixmap() {
+ // Only try to load the root pixmap if we haven't already loaded it and the previous load didn't fail.
+ if (!has_pixmap() && !m_pixmap_load_failed) {
+ load_pixmap();
+ m_pixmap_load_failed = !has_pixmap();
+ }
+}
+
+void background_manager::load_pixmap() {
+ int old_depth = m_pixmap_depth;
+ clear_pixmap();
+
+ try {
+ if (!m_connection.root_pixmap(&m_pixmap, &m_pixmap_depth, &m_pixmap_geom)) {
+ m_log.warn("background_manager: Failed to get root pixmap, default to black (is there a wallpaper?)");
+ return;
+ }
+ } catch (const exception& err) {
+ m_log.err("background_manager: Failed to get root pixmap, default to black (%s)", err.what());
+ clear_pixmap();
+ return;
+ }
+
+ m_log.trace("background_manager: root pixmap (0x%x:%d) %dx%d+%d+%d", m_pixmap, m_pixmap_depth, m_pixmap_geom.width,
+ m_pixmap_geom.height, m_pixmap_geom.x, m_pixmap_geom.y);
+
+ if (m_pixmap_depth == 1 && m_pixmap_geom.width == 1 && m_pixmap_geom.height == 1) {
+ m_log.err("background_manager: Cannot find root pixmap, try a different tool to set the desktop background");
+ clear_pixmap();
+ return;
+ }
+
+ // Only reload visual if depth changed
+ if (old_depth != m_pixmap_depth) {
+ m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, m_pixmap_depth);
+
+ if (!m_visual) {
+ m_log.err("background_manager: Cannot find true color visual for root pixmap (depth: %d)", m_pixmap_depth);
+ clear_pixmap();
+ return;
+ }
+ }
+}
+
+void background_manager::clear_pixmap() {
+ if (has_pixmap()) {
+ m_pixmap = XCB_NONE;
+ m_pixmap_depth = 0;
+ m_pixmap_geom = {0, 0, 0, 0};
+ m_visual = nullptr;
+ }
+}
+
+bg_slice::bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window)
+ : m_connection(conn), m_log(log), m_rect(rect), m_window(window) {}
+
+bg_slice::~bg_slice() {
+ free_resources();
+}
+
+/**
+ * Get the current desktop background at the location of this slice.
+ * The returned pointer is only valid as long as the slice itself is alive.
+ *
+ * This function is fast, since the current desktop background is cached.
+ */
+cairo::surface* bg_slice::get_surface() const {
+ return m_surface.get();
+}
+
+void bg_slice::clear() {
+ ensure_resources(0, nullptr);
+}
+
+void bg_slice::copy(xcb_pixmap_t root_pixmap, int depth, xcb_rectangle_t geom, xcb_visualtype_t* visual) {
+ assert(root_pixmap);
+ assert(depth > 0);
+ ensure_resources(depth, visual);
+ assert(m_pixmap);
+
+ // fill the slice
+ auto translated = m_connection.translate_coordinates(m_window, m_connection.screen()->root, m_rect.x, m_rect.y);
+ // Coordinates of the slice in the root pixmap
+ auto src_x = math_util::cap(translated->dst_x, geom.x, int16_t(geom.x + geom.width));
+ auto src_y = math_util::cap(translated->dst_y, geom.y, int16_t(geom.y + geom.height));
+ auto w = math_util::cap(m_rect.width, uint16_t(0), uint16_t(geom.width - (src_x - geom.x)));
+ auto h = math_util::cap(m_rect.height, uint16_t(0), uint16_t(geom.height - (src_y - geom.y)));
+ m_log.trace(
+ "background_manager: Copying from root pixmap (0x%x:%d) %dx%d+%d+%d", root_pixmap, depth, w, h, src_x, src_y);
+ m_connection.copy_area_checked(root_pixmap, m_pixmap, m_gcontext, src_x, src_y, 0, 0, w, h);
+}
+
+void bg_slice::ensure_resources(int depth, xcb_visualtype_t* visual) {
+ if (m_depth != depth) {
+ m_depth = depth;
+
+ free_resources();
+
+ if (depth != 0) {
+ allocate_resources(visual);
+ }
+ }
+}
+
+void bg_slice::allocate_resources(xcb_visualtype_t* visual) {
+ m_log.trace("background_manager: Allocating pixmap");
+ m_pixmap = m_connection.generate_id();
+ m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_rect.width, m_rect.height);
+
+ m_log.trace("background_manager: Allocating graphics context");
+ auto black_pixel = m_connection.screen()->black_pixel;
+ uint32_t mask = 0;
+ xcb_params_gc_t params{};
+ std::array<uint32_t, 32> value_list{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, black_pixel);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, background, black_pixel);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0);
+ m_connection.pack_values(mask, ¶ms, value_list);
+ m_gcontext = m_connection.generate_id();
+ m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list.data());
+
+ m_log.trace("background_manager: Allocating cairo surface");
+ m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, m_rect.width, m_rect.height);
+}
+
+void bg_slice::free_resources() {
+ m_surface.reset();
+
+ if (m_pixmap != XCB_NONE) {
+ m_connection.free_pixmap(m_pixmap);
+ m_pixmap = XCB_NONE;
+ }
+
+ if (m_gcontext != XCB_NONE) {
+ m_connection.free_gc(m_gcontext);
+ m_gcontext = XCB_NONE;
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/connection.hpp"
+
+#include <algorithm>
+#include <iomanip>
+
+#include "errors.hpp"
+#include "utils/factory.hpp"
+#include "utils/memory.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+
+POLYBAR_NS
+
+/**
+ * Create instance
+ */
+connection::make_type connection::make(xcb_connection_t* conn, int default_screen) {
+ return static_cast<connection::make_type>(
+ *factory_util::singleton<std::remove_reference_t<connection::make_type>>(conn, default_screen));
+}
+
+connection::connection(xcb_connection_t* c, int default_screen) : base_type(c, default_screen) {
+ // Preload required xcb atoms
+ vector<xcb_intern_atom_cookie_t> cookies(ATOMS.size());
+
+ for (size_t i = 0; i < cookies.size(); i++) {
+ cookies[i] = xcb_intern_atom_unchecked(*this, false, ATOMS[i].name.size(), ATOMS[i].name.data());
+ }
+
+ for (size_t i = 0; i < cookies.size(); i++) {
+ malloc_unique_ptr<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(*this, cookies[i], nullptr), free);
+ if (reply) {
+ ATOMS[i].atom = reply->atom;
+ }
+ }
+
+// Query for X extensions
+#if WITH_XRANDR
+ randr_util::query_extension(*this);
+#endif
+#if WITH_XCOMPOSITE
+ composite_util::query_extension(*this);
+#endif
+#if WITH_XKB
+ xkb_util::query_extension(*this);
+#endif
+}
+
+connection::~connection() {
+ disconnect();
+}
+
+/**
+ * Packs data in src into the dest array.
+ *
+ * Each value in src is transferred into dest, if the corresponding bit in the
+ * mask is set.
+ *
+ * Required if parameters were set using XCB_AUX_ADD_PARAM but a value_list is needed in the function call.
+ *
+ * @param mask bitmask specifying which entries in src are selected
+ * @param src Array of 32-bit integers. Must have at least as many entries as the highest bit set in mask
+ * @param dest Entries from src are packed into this array
+ */
+void connection::pack_values(uint32_t mask, const void* src, std::array<uint32_t, 32>& dest) {
+ size_t dest_i = 0;
+ for (size_t i = 0; i < dest.size() && mask; i++, mask >>= 1) {
+ if (mask & 0x1) {
+ dest[dest_i] = reinterpret_cast<const uint32_t*>(src)[i];
+ dest_i++;
+ }
+ }
+}
+
+/**
+ * Create X window id string
+ */
+string connection::id(xcb_window_t w) const {
+ return sstream() << "0x" << std::hex << std::setw(7) << std::setfill('0') << w;
+}
+
+void connection::reset_screen() {
+ m_screen = nullptr;
+}
+
+/**
+ * Get pointer to the default xcb screen
+ */
+xcb_screen_t* connection::screen() {
+ if (m_screen == nullptr) {
+ m_screen = screen_of_display(default_screen());
+ }
+ return m_screen;
+}
+
+/**
+ * Add given event to the event mask unless already added
+ */
+void connection::ensure_event_mask(xcb_window_t win, unsigned int event) {
+ auto attributes = get_window_attributes(win);
+ attributes->your_event_mask = attributes->your_event_mask | event;
+ change_window_attributes(win, XCB_CW_EVENT_MASK, &attributes->your_event_mask);
+}
+
+/**
+ * Clear event mask for the given window
+ */
+void connection::clear_event_mask(xcb_window_t win) {
+ unsigned int mask{XCB_EVENT_MASK_NO_EVENT};
+ change_window_attributes(win, XCB_CW_EVENT_MASK, &mask);
+}
+
+/**
+ * Creates an instance of shared_ptr<xcb_client_message_event_t>
+ */
+xcb_client_message_event_t connection::make_client_message(xcb_atom_t type, xcb_window_t target) const {
+ xcb_client_message_event_t client_message;
+ client_message.response_type = XCB_CLIENT_MESSAGE;
+ client_message.format = 32;
+ client_message.type = type;
+ client_message.window = target;
+
+ client_message.sequence = 0;
+ client_message.data.data32[0] = 0;
+ client_message.data.data32[1] = 0;
+ client_message.data.data32[2] = 0;
+ client_message.data.data32[3] = 0;
+ client_message.data.data32[4] = 0;
+
+ return client_message;
+}
+
+/**
+ * Send client message event
+ */
+void connection::send_client_message(
+ const xcb_client_message_event_t& message, xcb_window_t target, unsigned int event_mask, bool propagate) const {
+ send_event(propagate, target, event_mask, reinterpret_cast<const char*>(&message));
+ flush();
+}
+
+/**
+ * Try to get a visual type for the given screen that
+ * matches the given depth
+ */
+xcb_visualtype_t* connection::visual_type(xcb_visual_class_t class_, int match_depth) {
+ return xcb_aux_find_visual_by_attrs(screen(), class_, match_depth);
+}
+
+xcb_visualtype_t* connection::visual_type_for_id(xcb_visualid_t visual_id) {
+ return xcb_aux_find_visual_by_id(screen(), visual_id);
+}
+
+/**
+ * Query root window pixmap
+ */
+bool connection::root_pixmap(xcb_pixmap_t* pixmap, int* depth, xcb_rectangle_t* rect) {
+ *pixmap = XCB_NONE; // default value if getting the root pixmap fails
+
+ /*
+ * We try multiple properties for the root pixmap here because I am not 100% sure
+ * if all programs set them the same way. We might be able to just use _XSETROOT_ID
+ * but keeping the other as fallback should not hurt (if it does, feel free to remove).
+ *
+ * see https://metacpan.org/pod/X11::Protocol::XSetRoot#ROOT-WINDOW-PROPERTIES for description of the properties
+ * the properties here are ordered by reliability:
+ * _XSETROOT_ID: this is usually a dummy 1x1 pixmap only for resource managment, use only as last resort
+ * ESETROOT_PMAP_ID: according to the link above, this should usually by equal to _XROOTPMAP_ID
+ * _XROOTPMAP_ID: this appears to be the "correct" property to use? if available, use this
+ */
+ const xcb_atom_t pixmap_properties[3]{_XROOTPMAP_ID, ESETROOT_PMAP_ID, _XSETROOT_ID};
+ for (auto&& property : pixmap_properties) {
+ try {
+ auto prop = get_property(false, root(), property, XCB_ATOM_PIXMAP, 0L, 1L);
+ if (prop->format == 32 && prop->value_len == 1) {
+ *pixmap = *prop.value<xcb_pixmap_t>().begin();
+ }
+
+ if (*pixmap) {
+ auto geom = get_geometry(*pixmap);
+ *depth = geom->depth;
+ rect->width = geom->width;
+ rect->height = geom->height;
+ rect->x = geom->x;
+ rect->y = geom->y;
+ return true;
+ }
+ } catch (const exception& err) {
+ *pixmap = XCB_NONE;
+ *rect = xcb_rectangle_t{0, 0, 0U, 0U};
+ continue;
+ }
+ }
+ return false;
+}
+
+/**
+ * Parse connection error
+ */
+string connection::error_str(int error_code) {
+ switch (error_code) {
+ case XCB_CONN_ERROR:
+ return "Socket, pipe or stream error";
+ case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
+ return "Unsupported extension";
+ case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
+ return "Not enough memory";
+ case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
+ return "Request length exceeded";
+ case XCB_CONN_CLOSED_PARSE_ERR:
+ return "Can't parse display string";
+ case XCB_CONN_CLOSED_INVALID_SCREEN:
+ return "Invalid screen";
+ case XCB_CONN_CLOSED_FDPASSING_FAILED:
+ return "Failed to pass FD";
+ default:
+ return "Unknown error";
+ }
+}
+
+/**
+ * Dispatch event through the registry
+ */
+void connection::dispatch_event(const shared_ptr<xcb_generic_event_t>& evt) const {
+ m_registry.dispatch(evt);
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/cursor.hpp"
+
+#include "utils/scope.hpp"
+
+POLYBAR_NS
+
+namespace cursor_util {
+ bool valid(const string& name) {
+ return (cursors.find(name) != cursors.end());
+ }
+
+ bool set_cursor(xcb_connection_t* c, xcb_screen_t* screen, xcb_window_t w, const string& name) {
+ if (!valid(name)) {
+ throw std::runtime_error("Tried to set cursor to invalid name: '" + name + "'");
+ }
+
+ xcb_cursor_context_t* ctx;
+
+ if (xcb_cursor_context_new(c, screen, &ctx) < 0) {
+ return false;
+ }
+
+ scope_util::on_exit handler([&] { xcb_cursor_context_free(ctx); });
+
+ xcb_cursor_t cursor = XCB_CURSOR_NONE;
+ for (const auto& cursor_name : cursors.at(name)) {
+ cursor = xcb_cursor_load_cursor(ctx, cursor_name.c_str());
+ if (cursor != XCB_CURSOR_NONE) {
+ break;
+ }
+ }
+
+ if (cursor == XCB_CURSOR_NONE) {
+ return false;
+ }
+
+ xcb_change_window_attributes(c, w, XCB_CW_CURSOR, &cursor);
+ xcb_free_cursor(c, cursor);
+
+ return true;
+ }
+} // namespace cursor_util
+POLYBAR_NS_END
--- /dev/null
+#include "x11/ewmh.hpp"
+
+#include <unistd.h>
+
+#include "components/types.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace ewmh_util {
+
+ewmh_connection::ewmh_connection() {
+ xcb_ewmh_init_atoms_replies(&c, xcb_ewmh_init_atoms(connection::make(), &c), nullptr);
+}
+
+ewmh_connection::~ewmh_connection() {
+ xcb_ewmh_connection_wipe(&c);
+}
+
+xcb_ewmh_connection_t* ewmh_connection::operator->() {
+ return &c;
+}
+
+ewmh_connection::operator xcb_ewmh_connection_t*() {
+ return &c;
+}
+
+ewmh_connection& initialize() {
+ static ewmh_connection c;
+ return c;
+}
+
+bool supports(xcb_atom_t atom, int screen) {
+ auto& conn = initialize();
+ xcb_ewmh_get_atoms_reply_t reply{};
+ if (xcb_ewmh_get_supported_reply(conn, xcb_ewmh_get_supported(conn, screen), &reply, nullptr)) {
+ for (size_t n = 0; n < reply.atoms_len; n++) {
+ if (reply.atoms[n] == atom) {
+ xcb_ewmh_get_atoms_reply_wipe(&reply);
+ return true;
+ }
+ }
+ xcb_ewmh_get_atoms_reply_wipe(&reply);
+ }
+ return false;
+}
+
+string get_wm_name(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+ if (xcb_ewmh_get_wm_name_reply(conn, xcb_ewmh_get_wm_name(conn, win), &utf8_reply, nullptr)) {
+ return get_reply_string(&utf8_reply);
+ }
+ return "";
+}
+
+string get_visible_name(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+ if (xcb_ewmh_get_wm_visible_name_reply(conn, xcb_ewmh_get_wm_visible_name(conn, win), &utf8_reply, nullptr)) {
+ return get_reply_string(&utf8_reply);
+ }
+ return "";
+}
+
+string get_icon_name(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+ if (xcb_ewmh_get_wm_icon_name_reply(conn, xcb_ewmh_get_wm_icon_name(conn, win), &utf8_reply, nullptr)) {
+ return get_reply_string(&utf8_reply);
+ }
+ return "";
+}
+
+string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply) {
+ string str;
+ if (reply) {
+ str = string(reply->strings, reply->strings_len);
+ xcb_ewmh_get_utf8_strings_reply_wipe(reply);
+ }
+ return str;
+}
+
+unsigned int get_current_desktop(int screen) {
+ auto& conn = initialize();
+ unsigned int desktop = XCB_NONE;
+ xcb_ewmh_get_current_desktop_reply(conn, xcb_ewmh_get_current_desktop(conn, screen), &desktop, nullptr);
+ return desktop;
+}
+
+unsigned int get_number_of_desktops(int screen) {
+ auto& conn = initialize();
+ unsigned int desktops = XCB_NONE;
+ xcb_ewmh_get_number_of_desktops_reply(conn, xcb_ewmh_get_number_of_desktops(conn, screen), &desktops, nullptr);
+ return desktops;
+}
+
+vector<position> get_desktop_viewports(int screen) {
+ auto& conn = initialize();
+ vector<position> viewports;
+ xcb_ewmh_get_desktop_viewport_reply_t reply{};
+ if (xcb_ewmh_get_desktop_viewport_reply(conn, xcb_ewmh_get_desktop_viewport(conn, screen), &reply, nullptr)) {
+ for (size_t n = 0; n < reply.desktop_viewport_len; n++) {
+ viewports.emplace_back(position{
+ static_cast<short int>(reply.desktop_viewport[n].x), static_cast<short int>(reply.desktop_viewport[n].y)});
+ }
+ }
+ return viewports;
+}
+
+vector<string> get_desktop_names(int screen) {
+ auto& conn = initialize();
+ xcb_ewmh_get_utf8_strings_reply_t reply{};
+ if (xcb_ewmh_get_desktop_names_reply(conn, xcb_ewmh_get_desktop_names(conn, screen), &reply, nullptr)) {
+ return string_util::split(string(reply.strings, reply.strings_len), '\0');
+ }
+ return {};
+}
+
+xcb_window_t get_active_window(int screen) {
+ auto& conn = initialize();
+ unsigned int win = XCB_NONE;
+ xcb_ewmh_get_active_window_reply(conn, xcb_ewmh_get_active_window(conn, screen), &win, nullptr);
+ return win;
+}
+
+void change_current_desktop(unsigned int desktop) {
+ auto& conn = initialize();
+ xcb_ewmh_request_change_current_desktop(conn, 0, desktop, XCB_CURRENT_TIME);
+ xcb_flush(conn->connection);
+}
+
+unsigned int get_desktop_from_window(xcb_window_t window) {
+ auto& conn = initialize();
+ unsigned int desktop = XCB_NONE;
+ xcb_ewmh_get_wm_desktop_reply(conn, xcb_ewmh_get_wm_desktop(conn, window), &desktop, nullptr);
+ return desktop;
+}
+
+void set_wm_window_type(xcb_window_t win, vector<xcb_atom_t> types) {
+ auto& conn = initialize();
+ xcb_ewmh_set_wm_window_type(conn, win, types.size(), types.data());
+ xcb_flush(conn->connection);
+}
+
+void set_wm_state(xcb_window_t win, vector<xcb_atom_t> states) {
+ auto& conn = initialize();
+ xcb_ewmh_set_wm_state(conn, win, states.size(), states.data());
+ xcb_flush(conn->connection);
+}
+
+vector<xcb_atom_t> get_wm_state(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_ewmh_get_atoms_reply_t reply;
+ if (xcb_ewmh_get_wm_state_reply(conn, xcb_ewmh_get_wm_state(conn, win), &reply, nullptr)) {
+ return {reply.atoms, reply.atoms + reply.atoms_len};
+ }
+ return {};
+}
+
+void set_wm_pid(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_ewmh_set_wm_pid(conn, win, getpid());
+ xcb_flush(conn->connection);
+}
+
+void set_wm_pid(xcb_window_t win, unsigned int pid) {
+ auto& conn = initialize();
+ xcb_ewmh_set_wm_pid(conn, win, pid);
+ xcb_flush(conn->connection);
+}
+
+void set_wm_desktop(xcb_window_t win, unsigned int desktop) {
+ auto& conn = initialize();
+ xcb_ewmh_set_wm_desktop(conn, win, desktop);
+ xcb_flush(conn->connection);
+}
+
+void set_wm_window_opacity(xcb_window_t win, unsigned long int values) {
+ auto& conn = initialize();
+ xcb_change_property(
+ conn->connection, XCB_PROP_MODE_REPLACE, win, _NET_WM_WINDOW_OPACITY, XCB_ATOM_CARDINAL, 32, 1, &values);
+ xcb_flush(conn->connection);
+}
+
+vector<xcb_window_t> get_client_list(int screen) {
+ auto& conn = initialize();
+ xcb_ewmh_get_windows_reply_t reply;
+ if (xcb_ewmh_get_client_list_reply(conn, xcb_ewmh_get_client_list(conn, screen), &reply, nullptr)) {
+ return {reply.windows, reply.windows + reply.windows_len};
+ }
+ return {};
+}
+
+/**
+ * Retrieves the _NET_SUPPORTING_WM_CHECK atom on the given window
+ *
+ * @return The _NET_SUPPORTING_WM_CHECK window id or XCB_NONE in case of an error
+ */
+xcb_window_t get_supporting_wm_check(xcb_window_t win) {
+ auto& conn = initialize();
+ xcb_window_t result{};
+ if (xcb_ewmh_get_supporting_wm_check_reply(conn, xcb_ewmh_get_supporting_wm_check(conn, win), &result, nullptr)) {
+ return result;
+ }
+ return XCB_NONE;
+}
+
+/**
+ * Searches for the meta window spawned by the WM to indicate a compliant WM is active.
+ *
+ * The window has the following properties:
+ * * It's pointed to by the root window's _NET_SUPPORTING_WM_CHECK atom
+ * * Its own _NET_SUPPORTING_WM_CHECK atom is set and points to itself
+ *
+ * @return The meta window id or XCB_NONE if it wasn't found
+ */
+xcb_window_t get_ewmh_meta_window(xcb_window_t root) {
+ xcb_window_t wm_meta_window = ewmh_util::get_supporting_wm_check(root);
+
+ if (wm_meta_window != XCB_NONE && ewmh_util::get_supporting_wm_check(wm_meta_window) == wm_meta_window) {
+ return wm_meta_window;
+ }
+
+ return XCB_NONE;
+}
+} // namespace ewmh_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/extensions/composite.hpp"
+#include "errors.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace composite_util {
+ /**
+ * Query for the XCOMPOSITE extension
+ */
+ void query_extension(connection& conn) {
+ conn.composite().query_version(XCB_COMPOSITE_MAJOR_VERSION, XCB_COMPOSITE_MINOR_VERSION);
+
+ if (!conn.extension<xpp::composite::extension>()->present) {
+ throw application_error("Missing X extension: Composite");
+ }
+ }
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/extensions/randr.hpp"
+
+#include <algorithm>
+#include <utility>
+
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+/**
+ * Workaround for the inconsistent naming of outputs between intel and nvidia
+ * drivers (xf86-video-intel drops the dash)
+ *
+ * If exact == false, dashes will be ignored while matching
+ */
+bool randr_output::match(const string& o, bool exact) const {
+ if (exact) {
+ return name == o;
+ } else {
+ return string_util::replace(name, "-", "") == string_util::replace(o, "-", "");
+ }
+}
+
+/**
+ * Match position
+ */
+bool randr_output::match(const position& p) const {
+ return p.x == x && p.y == y;
+}
+
+/**
+ * Checks if `this` contains the position p
+ */
+bool randr_output::contains(const position& p) const {
+ return x <= p.x && y <= p.y && (x + w) > p.x && (y + h) > p.y;
+}
+
+/**
+ * Checks if inner is contained within `this`
+ *
+ * Also returns true for outputs that occupy the exact same space
+ */
+bool randr_output::contains(const randr_output& inner) const {
+ return inner.x >= x && inner.x + inner.w <= x + w && inner.y >= y && inner.y + inner.h <= y + h;
+}
+
+/**
+ * Checks if the given output is the same as this
+ *
+ * Looks at xcb_randr_output_t, position, dimension, name and 'primary'
+ */
+bool randr_output::equals(const randr_output& o) const {
+ return o.output == output && o.x == x && o.y == y && o.w == w && o.h == h && o.primary == primary && o.name == name;
+}
+
+namespace randr_util {
+ /**
+ * XRandR version
+ */
+ static unsigned int g_major_version = 0;
+ static unsigned int g_minor_version = 0;
+
+ /**
+ * Query for the XRandR extension
+ */
+ void query_extension(connection& conn) {
+ auto ext = conn.randr().query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+ g_major_version = ext->major_version;
+ g_minor_version = ext->minor_version;
+
+ if (!conn.extension<xpp::randr::extension>()->present) {
+ throw application_error("Missing X extension: Randr");
+ }
+ }
+
+ /**
+ * Check for XRandR monitor support
+ */
+ bool check_monitor_support() {
+ return WITH_XRANDR_MONITORS && g_major_version >= 1 && g_minor_version >= 5;
+ }
+
+ /**
+ * Define monitor
+ */
+ monitor_t make_monitor(xcb_randr_output_t randr, string name, unsigned short int w, unsigned short int h, short int x,
+ short int y, bool primary) {
+ monitor_t mon{new monitor_t::element_type{}};
+ mon->output = randr;
+ mon->name = move(name);
+ mon->x = x;
+ mon->y = y;
+ mon->h = h;
+ mon->w = w;
+ mon->primary = primary;
+ return mon;
+ }
+
+ /**
+ * Create a list of all available randr outputs
+ */
+ vector<monitor_t> get_monitors(connection& conn, bool connected_only, bool purge_clones) {
+ xcb_window_t root = conn.root();
+ vector<monitor_t> monitors;
+ bool found = false;
+
+ if (check_monitor_support()) {
+#if WITH_XRANDR_MONITORS
+ /* Use C, because XPP does not provide access to output info from monitors. */
+ xcb_generic_error_t* err;
+ auto rrmonitors = xcb_randr_get_monitors_reply(conn, xcb_randr_get_monitors(conn, root, true), &err);
+ if (err != NULL) {
+ free(err);
+ } else {
+ for (auto iter = xcb_randr_get_monitors_monitors_iterator(rrmonitors); iter.rem;
+ xcb_randr_monitor_info_next(&iter)) {
+ auto mon = iter.data;
+ auto name = conn.get_atom_name(mon->name).name();
+
+ /* Output is only useful for some plugins. We take the first one. */
+ int randr_output_len = xcb_randr_monitor_info_outputs_length(mon);
+ auto randr_outputs = xcb_randr_monitor_info_outputs(mon);
+ auto output = (randr_output_len >= 1) ? randr_outputs[0] : XCB_NONE;
+ monitors.emplace_back(
+ make_monitor(output, move(name), mon->width, mon->height, mon->x, mon->y, mon->primary));
+ found = true;
+ }
+ free(rrmonitors);
+ }
+#endif
+ }
+ if (!found || !purge_clones) {
+ auto primary_output = conn.get_output_primary(root).output();
+ string primary_name{};
+
+ if (primary_output != XCB_NONE) {
+ auto primary_info = conn.get_output_info(primary_output);
+ auto name_iter = primary_info.name();
+ primary_name = {name_iter.begin(), name_iter.end()};
+ }
+
+ for (auto&& output : conn.get_screen_resources_current(root).outputs()) {
+ try {
+ auto info = conn.get_output_info(output);
+ if (info->crtc == XCB_NONE) {
+ continue;
+ } else if (connected_only && info->connection != XCB_RANDR_CONNECTION_CONNECTED) {
+ continue;
+ }
+
+ auto name_iter = info.name();
+ string name{name_iter.begin(), name_iter.end()};
+
+ auto crtc = conn.get_crtc_info(info->crtc);
+ auto primary = (primary_name == name);
+ if (!std::any_of(
+ monitors.begin(), monitors.end(), [name](const auto& monitor) { return monitor->name == name; })) {
+ monitors.emplace_back(
+ make_monitor(output, move(name), crtc->width, crtc->height, crtc->x, crtc->y, primary));
+ }
+ } catch (const exception&) {
+ // silently ignore output
+ }
+ }
+ }
+
+ if (purge_clones) {
+ for (auto& outer : monitors) {
+ if (outer->w == 0) {
+ continue;
+ }
+
+ for (auto& inner : monitors) {
+ if (outer == inner || inner->w == 0) {
+ continue;
+ }
+
+ // If inner is contained in outer, inner is removed
+ // If both happen to be the same size and have the same coordinates,
+ // inner is still removed but it doesn't matter since both occupy the
+ // exact same space
+ if (outer->contains(*inner)) {
+ // Reset width so that the output gets removed afterwards
+ inner->w = 0;
+ }
+ }
+ }
+
+ // Remove all cloned monitors (monitors with 0 width)
+ monitors.erase(
+ std::remove_if(monitors.begin(), monitors.end(), [](const auto& monitor) { return monitor->w == 0; }),
+ monitors.end());
+ }
+
+ return monitors;
+ }
+
+ /**
+ * Searches for a monitor with name in monitors
+ *
+ * Does best-fit matching (if exact_match == true, this is also first-fit)
+ */
+ monitor_t match_monitor(vector<monitor_t> monitors, const string& name, bool exact_match) {
+ monitor_t result{};
+ for (auto&& monitor : monitors) {
+ // If we can do an exact match, we have found our result
+ if (monitor->match(name, true)) {
+ result = move(monitor);
+ break;
+ }
+
+ /*
+ * Non-exact matches are moved into the result but we continue searching
+ * through the list, maybe we can find an exact match
+ * Note: If exact_match == true, we don't need to run this because it
+ * would be the exact same check as above
+ */
+ if (!exact_match && monitor->match(name, false)) {
+ result = move(monitor);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Get backlight value range for given output
+ */
+ void get_backlight_range(connection& conn, const monitor_t& mon, backlight_values& dst) {
+ auto atom = Backlight;
+ auto reply = conn.query_output_property(mon->output, atom);
+
+ dst.min = 0;
+ dst.max = 0;
+
+ if (!reply->range || reply->length != 2) {
+ atom = BACKLIGHT;
+ reply = conn.query_output_property(mon->output, atom);
+ }
+
+ if (!reply->range || reply->length != 2) {
+ return;
+ }
+
+ auto range = reply.valid_values().begin();
+ dst.min = static_cast<double>(*range++);
+ dst.max = static_cast<double>(*range);
+ dst.atom = atom;
+ }
+
+ /**
+ * Get backlight value for given output
+ */
+ void get_backlight_value(connection& conn, const monitor_t& mon, backlight_values& dst) {
+ dst.val = 0.0;
+
+ if (!dst.atom) {
+ return;
+ }
+
+ auto reply = conn.get_output_property(mon->output, dst.atom, XCB_ATOM_NONE, 0, 4, 0, 0);
+ if (reply->num_items == 1 && reply->format == 32 && reply->type == XCB_ATOM_INTEGER) {
+ int value = *reinterpret_cast<int*>(xcb_randr_get_output_property_data(reply.get().get()));
+ dst.val = static_cast<double>(value);
+ }
+ }
+} // namespace randr_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/extensions/xkb.hpp"
+
+#include "errors.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+
+POLYBAR_NS
+
+/**
+ * Get next layout index
+ */
+const keyboard::indicator& keyboard::get(const indicator::type& i) const {
+ return indicators.at(i);
+}
+
+/**
+ * Update indicator states
+ */
+void keyboard::set(unsigned int state) {
+ for (auto& i : indicators) {
+ i.second.enabled = state & i.second.mask;
+ }
+}
+
+/**
+ * Get state for the given class
+ */
+bool keyboard::on(const indicator::type& i) const {
+ return indicators.at(i).enabled;
+}
+
+/**
+ * Set current group number
+ */
+void keyboard::current(unsigned char group) {
+ current_group = group;
+}
+
+/**
+ * Get current group number
+ */
+unsigned char keyboard::current() const {
+ return current_group;
+}
+
+/**
+ * Get current group name
+ */
+const string keyboard::group_name(size_t index) const {
+ if (!layouts.empty() && index < layouts.size()) {
+ return layouts[index].group_name;
+ }
+ return "";
+}
+
+/**
+ * Get current layout name
+ */
+const string keyboard::layout_name(size_t index) const {
+ if (index >= layouts.size() || index >= layouts[index].symbols.size()) {
+ return "";
+ }
+ return layouts[index].symbols[index];
+}
+
+/**
+ * Get current variant name
+ * "GROUP (VARIANT)"
+ * ^^^^^^^
+ */
+const string keyboard::variant_name(size_t index) const {
+ string group_name = this->group_name(index);
+ if (int start = group_name.find('(') + 1) {
+ int num_chars = group_name.find(')') - start;
+ return group_name.substr(start, num_chars);
+ } else {
+ return "";
+ }
+}
+
+/**
+ * Get indicator name
+ */
+const string keyboard::indicator_name(const indicator::type& i) const {
+ return indicators.at(i).name;
+}
+
+size_t keyboard::size() const {
+ return layouts.size();
+}
+
+namespace xkb_util {
+ /**
+ * Query for the XKB extension
+ */
+ void query_extension(connection& conn) {
+ conn.xkb().use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
+
+ if (!conn.extension<xpp::xkb::extension>()->present) {
+ throw application_error("Missing X extension: XKb");
+ }
+ }
+
+ /**
+ * Get current group number
+ */
+ void switch_layout(connection& conn, xcb_xkb_device_spec_t device, unsigned char index) {
+ xcb_xkb_latch_lock_state(conn, device, 0, 0, true, index, 0, 0, 0);
+ xcb_flush(conn);
+ }
+
+ /**
+ * Get current group number
+ */
+ unsigned char get_current_group(connection& conn, xcb_xkb_device_spec_t device) {
+ return conn.xkb().get_state(device)->group;
+ }
+
+ /**
+ * Get keyboard layouts
+ */
+ vector<keyboard::layout> get_layouts(connection& conn, xcb_xkb_device_spec_t device) {
+ vector<keyboard::layout> results;
+
+ unsigned int mask{XCB_XKB_NAME_DETAIL_GROUP_NAMES | XCB_XKB_NAME_DETAIL_SYMBOLS};
+ auto reply = conn.xkb().get_names(device, mask);
+
+ xcb_xkb_get_names_value_list_t values{};
+ void* buffer = xcb_xkb_get_names_value_list(reply.get().get());
+ xcb_xkb_get_names_value_list_unpack(buffer, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames,
+ reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &values);
+
+ for (int i = 0; i < xcb_xkb_get_names_value_list_groups_length(reply.get().get(), &values); i++) {
+ vector<string> sym_names;
+
+ for (auto&& sym : string_util::split(conn.get_atom_name(values.symbolsName).name(), '+')) {
+ if (!(sym = parse_layout_symbol(move(sym))).empty()) {
+ sym_names.emplace_back(move(sym));
+ }
+ }
+
+ const auto& name = conn.get_atom_name(values.groups[i]).name();
+ results.emplace_back(keyboard::layout{name, sym_names});
+ }
+
+ return results;
+ }
+
+ /**
+ * Get keyboard indicators
+ */
+ map<keyboard::indicator::type, keyboard::indicator> get_indicators(connection& conn, xcb_xkb_device_spec_t device) {
+ map<keyboard::indicator::type, keyboard::indicator> results;
+
+ unsigned int mask{XCB_XKB_NAME_DETAIL_INDICATOR_NAMES};
+ auto reply = conn.xkb().get_names(device, mask);
+
+ xcb_xkb_get_names_value_list_t values{};
+ void* buffer = xcb_xkb_get_names_value_list(reply.get().get());
+ xcb_xkb_get_names_value_list_unpack(buffer, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames,
+ reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &values);
+
+ for (int i = 0; i < xcb_xkb_get_names_value_list_indicator_names_length(reply.get().get(), &values); i++) {
+ auto indicatorName = values.indicatorNames[i];
+
+ auto name = conn.get_atom_name(indicatorName).name();
+ auto type = keyboard::indicator::type::NONE;
+
+ if (string_util::compare(name, "caps lock")) {
+ type = keyboard::indicator::type::CAPS_LOCK;
+ } else if (string_util::compare(name, "num lock")) {
+ type = keyboard::indicator::type::NUM_LOCK;
+ } else if (string_util::compare(name, "scroll lock")) {
+ type = keyboard::indicator::type::SCROLL_LOCK;
+ } else {
+ continue;
+ }
+
+ auto data = conn.xkb().get_named_indicator(device, 0, 0, indicatorName);
+ auto mask = (*conn.xkb().get_indicator_map(device, 1 << data->ndx).maps().begin()).mods;
+ auto enabled = static_cast<bool>(data->on);
+
+ results.emplace(type, keyboard::indicator{indicatorName, mask, name, enabled});
+ }
+
+ return results;
+ }
+
+ /**
+ * Parse symbol name and exclude entries blacklisted entries
+ */
+ string parse_layout_symbol(string&& name) {
+ size_t pos;
+ while ((pos = name.find_first_of({'(', ':'})) != string::npos) {
+ name.erase(pos);
+ }
+ if (string_util::contains(LAYOUT_SYMBOL_BLACKLIST, ";" + name + ";")) {
+ return "";
+ }
+ return move(name);
+ }
+} // namespace xkb_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/icccm.hpp"
+
+#include "x11/atoms.hpp"
+
+POLYBAR_NS
+
+namespace icccm_util {
+ string get_wm_name(xcb_connection_t* c, xcb_window_t w) {
+ xcb_icccm_get_text_property_reply_t reply{};
+ if (xcb_icccm_get_wm_name_reply(c, xcb_icccm_get_wm_name(c, w), &reply, nullptr)) {
+ return get_reply_string(&reply);
+ }
+ return "";
+ }
+
+ pair<string, string> get_wm_class(xcb_connection_t* c, xcb_window_t w) {
+ pair<string, string> result{"", ""};
+ xcb_icccm_get_wm_class_reply_t reply{};
+ if (xcb_icccm_get_wm_class_reply(c, xcb_icccm_get_wm_class(c, w), &reply, nullptr)) {
+ result = {string(reply.instance_name), string(reply.class_name)};
+ xcb_icccm_get_wm_class_reply_wipe(&reply);
+ }
+ return result;
+ }
+
+ string get_reply_string(xcb_icccm_get_text_property_reply_t* reply) {
+ string str;
+ if (reply) {
+ str = string(reply->name, reply->name_len);
+ xcb_icccm_get_text_property_reply_wipe(reply);
+ }
+ return str;
+ }
+
+ void set_wm_name(xcb_connection_t* c, xcb_window_t w, const char* wmname, size_t l, const char* wmclass, size_t l2) {
+ xcb_icccm_set_wm_name(c, w, XCB_ATOM_STRING, 8, l, wmname);
+ xcb_icccm_set_wm_class(c, w, l2, wmclass);
+ }
+
+ void set_wm_protocols(xcb_connection_t* c, xcb_window_t w, vector<xcb_atom_t> flags) {
+ xcb_icccm_set_wm_protocols(c, w, WM_PROTOCOLS, flags.size(), flags.data());
+ }
+
+ bool get_wm_urgency(xcb_connection_t* c, xcb_window_t w) {
+ xcb_icccm_wm_hints_t hints;
+ if (xcb_icccm_get_wm_hints_reply(c, xcb_icccm_get_wm_hints(c, w), &hints, NULL)) {
+ if (xcb_icccm_wm_hints_get_urgency(&hints) == XCB_ICCCM_WM_HINT_X_URGENCY)
+ return true;
+ }
+ return false;
+ }
+
+ void set_wm_size_hints(xcb_connection_t* c, xcb_window_t w, int x, int y, int width, int height) {
+ xcb_size_hints_t hints{};
+
+ xcb_icccm_size_hints_set_size(&hints, false, width, height);
+ xcb_icccm_size_hints_set_min_size(&hints, width, height);
+ xcb_icccm_size_hints_set_max_size(&hints, width, height);
+ xcb_icccm_size_hints_set_base_size(&hints, width, height);
+ xcb_icccm_size_hints_set_position(&hints, false, x, y);
+
+ xcb_icccm_set_wm_size_hints(c, w, XCB_ATOM_WM_NORMAL_HINTS, &hints);
+ }
+} // namespace icccm_util
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/legacy_tray_manager.hpp"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_image.h>
+
+#include <thread>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "errors.hpp"
+#include "events/signal.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/memory.hpp"
+#include "utils/process.hpp"
+#include "utils/units.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+#include "x11/winspec.hpp"
+#include "x11/xembed.hpp"
+
+/**
+ * Legacy tray implementation that does not run as part of a module.
+ */
+
+POLYBAR_NS
+
+namespace legacy_tray {
+
+/**
+ * Create instance
+ */
+tray_manager::make_type tray_manager::make(const bar_settings& settings) {
+ return std::make_unique<tray_manager>(
+ connection::make(), signal_emitter::make(), logger::make(), background_manager::make(), settings);
+}
+
+tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back,
+ const bar_settings& settings)
+ : m_connection(conn), m_sig(emitter), m_log(logger), m_background_manager(back), m_bar_opts(settings) {
+ m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
+}
+
+tray_manager::~tray_manager() {
+ if (m_delaythread.joinable()) {
+ m_delaythread.join();
+ }
+ m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
+ deactivate();
+}
+
+void tray_manager::setup(const config& conf, const string& tray_module_name) {
+ auto bs = conf.section();
+
+ for (const auto& deprecated : {"tray-position", "tray-detached", "tray-maxsize", "tray-scale", "tray-background",
+ "tray-foreground", "tray-padding", "tray-offset-x", "tray-offset-y"}) {
+ if (conf.has(bs, deprecated)) {
+ m_log.warn("tray: %s.%s is deprecated, use the dedicated tray module to display the system tray", bs, deprecated);
+ }
+ }
+
+ string position = conf.get(bs, "tray-position", "none"s);
+
+ if (!position.empty() && position != "none" && !tray_module_name.empty()) {
+ m_log.err(
+ "The tray position is manually defined (`tray-position`) and also set by the tray module (%s). `tray-position` "
+ "will be ignored",
+ tray_module_name);
+ }
+
+ if (!tray_module_name.empty()) {
+ m_opts.tray_position = tray_postition::MODULE;
+ return;
+ } else if (position == "left") {
+ m_opts.tray_position = tray_postition::LEFT;
+ } else if (position == "right") {
+ m_opts.tray_position = tray_postition::RIGHT;
+ } else if (position == "center") {
+ m_opts.tray_position = tray_postition::CENTER;
+ } else if (position != "none") {
+ return m_log.err("Disabling tray manager (reason: Invalid position \"" + position + "\")");
+ } else {
+ return;
+ }
+
+ m_opts.detached = conf.get(bs, "tray-detached", false);
+ m_opts.height = m_bar_opts.size.h;
+ m_opts.height -= m_bar_opts.borders.at(edge::BOTTOM).size;
+ m_opts.height -= m_bar_opts.borders.at(edge::TOP).size;
+ m_opts.height_fill = m_opts.height;
+
+ if (m_opts.height % 2 != 0) {
+ m_opts.height--;
+ }
+
+ auto maxsize = conf.get<unsigned int>(bs, "tray-maxsize", 16);
+ if (m_opts.height > maxsize) {
+ m_opts.spacing += (m_opts.height - maxsize) / 2;
+ m_opts.height = maxsize;
+ }
+
+ m_opts.width_max = m_bar_opts.size.w;
+ m_opts.width = m_opts.height;
+ m_opts.orig_y = m_bar_opts.pos.y + m_bar_opts.borders.at(edge::TOP).size;
+
+ // Apply user-defined scaling
+ auto scale = conf.get(bs, "tray-scale", 1.0);
+ m_opts.width *= scale;
+ m_opts.height_fill *= scale;
+
+ auto inner_area = m_bar_opts.inner_area(true);
+
+ switch (m_opts.tray_position) {
+ case tray_postition::NONE:
+ break;
+ case tray_postition::LEFT:
+ m_opts.orig_x = inner_area.x;
+ break;
+ case tray_postition::CENTER:
+ m_opts.orig_x = inner_area.x + inner_area.width / 2 - m_opts.width / 2;
+ break;
+ case tray_postition::RIGHT:
+ m_opts.orig_x = inner_area.x + inner_area.width;
+ break;
+ case tray_postition::MODULE:
+ break;
+ }
+
+ if (conf.has(bs, "tray-transparent")) {
+ m_log.warn("tray-transparent is deprecated, the tray always uses pseudo-transparency. Please remove it.");
+ }
+
+ // Set user-defined foreground and background colors.
+ m_opts.background = conf.get(bs, "tray-background", m_bar_opts.background);
+ m_opts.foreground = conf.get(bs, "tray-foreground", m_bar_opts.foreground);
+
+ if (m_opts.background.alpha_i() != 255) {
+ m_log.trace("tray: enable transparency");
+ m_opts.transparent = true;
+ }
+
+ // Add user-defined padding
+ m_opts.spacing += conf.get<unsigned int>(bs, "tray-padding", 0);
+
+ // Add user-defiend offset
+ auto offset_x = conf.get(bs, "tray-offset-x", percentage_with_offset{});
+ auto offset_y = conf.get(bs, "tray-offset-y", percentage_with_offset{});
+
+ int max_x;
+ int max_y;
+
+ if (m_opts.detached) {
+ max_x = m_bar_opts.monitor->w;
+ max_y = m_bar_opts.monitor->h;
+ } else {
+ max_x = inner_area.width;
+ max_y = inner_area.height;
+ }
+
+ m_opts.orig_x += units_utils::percentage_with_offset_to_pixel(offset_x, max_x, m_bar_opts.dpi_x);
+ m_opts.orig_y += units_utils::percentage_with_offset_to_pixel(offset_y, max_y, m_bar_opts.dpi_y);
+ ;
+ m_opts.rel_x = m_opts.orig_x - m_bar_opts.pos.x;
+ m_opts.rel_y = m_opts.orig_y - m_bar_opts.pos.y;
+
+ // Put the tray next to the bar in the window stack
+ m_opts.sibling = m_bar_opts.x_data.window;
+
+ // Activate the tray manager
+ query_atom();
+ activate();
+}
+
+/**
+ * Get the settings container
+ */
+const tray_settings tray_manager::settings() const {
+ return m_opts;
+}
+
+/**
+ * Activate systray management
+ */
+void tray_manager::activate() {
+ if (m_activated) {
+ return;
+ }
+
+ m_log.info("Activating tray manager");
+ m_activated = true;
+ m_opts.running = true;
+
+ m_sig.attach(this);
+
+ try {
+ create_window();
+ create_bg();
+ restack_window();
+ set_wm_hints();
+ set_tray_colors();
+ } catch (const exception& err) {
+ m_log.err(err.what());
+ m_log.err("Cannot activate tray manager... failed to setup window");
+ m_activated = false;
+ return;
+ }
+
+ // Attempt to get control of the systray selection then
+ // notify clients waiting for a manager.
+ acquire_selection();
+
+ if (!m_acquired_selection) {
+ deactivate();
+ return;
+ }
+
+ // Send delayed notification
+ if (!m_firstactivation) {
+ notify_clients();
+ } else {
+ notify_clients_delayed();
+ }
+
+ m_firstactivation = false;
+}
+
+/**
+ * Deactivate systray management
+ */
+void tray_manager::deactivate(bool clear_selection) {
+ if (!m_activated) {
+ return;
+ }
+
+ m_log.info("Deactivating tray manager");
+ m_activated = false;
+ m_opts.running = false;
+
+ m_sig.detach(this);
+
+ if (!m_connection.connection_has_error() && clear_selection && m_acquired_selection) {
+ m_log.trace("tray: Unset selection owner");
+ m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
+ }
+
+ m_log.trace("tray: Unembed clients");
+ m_clients.clear();
+
+ if (m_tray) {
+ m_log.trace("tray: Destroy window");
+ m_connection.destroy_window(m_tray);
+ }
+ m_context.reset();
+ m_surface.reset();
+ if (m_pixmap) {
+ m_connection.free_pixmap(m_pixmap);
+ }
+ if (m_gc) {
+ m_connection.free_gc(m_pixmap);
+ }
+
+ m_tray = 0;
+ m_pixmap = 0;
+ m_gc = 0;
+ m_prevwidth = 0;
+ m_prevheight = 0;
+ m_opts.configured_x = 0;
+ m_opts.configured_y = 0;
+ m_opts.configured_w = 0;
+ m_opts.configured_h = 0;
+ m_opts.configured_slots = 0;
+ m_acquired_selection = false;
+ m_mapped = false;
+
+ m_connection.flush();
+
+ m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure tray
+ */
+void tray_manager::reconfigure() {
+ if (!m_tray) {
+ return;
+ } else if (m_mtx.try_lock()) {
+ std::unique_lock<mutex> guard(m_mtx, std::adopt_lock);
+
+ try {
+ reconfigure_clients();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray clients (%s)", err.what());
+ }
+ try {
+ reconfigure_window();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray window (%s)", err.what());
+ }
+ try {
+ reconfigure_bg();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray background (%s)", err.what());
+ }
+
+ m_opts.configured_slots = mapped_clients();
+ guard.unlock();
+ refresh_window();
+ m_connection.flush();
+ }
+
+ m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure container window
+ */
+void tray_manager::reconfigure_window() {
+ m_log.trace("tray: Reconfigure window (mapped=%i, clients=%i)", static_cast<bool>(m_mapped), m_clients.size());
+
+ if (!m_tray) {
+ return;
+ }
+
+ auto clients = mapped_clients();
+ if (!clients && m_mapped) {
+ m_log.trace("tray: Reconfigure window / unmap");
+ m_connection.unmap_window_checked(m_tray);
+ } else if (clients && !m_mapped && !m_hidden) {
+ m_log.trace("tray: Reconfigure window / map");
+ m_connection.map_window_checked(m_tray);
+ }
+
+ auto width = calculate_w();
+ auto x = calculate_x(width);
+
+ if (m_opts.transparent) {
+ xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()};
+ m_bg_slice = m_background_manager.observe(rect, m_tray);
+ }
+
+ if (width > 0) {
+ m_log.trace("tray: New window values, width=%d, x=%d", width, x);
+
+ unsigned int mask = 0;
+ array<uint32_t, 32> values{};
+ xcb_params_configure_window_t params{};
+
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, width, width);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, x, x);
+ connection::pack_values(mask, ¶ms, values);
+ m_connection.configure_window_checked(m_tray, mask, values.data());
+ }
+
+ m_opts.configured_w = width;
+ m_opts.configured_x = x;
+}
+
+/**
+ * Reconfigure clients
+ */
+void tray_manager::reconfigure_clients() {
+ m_log.trace("tray: Reconfigure clients");
+
+ int x = m_opts.spacing;
+
+ for (auto it = m_clients.rbegin(); it != m_clients.rend(); it++) {
+ auto client = *it;
+
+ try {
+ client->ensure_state();
+ client->reconfigure(x, calculate_client_y());
+
+ x += m_opts.width + m_opts.spacing;
+ } catch (const xpp::x::error::window& err) {
+ remove_client(client, false);
+ }
+ }
+}
+
+/**
+ * Reconfigure root pixmap
+ */
+void tray_manager::reconfigure_bg(bool realloc) {
+ if (!m_opts.transparent || m_clients.empty() || !m_mapped) {
+ return;
+ };
+
+ m_log.trace("tray: Reconfigure bg (realloc=%i)", realloc);
+
+ if (!m_context) {
+ return m_log.err("tray: no context for drawing the background");
+ }
+
+ cairo::surface* surface = m_bg_slice->get_surface();
+ if (!surface) {
+ return;
+ }
+
+ m_context->clear();
+ *m_context << CAIRO_OPERATOR_SOURCE << *m_surface;
+ cairo_set_source_surface(*m_context, *surface, 0, 0);
+ m_context->paint();
+ *m_context << CAIRO_OPERATOR_OVER << m_opts.background;
+ m_context->paint();
+}
+
+/**
+ * Refresh the bar window by clearing it along with each client window
+ */
+void tray_manager::refresh_window() {
+ if (!m_activated || !m_mapped || !m_mtx.try_lock()) {
+ return;
+ }
+
+ std::lock_guard<mutex> lock(m_mtx, std::adopt_lock);
+
+ m_log.trace("tray: Refreshing window");
+
+ auto width = calculate_w();
+ auto height = calculate_h();
+
+ if (m_opts.transparent && !m_context) {
+ xcb_rectangle_t rect{0, 0, static_cast<uint16_t>(width), static_cast<uint16_t>(height)};
+ m_connection.poly_fill_rectangle(m_pixmap, m_gc, 1, &rect);
+ }
+
+ if (m_surface) {
+ m_surface->flush();
+ }
+
+ m_connection.clear_area(0, m_tray, 0, 0, width, height);
+
+ for (auto&& client : m_clients) {
+ try {
+ if (client->mapped()) {
+ client->clear_window();
+ }
+ } catch (const std::exception& e) {
+ m_log.err("Failed to clear tray client %s '%s' (%s)", m_connection.id(client->window()),
+ ewmh_util::get_wm_name(client->window()), e.what());
+ }
+ }
+
+ m_connection.flush();
+
+ if (!mapped_clients()) {
+ m_opts.configured_w = 0;
+ } else {
+ m_opts.configured_w = width;
+ }
+}
+
+/**
+ * Redraw window
+ */
+void tray_manager::redraw_window(bool realloc_bg) {
+ m_log.info("Redraw tray container (id=%s)", m_connection.id(m_tray));
+ reconfigure_bg(realloc_bg);
+ refresh_window();
+}
+
+/**
+ * Find the systray selection atom
+ */
+void tray_manager::query_atom() {
+ m_log.trace("tray: Find systray selection atom for the default screen");
+ string name{"_NET_SYSTEM_TRAY_S" + to_string(m_connection.default_screen())};
+ auto reply = m_connection.intern_atom(false, name.length(), name.c_str());
+ m_atom = reply.atom();
+}
+
+/**
+ * Create tray window
+ */
+void tray_manager::create_window() {
+ m_log.trace("tray: Create tray window");
+
+ // clang-format off
+ auto win = winspec(m_connection, m_tray)
+ << cw_size(calculate_w(), calculate_h())
+ << cw_pos(calculate_x(calculate_w()), calculate_y())
+ << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
+ << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
+ << cw_params_event_mask(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
+ |XCB_EVENT_MASK_STRUCTURE_NOTIFY
+ |XCB_EVENT_MASK_EXPOSURE)
+ << cw_params_override_redirect(true);
+ // clang-format on
+
+ if (!m_opts.transparent) {
+ win << cw_params_back_pixel(m_opts.background);
+ win << cw_params_border_pixel(m_opts.background);
+ }
+
+ m_tray = win << cw_flush(true);
+ m_log.info("Tray window: %s", m_connection.id(m_tray));
+
+ // activate the background manager if we have transparency
+ if (m_opts.transparent) {
+ xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()};
+ m_bg_slice = m_background_manager.observe(rect, m_tray);
+ }
+
+ const unsigned int shadow{0};
+ m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _COMPTON_SHADOW, XCB_ATOM_CARDINAL, 32, 1, &shadow);
+}
+
+/**
+ * Create tray window background components
+ */
+void tray_manager::create_bg(bool realloc) {
+ if (!m_opts.transparent) {
+ return;
+ }
+ if (!realloc && m_pixmap && m_gc && m_surface && m_context) {
+ return;
+ }
+ if (realloc && m_pixmap) {
+ m_connection.free_pixmap(m_pixmap);
+ m_pixmap = 0;
+ }
+ if (realloc && m_gc) {
+ m_connection.free_gc(m_gc);
+ m_gc = 0;
+ }
+
+ if (realloc && m_surface) {
+ m_surface.reset();
+ }
+ if (realloc && m_context) {
+ m_context.reset();
+ }
+
+ auto w = m_opts.width_max;
+ auto h = calculate_h();
+
+ if (!m_pixmap) {
+ try {
+ m_pixmap = m_connection.generate_id();
+ m_connection.create_pixmap_checked(m_connection.screen()->root_depth, m_pixmap, m_tray, w, h);
+ } catch (const exception& err) {
+ return m_log.err("Failed to create pixmap for tray background (err: %s)", err.what());
+ }
+ }
+
+ if (!m_gc) {
+ try {
+ xcb_params_gc_t params{};
+ unsigned int mask = 0;
+ array<uint32_t, 32> values{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 1);
+ connection::pack_values(mask, ¶ms, values);
+ m_gc = m_connection.generate_id();
+ m_connection.create_gc_checked(m_gc, m_pixmap, mask, values.data());
+ } catch (const exception& err) {
+ return m_log.err("Failed to create gcontext for tray background (err: %s)", err.what());
+ }
+ }
+
+ if (!m_surface) {
+ xcb_visualtype_t* visual = m_connection.visual_type_for_id(m_connection.screen()->root_visual);
+ if (!visual) {
+ return m_log.err("Failed to get root visual for tray background");
+ }
+ m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, w, h);
+ }
+
+ if (!m_context) {
+ m_context = make_unique<cairo::context>(*m_surface, m_log);
+ m_context->clear();
+ *m_context << CAIRO_OPERATOR_SOURCE << m_opts.background;
+ m_context->paint();
+ }
+
+ try {
+ m_connection.change_window_attributes_checked(m_tray, XCB_CW_BACK_PIXMAP, &m_pixmap);
+ } catch (const exception& err) {
+ m_log.err("Failed to set tray window back pixmap (%s)", err.what());
+ }
+}
+
+/**
+ * Put tray window above the defined sibling in the window stack
+ */
+void tray_manager::restack_window() {
+ if (m_opts.sibling == XCB_NONE) {
+ return;
+ }
+
+ try {
+ m_log.trace("tray: Restacking tray window");
+
+ unsigned int mask = 0;
+ array<uint32_t, 32> values{};
+ xcb_params_configure_window_t params{};
+
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, sibling, m_opts.sibling);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, stack_mode, XCB_STACK_MODE_ABOVE);
+
+ connection::pack_values(mask, ¶ms, values);
+ m_connection.configure_window_checked(m_tray, mask, values.data());
+ } catch (const exception& err) {
+ auto id = m_connection.id(m_opts.sibling);
+ m_log.err("tray: Failed to put tray above %s in the stack (%s)", id, err.what());
+ }
+}
+
+/**
+ * Set window WM hints
+ */
+void tray_manager::set_wm_hints() {
+ const unsigned int visual{m_connection.screen()->root_visual};
+ const unsigned int orientation{_NET_SYSTEM_TRAY_ORIENTATION_HORZ};
+
+ m_log.trace("bar: Set window WM_NAME / WM_CLASS");
+ icccm_util::set_wm_name(m_connection, m_tray, TRAY_WM_NAME, 19_z, TRAY_WM_CLASS, 12_z);
+
+ m_log.trace("tray: Set window WM_PROTOCOLS");
+ icccm_util::set_wm_protocols(m_connection, m_tray, {WM_DELETE_WINDOW, WM_TAKE_FOCUS});
+
+ m_log.trace("tray: Set window _NET_WM_WINDOW_TYPE");
+ ewmh_util::set_wm_window_type(m_tray, {_NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_NORMAL});
+
+ m_log.trace("tray: Set window _NET_WM_STATE");
+ ewmh_util::set_wm_state(m_tray, {_NET_WM_STATE_SKIP_TASKBAR});
+
+ m_log.trace("tray: Set window _NET_WM_PID");
+ ewmh_util::set_wm_pid(m_tray);
+
+ m_log.trace("tray: Set window _NET_SYSTEM_TRAY_VISUAL");
+ xcb_change_property(
+ m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_VISUAL, XCB_ATOM_VISUALID, 32, 1, &visual);
+
+ m_log.trace("tray: Set window _NET_SYSTEM_TRAY_ORIENTATION");
+ xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_ORIENTATION,
+ _NET_SYSTEM_TRAY_ORIENTATION, 32, 1, &orientation);
+}
+
+/**
+ * Set color atom used by clients when determing icon theme
+ */
+void tray_manager::set_tray_colors() {
+ m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to %x", m_opts.foreground);
+
+ auto r = m_opts.foreground.red_i();
+ auto g = m_opts.foreground.green_i();
+ auto b = m_opts.foreground.blue_i();
+
+ const uint16_t r16 = (r << 8) | r;
+ const uint16_t g16 = (g << 8) | g;
+ const uint16_t b16 = (b << 8) | b;
+
+ const uint32_t colors[12] = {
+ r16, g16, b16, // normal
+ r16, g16, b16, // error
+ r16, g16, b16, // warning
+ r16, g16, b16, // success
+ };
+
+ m_connection.change_property(
+ XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32, 12, colors);
+}
+
+/**
+ * Acquire the systray selection
+ */
+void tray_manager::acquire_selection() {
+ m_othermanager = XCB_NONE;
+ xcb_window_t owner;
+
+ try {
+ owner = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
+ } catch (const exception& err) {
+ return;
+ }
+
+ if (owner == m_tray) {
+ m_log.trace("tray: Already managing the systray selection");
+ m_acquired_selection = true;
+ } else if ((m_othermanager = owner) != XCB_NONE) {
+ m_log.warn("Systray selection already managed (window=%s)", m_connection.id(owner));
+ track_selection_owner(m_othermanager);
+ } else {
+ m_log.trace("tray: Change selection owner to %s", m_connection.id(m_tray));
+ m_connection.set_selection_owner_checked(m_tray, m_atom, XCB_CURRENT_TIME);
+ if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_tray) {
+ throw application_error("Failed to get control of the systray selection");
+ }
+ m_acquired_selection = true;
+ }
+}
+
+/**
+ * Notify pending clients about the new systray MANAGER
+ */
+void tray_manager::notify_clients() {
+ if (m_activated) {
+ m_log.info("Notifying pending tray clients");
+ auto message = m_connection.make_client_message(MANAGER, m_connection.root());
+ message.data.data32[0] = XCB_CURRENT_TIME;
+ message.data.data32[1] = m_atom;
+ message.data.data32[2] = m_tray;
+ m_connection.send_client_message(message, m_connection.root());
+ }
+}
+
+/**
+ * Send delayed notification to pending clients
+ */
+void tray_manager::notify_clients_delayed() {
+ if (m_delaythread.joinable()) {
+ m_delaythread.join();
+ }
+ m_delaythread = thread([this]() {
+ this_thread::sleep_for(1s);
+ notify_clients();
+ });
+}
+
+/**
+ * Track changes to the given selection owner
+ * If it gets destroyed or goes away we can reactivate the tray_manager
+ */
+void tray_manager::track_selection_owner(xcb_window_t owner) {
+ if (owner != XCB_NONE) {
+ m_log.trace("tray: Listen for events on the new selection window");
+ const unsigned int mask{XCB_CW_EVENT_MASK};
+ const unsigned int values[]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.change_window_attributes(owner, mask, values);
+ }
+}
+
+/**
+ * Process client docking request
+ */
+void tray_manager::process_docking_request(xcb_window_t win) {
+ m_log.info("Processing docking request from '%s' (%s)", ewmh_util::get_wm_name(win), m_connection.id(win));
+
+ m_clients.emplace_back(std::make_shared<tray_client>(m_connection, win, m_opts.width, m_opts.height));
+ auto& client = m_clients.back();
+
+ try {
+ client->query_xembed();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
+ remove_client(win, true);
+ return;
+ }
+
+ m_log.trace("tray: xembed = %s", client->is_xembed_supported() ? "true" : "false");
+ if (client->is_xembed_supported()) {
+ m_log.trace("tray: version = 0x%x, flags = 0x%x, XEMBED_MAPPED = %s", client->get_xembed().get_version(),
+ client->get_xembed().get_flags(), client->get_xembed().is_mapped() ? "true" : "false");
+ }
+
+ try {
+ const unsigned int mask = XCB_CW_EVENT_MASK;
+ const unsigned int values[]{XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+
+ m_log.trace("tray: Update client window");
+ m_connection.change_window_attributes_checked(client->window(), mask, values);
+
+ m_log.trace("tray: Configure client size");
+ client->reconfigure(0, 0);
+
+ m_log.trace("tray: Add client window to the save set");
+ m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client->window());
+
+ m_log.trace("tray: Reparent client");
+ m_connection.reparent_window_checked(
+ client->window(), m_tray, calculate_client_x(client->window()), calculate_client_y());
+
+ if (client->is_xembed_supported()) {
+ m_log.trace("tray: Send embbeded notification to client");
+ xembed::notify_embedded(m_connection, client->window(), m_tray, client->get_xembed().get_version());
+ }
+
+ if (!client->is_xembed_supported() || client->get_xembed().is_mapped()) {
+ m_log.trace("tray: Map client");
+ m_connection.map_window_checked(client->window());
+ }
+
+ } catch (const std::exception& err) {
+ m_log.err("Failed to setup tray client removing... (%s)", err.what());
+ remove_client(win, false);
+ }
+}
+
+/**
+ * Calculate x position of tray window
+ */
+int tray_manager::calculate_x(unsigned int width) const {
+ auto x = m_opts.orig_x;
+ if (m_opts.tray_position == tray_postition::RIGHT) {
+ x -= ((m_opts.width + m_opts.spacing) * m_clients.size() + m_opts.spacing);
+ } else if (m_opts.tray_position == tray_postition::CENTER) {
+ x -= (width / 2) - (m_opts.width / 2);
+ }
+ return x;
+}
+
+/**
+ * Calculate y position of tray window
+ */
+int tray_manager::calculate_y(bool abspos) const {
+ return abspos ? m_opts.orig_y : m_opts.rel_y;
+}
+
+/**
+ * Calculate width of tray window
+ */
+unsigned short int tray_manager::calculate_w() const {
+ unsigned int width = m_opts.spacing;
+ unsigned int count{0};
+ for (auto&& client : m_clients) {
+ if (client->mapped()) {
+ count++;
+ width += m_opts.spacing + m_opts.width;
+ }
+ }
+ return count ? width : 0;
+}
+
+/**
+ * Calculate height of tray window
+ */
+unsigned short int tray_manager::calculate_h() const {
+ return m_opts.height_fill;
+}
+
+/**
+ * Calculate x position of client window
+ */
+int tray_manager::calculate_client_x(const xcb_window_t& win) {
+ for (unsigned int i = 0; i < m_clients.size(); i++) {
+ if (m_clients[i]->match(win)) {
+ return m_opts.spacing + m_opts.width * i;
+ }
+ }
+ return m_opts.spacing;
+}
+
+/**
+ * Calculate y position of client window
+ */
+int tray_manager::calculate_client_y() {
+ return (m_opts.height_fill - m_opts.height) / 2;
+}
+
+/**
+ * Check if the given window is embedded
+ */
+bool tray_manager::is_embedded(const xcb_window_t& win) const {
+ return m_clients.end() != std::find_if(m_clients.begin(), m_clients.end(),
+ [win](shared_ptr<tray_client> client) { return client->match(win); });
+}
+
+/**
+ * Find tray client by window
+ */
+shared_ptr<tray_client> tray_manager::find_client(const xcb_window_t& win) const {
+ for (auto&& client : m_clients) {
+ if (client->match(win)) {
+ return client;
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Remove tray client
+ */
+void tray_manager::remove_client(shared_ptr<tray_client>& client, bool reconfigure) {
+ remove_client(client->window(), reconfigure);
+}
+
+/**
+ * Remove tray client by window
+ */
+void tray_manager::remove_client(xcb_window_t win, bool reconfigure) {
+ m_clients.erase(std::remove_if(
+ m_clients.begin(), m_clients.end(), [win](shared_ptr<tray_client> client) { return client->match(win); }));
+
+ if (reconfigure) {
+ tray_manager::reconfigure();
+ }
+}
+
+/**
+ * Get number of mapped clients
+ */
+unsigned int tray_manager::mapped_clients() const {
+ unsigned int mapped_clients = 0;
+
+ for (auto&& client : m_clients) {
+ if (client->mapped()) {
+ mapped_clients++;
+ }
+ }
+
+ return mapped_clients;
+}
+
+bool tray_manager::change_visibility(bool visible) {
+ unsigned int clients{mapped_clients()};
+
+ m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
+ static_cast<bool>(m_activated), static_cast<bool>(m_mapped), static_cast<bool>(m_hidden));
+
+ m_hidden = !visible;
+
+ if (!m_activated) {
+ return false;
+ } else if (!m_hidden && !m_mapped && clients) {
+ m_connection.map_window(m_tray);
+ } else if ((!clients || m_hidden) && m_mapped) {
+ m_connection.unmap_window(m_tray);
+ } else if (m_mapped && !m_hidden && clients) {
+ redraw_window();
+ }
+
+ m_connection.flush();
+
+ return true;
+}
+
+/**
+ * Event callback : XCB_EXPOSE
+ */
+void tray_manager::handle(const evt::expose& evt) {
+ if (m_activated && !m_clients.empty() && evt->count == 0) {
+ redraw_window();
+ }
+}
+
+/**
+ * Event callback : XCB_VISIBILITY_NOTIFY
+ */
+void tray_manager::handle(const evt::visibility_notify& evt) {
+ if (m_activated && !m_clients.empty()) {
+ m_log.trace("tray: Received visibility_notify for %s", m_connection.id(evt->window));
+ reconfigure_window();
+ }
+}
+
+/**
+ * Event callback : XCB_CLIENT_MESSAGE
+ */
+void tray_manager::handle(const evt::client_message& evt) {
+ if (!m_activated) {
+ return;
+ } else if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_tray) {
+ m_log.notice("Received WM_DELETE");
+ m_tray = 0;
+ deactivate();
+ } else if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
+ m_log.trace("tray: Received client_message");
+
+ if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
+ if (!is_embedded(evt->data.data32[2])) {
+ process_docking_request(evt->data.data32[2]);
+ } else {
+ auto win = evt->data.data32[2];
+ m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
+ }
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_CONFIGURE_REQUEST
+ *
+ * Called when a tray client thinks he's part of the free world and
+ * wants to reconfigure its window. This is of course nothing we appreciate
+ * so we return an answer that'll put him in place.
+ */
+void tray_manager::handle(const evt::configure_request& evt) {
+ if (m_activated && is_embedded(evt->window)) {
+ try {
+ m_log.trace("tray: Client configure request %s", m_connection.id(evt->window));
+ find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * @see tray_manager::handle(const evt::configure_request&);
+ */
+void tray_manager::handle(const evt::resize_request& evt) {
+ if (m_activated && is_embedded(evt->window)) {
+ try {
+ m_log.trace("tray: Received resize_request for client %s", m_connection.id(evt->window));
+ find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_SELECTION_CLEAR
+ */
+void tray_manager::handle(const evt::selection_clear& evt) {
+ if (!m_activated) {
+ return;
+ } else if (evt->selection != m_atom) {
+ return;
+ } else if (evt->owner != m_tray) {
+ return;
+ }
+
+ try {
+ m_log.warn("Lost systray selection, deactivating...");
+ m_othermanager = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
+ track_selection_owner(m_othermanager);
+ } catch (const exception& err) {
+ m_log.err("Failed to get systray selection owner");
+ m_othermanager = XCB_NONE;
+ }
+
+ deactivate(false);
+}
+
+/**
+ * Event callback : XCB_PROPERTY_NOTIFY
+ */
+void tray_manager::handle(const evt::property_notify& evt) {
+ if (!m_activated) {
+ return;
+ }
+
+ // React an wallpaper change, if bar has transparency
+ if (m_opts.transparent &&
+ (evt->atom == _XROOTPMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID)) {
+ redraw_window(true);
+ return;
+ }
+
+ if (evt->atom != _XEMBED_INFO) {
+ return;
+ }
+
+ auto client = find_client(evt->window);
+
+ if (!client) {
+ return;
+ }
+
+ m_log.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
+
+ auto win = client->window();
+
+ if (evt->state == XCB_PROPERTY_NEW_VALUE) {
+ m_log.trace("tray: _XEMBED_INFO value has changed");
+ }
+
+ try {
+ client->query_xembed();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
+ remove_client(win, true);
+ return;
+ }
+
+ m_log.trace("tray: version = 0x%x, flags = 0x%x, XEMBED_MAPPED = %s", client->get_xembed().get_version(),
+ client->get_xembed().get_flags(), client->get_xembed().is_mapped() ? "true" : "false");
+
+ if (client->get_xembed().is_mapped()) {
+ reconfigure();
+ }
+}
+
+/**
+ * Event callback : XCB_REPARENT_NOTIFY
+ */
+void tray_manager::handle(const evt::reparent_notify& evt) {
+ if (m_activated && is_embedded(evt->window) && evt->parent != m_tray) {
+ m_log.trace("tray: Received reparent_notify for client, remove...");
+ remove_client(evt->window);
+ }
+}
+
+/**
+ * Event callback : XCB_DESTROY_NOTIFY
+ */
+void tray_manager::handle(const evt::destroy_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ deactivate();
+ } else if (!m_activated && evt->window == m_othermanager) {
+ m_log.info("Systray selection unmanaged... re-activating");
+ activate();
+ } else if (m_activated && is_embedded(evt->window)) {
+ m_log.trace("tray: Received destroy_notify for client, remove...");
+ remove_client(evt->window);
+ redraw_window();
+ }
+}
+
+/**
+ * Event callback : XCB_MAP_NOTIFY
+ */
+void tray_manager::handle(const evt::map_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ m_log.trace("tray: Received map_notify");
+ m_log.trace("tray: Update container mapped flag");
+ m_mapped = true;
+ redraw_window();
+ } else if (is_embedded(evt->window)) {
+ m_log.trace("tray: Received map_notify");
+ m_log.trace("tray: Set client mapped");
+ find_client(evt->window)->mapped(true);
+ unsigned int clientcount{mapped_clients()};
+ if (clientcount > m_opts.configured_slots) {
+ reconfigure();
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_UNMAP_NOTIFY
+ */
+void tray_manager::handle(const evt::unmap_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ m_log.trace("tray: Received unmap_notify");
+ m_log.trace("tray: Update container mapped flag");
+ m_mapped = false;
+ } else if (m_activated && is_embedded(evt->window)) {
+ m_log.trace("tray: Received unmap_notify");
+ m_log.trace("tray: Set client unmapped");
+ find_client(evt->window)->mapped(false);
+ }
+}
+
+/**
+ * Signal handler connected to the bar window's visibility change signal.
+ * This is used as a fallback in case the window restacking fails. It will
+ * toggle the tray window whenever the visibility of the bar window changes.
+ */
+bool tray_manager::on(const signals::ui::visibility_change& evt) {
+ return change_visibility(evt.cast());
+}
+
+bool tray_manager::on(const signals::ui::dim_window& evt) {
+ if (m_activated) {
+ ewmh_util::set_wm_window_opacity(m_tray, evt.cast() * 0xFFFFFFFF);
+ }
+ // let the event bubble
+ return false;
+}
+
+bool tray_manager::on(const signals::ui::update_background&) {
+ redraw_window(true);
+
+ return false;
+}
+
+bool tray_manager::on(const signals::ui_tray::tray_pos_change& evt) {
+ m_opts.orig_x =
+ m_bar_opts.inner_area(true).x + std::max(0, std::min(evt.cast(), (int)(m_bar_opts.size.w - calculate_w())));
+
+ reconfigure_window();
+
+ return true;
+}
+
+bool tray_manager::on(const signals::ui_tray::tray_visibility& evt) {
+ if (evt.cast() == m_hidden && m_opts.tray_position == tray_postition::MODULE) {
+ return change_visibility(evt.cast());
+ } else {
+ return true;
+ }
+}
+
+tray_client::tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h)
+ : m_connection(conn), m_window(win), m_width(w), m_height(h) {}
+
+tray_client::~tray_client() {
+ xembed::unembed(m_connection, window(), m_connection.root());
+}
+
+unsigned int tray_client::width() const {
+ return m_width;
+}
+
+unsigned int tray_client::height() const {
+ return m_height;
+}
+
+void tray_client::clear_window() const {
+ m_connection.clear_area_checked(1, window(), 0, 0, width(), height());
+}
+
+/**
+ * Match given window against client window
+ */
+bool tray_client::match(const xcb_window_t& win) const {
+ return win == m_window;
+}
+
+/**
+ * Get client window mapped state
+ */
+bool tray_client::mapped() const {
+ return m_mapped;
+}
+
+/**
+ * Set client window mapped state
+ */
+void tray_client::mapped(bool state) {
+ m_mapped = state;
+}
+
+/**
+ * Get client window
+ */
+xcb_window_t tray_client::window() const {
+ return m_window;
+}
+
+void tray_client::query_xembed() {
+ m_xembed_supported = xembed::query(m_connection, m_window, m_xembed);
+}
+
+bool tray_client::is_xembed_supported() const {
+ return m_xembed_supported;
+}
+
+const xembed::info& tray_client::get_xembed() const {
+ return m_xembed;
+}
+
+/**
+ * Make sure that the window mapping state is correct
+ */
+void tray_client::ensure_state() const {
+ bool should_be_mapped = true;
+
+ if (is_xembed_supported()) {
+ should_be_mapped = m_xembed.is_mapped();
+ }
+
+ if (!mapped() && should_be_mapped) {
+ m_connection.map_window_checked(window());
+ } else if (mapped() && !should_be_mapped) {
+ m_connection.unmap_window_checked(window());
+ }
+}
+
+/**
+ * Configure window size
+ */
+void tray_client::reconfigure(int x, int y) const {
+ unsigned int configure_mask = 0;
+ array<uint32_t, 32> configure_values{};
+ xcb_params_configure_window_t configure_params{};
+
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_width);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_height);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, x);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, y);
+
+ connection::pack_values(configure_mask, &configure_params, configure_values);
+ m_connection.configure_window_checked(window(), configure_mask, configure_values.data());
+}
+
+/**
+ * Respond to client resize requests
+ */
+void tray_client::configure_notify(int x, int y) const {
+ xcb_configure_notify_event_t notify;
+ notify.response_type = XCB_CONFIGURE_NOTIFY;
+ notify.event = m_window;
+ notify.window = m_window;
+ notify.override_redirect = false;
+ notify.above_sibling = 0;
+ notify.x = x;
+ notify.y = y;
+ notify.width = m_width;
+ notify.height = m_height;
+ notify.border_width = 0;
+
+ unsigned int mask{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.send_event_checked(false, m_window, mask, reinterpret_cast<const char*>(¬ify));
+}
+
+} // namespace legacy_tray
+
+POLYBAR_NS_END
--- /dev/null
+#include <xpp/event.hpp>
+
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+
+POLYBAR_NS
+
+registry::registry(connection& conn) : xpp::event::registry<connection&, XPP_EXTENSION_LIST>(conn) {}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/tray_client.hpp"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_icccm.h>
+
+#include "utils/memory.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/winspec.hpp"
+#include "xpp/pixmap.hpp"
+
+POLYBAR_NS
+
+namespace tray {
+
+/*
+ * The client window is embedded into a wrapper window with identical, size, depth, and visual.
+ * This wrapper window is used to paint the background of the icon (also dealing with transparent backgrounds through
+ * pseudo-transparency).
+ *
+ * True transprency is currently not supported here because it cannot be achieved with external compositors (those only
+ * seem to work for top-level windows) and has to be implemented by hand.
+ */
+client::client(
+ const logger& log, connection& conn, xcb_window_t parent, xcb_window_t win, size s, rgba desired_background)
+ : m_log(log)
+ , m_connection(conn)
+ , m_name(ewmh_util::get_wm_name(win))
+ , m_client(win)
+ , m_size(s)
+ , m_desired_background(desired_background)
+ , m_background_manager(background_manager::make()) {
+ auto geom = conn.get_geometry(win);
+ auto attrs = conn.get_window_attributes(win);
+ int client_depth = geom->depth;
+ auto client_visual = attrs->visual;
+ auto client_colormap = attrs->colormap;
+
+ m_log.trace("%s: depth: %u, width: %u, height: %u", name(), client_depth, geom->width, geom->height);
+
+ /*
+ * Create embedder window for tray icon
+ *
+ * The embedder window inherits the depth, visual and color map from the icon window in order for reparenting to
+ * always work, even if the icon window uses ParentRelative for some of its pixmaps (back pixmap or border pixmap).
+ */
+ // clang-format off
+ m_wrapper = winspec(conn)
+ << cw_size(s.w, s.h)
+ << cw_pos(0, 0)
+ << cw_depth(client_depth)
+ << cw_visual(client_visual)
+ << cw_parent(parent)
+ << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
+ // The X server requires the border pixel to be defined if the depth doesn't match the parent (bar) window
+ << cw_params_border_pixel(conn.screen()->black_pixel)
+ << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
+ << cw_params_save_under(true)
+ << cw_params_event_mask(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
+ | XCB_EVENT_MASK_PROPERTY_CHANGE
+ | XCB_EVENT_MASK_STRUCTURE_NOTIFY
+ | XCB_EVENT_MASK_EXPOSURE)
+ << cw_params_colormap(client_colormap)
+ << cw_flush(true);
+ // clang-format on
+
+ try {
+ m_pixmap = m_connection.generate_id();
+ m_connection.create_pixmap_checked(client_depth, m_pixmap, m_wrapper, s.w, s.h);
+ } catch (const std::exception& err) {
+ m_pixmap = XCB_NONE;
+ m_log.err("Failed to create pixmap for tray background (err: %s)", err.what());
+ throw;
+ }
+
+ try {
+ m_connection.change_window_attributes_checked(m_wrapper, XCB_CW_BACK_PIXMAP, &m_pixmap);
+ } catch (const std::exception& err) {
+ m_log.err("Failed to set tray window back pixmap (%s)", err.what());
+ throw;
+ }
+
+ xcb_visualtype_t* visual = m_connection.visual_type_for_id(client_visual);
+ if (!visual) {
+ throw std::runtime_error("Failed to get root visual for tray background");
+ }
+
+ m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, s.w, s.h);
+ m_context = make_unique<cairo::context>(*m_surface, m_log);
+
+ observe_background();
+}
+
+client::~client() {
+ if (m_client != XCB_NONE) {
+ xembed::unembed(m_connection, m_client, m_connection.root());
+ }
+
+ if (m_wrapper != XCB_NONE) {
+ m_connection.destroy_window(m_wrapper);
+ }
+
+ if (m_pixmap != XCB_NONE) {
+ m_connection.free_pixmap(m_pixmap);
+ }
+}
+
+string client::name() const {
+ return "client(" + m_connection.id(m_client) + ", " + m_name + ")";
+}
+
+unsigned int client::width() const {
+ return m_size.w;
+}
+
+unsigned int client::height() const {
+ return m_size.h;
+}
+
+void client::clear_window() const {
+ if (!mapped()) {
+ return;
+ }
+
+ // Do not produce Expose events for the embedder because that triggers an infinite loop.
+ m_connection.clear_area_checked(0, embedder(), 0, 0, width(), height());
+
+ auto send_visibility = [&](uint8_t state) {
+ xcb_visibility_notify_event_t evt{};
+ evt.response_type = XCB_VISIBILITY_NOTIFY;
+ evt.window = client_window();
+ evt.state = state;
+
+ m_connection.send_event_checked(
+ true, client_window(), XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char*>(&evt));
+ };
+
+ send_visibility(XCB_VISIBILITY_FULLY_OBSCURED);
+ send_visibility(XCB_VISIBILITY_UNOBSCURED);
+
+ m_connection.clear_area_checked(1, client_window(), 0, 0, width(), height());
+}
+
+void client::update_client_attributes() const {
+ uint32_t configure_mask = 0;
+ std::array<uint32_t, 32> configure_values{};
+ xcb_params_cw_t configure_params{};
+
+ XCB_AUX_ADD_PARAM(
+ &configure_mask, &configure_params, event_mask, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY);
+
+ connection::pack_values(configure_mask, &configure_params, configure_values);
+
+ m_log.trace("%s: Update client window", name());
+ m_connection.change_window_attributes_checked(client_window(), configure_mask, configure_values.data());
+}
+
+void client::reparent() const {
+ m_log.trace("%s: Reparent client", name());
+ m_connection.reparent_window_checked(client_window(), embedder(), 0, 0);
+}
+
+/**
+ * Is this the client for the given client window
+ */
+bool client::match(const xcb_window_t& win) const {
+ return win == m_client;
+}
+
+/**
+ * Get client window mapped state
+ */
+bool client::mapped() const {
+ return m_mapped;
+}
+
+/**
+ * Set client window mapped state
+ */
+void client::mapped(bool state) {
+ if (m_mapped != state) {
+ m_log.trace("%s: set mapped: %i", name(), state);
+ m_mapped = state;
+ }
+}
+
+/**
+ * Sets the client window's visibility.
+ *
+ * Use this to trigger a mapping/unmapping
+ */
+void client::hidden(bool state) {
+ m_hidden = state;
+}
+
+/**
+ * Whether the current state indicates the client should be mapped.
+ */
+bool client::should_be_mapped() const {
+ if (m_hidden) {
+ return false;
+ }
+
+ if (is_xembed_supported()) {
+ return m_xembed.is_mapped();
+ }
+
+ return true;
+}
+
+xcb_window_t client::embedder() const {
+ return m_wrapper;
+}
+
+xcb_window_t client::client_window() const {
+ return m_client;
+}
+
+void client::query_xembed() {
+ m_xembed_supported = xembed::query(m_connection, m_client, m_xembed);
+
+ if (is_xembed_supported()) {
+ m_log.trace("%s: %s", name(), get_xembed().to_string());
+ } else {
+ m_log.trace("%s: no xembed");
+ }
+}
+
+bool client::is_xembed_supported() const {
+ return m_xembed_supported;
+}
+
+const xembed::info& client::get_xembed() const {
+ return m_xembed;
+}
+
+void client::notify_xembed() const {
+ if (is_xembed_supported()) {
+ m_log.trace("%s: Send embedded notification to client", name());
+ xembed::notify_embedded(m_connection, client_window(), embedder(), m_xembed.get_version());
+ }
+}
+
+void client::add_to_save_set() const {
+ m_log.trace("%s: Add client window to the save set", name());
+ m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client_window());
+}
+
+/**
+ * Make sure that the window mapping state is correct
+ */
+void client::ensure_state() const {
+ bool new_state = should_be_mapped();
+
+ if (new_state == m_mapped) {
+ return;
+ }
+
+ m_log.trace("%s: ensure_state (hidden=%i, mapped=%i, should_be_mapped=%i)", name(), m_hidden, m_mapped, new_state);
+
+ if (new_state) {
+ m_log.trace("%s: Map client", name());
+ m_connection.map_window_checked(embedder());
+ m_connection.map_window_checked(client_window());
+ } else {
+ m_log.trace("%s: Unmap client", name());
+ m_connection.unmap_window_checked(client_window());
+ m_connection.unmap_window_checked(embedder());
+ }
+}
+
+/**
+ * Configure window position
+ */
+void client::set_position(int x, int y) {
+ m_log.trace("%s: moving to (%d, %d)", name(), x, y);
+
+ position new_pos{x, y};
+
+ if (new_pos == m_pos) {
+ return;
+ }
+
+ m_pos = new_pos;
+
+ uint32_t configure_mask = 0;
+ array<uint32_t, 32> configure_values{};
+ xcb_params_configure_window_t configure_params{};
+
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_size.w);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_size.h);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, x);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, y);
+ connection::pack_values(configure_mask, &configure_params, configure_values);
+ m_connection.configure_window_checked(embedder(), configure_mask, configure_values.data());
+
+ configure_mask = 0;
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_size.w);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_size.h);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, 0);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, 0);
+ connection::pack_values(configure_mask, &configure_params, configure_values);
+ m_connection.configure_window_checked(client_window(), configure_mask, configure_values.data());
+
+ xcb_size_hints_t size_hints{};
+ xcb_icccm_size_hints_set_size(&size_hints, false, m_size.w, m_size.h);
+ xcb_icccm_set_wm_size_hints(m_connection, client_window(), XCB_ATOM_WM_NORMAL_HINTS, &size_hints);
+
+ // The position has changed, we need a new background slice.
+ observe_background();
+}
+
+/**
+ * Respond to client resize/move requests
+ */
+void client::configure_notify() const {
+ xcb_configure_notify_event_t notify;
+ notify.response_type = XCB_CONFIGURE_NOTIFY;
+ notify.event = client_window();
+ notify.window = client_window();
+ notify.override_redirect = false;
+ notify.above_sibling = 0;
+ notify.x = 0;
+ notify.y = 0;
+ notify.width = m_size.w;
+ notify.height = m_size.h;
+ notify.border_width = 0;
+
+ unsigned int mask{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.send_event_checked(false, client_window(), mask, reinterpret_cast<const char*>(¬ify));
+}
+
+/**
+ * Redraw background using the observed background slice.
+ */
+void client::update_bg() const {
+ m_log.trace("%s: Update background", name());
+
+ m_context->clear();
+
+ // Composite background slice with background color.
+ if (m_bg_slice) {
+ auto root_bg = m_bg_slice->get_surface();
+ if (root_bg != nullptr) {
+ *m_context << CAIRO_OPERATOR_SOURCE << *root_bg;
+ m_context->paint();
+ *m_context << CAIRO_OPERATOR_OVER;
+ }
+ }
+ *m_context << m_desired_background;
+ m_context->paint();
+
+ m_surface->flush();
+
+ clear_window();
+ m_connection.flush();
+}
+
+void client::observe_background() {
+ // Opaque backgrounds don't require pseudo-transparency
+ if (!m_transparent) {
+ return;
+ }
+
+ xcb_rectangle_t rect{0, 0, static_cast<uint16_t>(m_size.w), static_cast<uint16_t>(m_size.h)};
+ m_bg_slice = m_background_manager.observe(rect, embedder());
+
+ update_bg();
+}
+
+} // namespace tray
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/tray_manager.hpp"
+
+#include <xcb/xcb_image.h>
+
+#include <thread>
+#include <utility>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "components/config.hpp"
+#include "errors.hpp"
+#include "events/signal.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/memory.hpp"
+#include "utils/process.hpp"
+#include "utils/units.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+#include "x11/winspec.hpp"
+#include "x11/xembed.hpp"
+
+/*
+ * Tray implementation according to the System Tray Protocol.
+ *
+ * Ref: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
+ *
+ * This class manages embedded tray icons by placing them on the bar in the correct positions; the start position is
+ * requested by the renderer.
+ *
+ * The tray manager needs to trigger bar updates only when the size of the entire tray changes (e.g. when tray icons are
+ * added/removed). Everything else can be handled without an update.
+ */
+
+POLYBAR_NS
+
+namespace tray {
+
+manager::manager(
+ connection& conn, signal_emitter& emitter, const logger& logger, const bar_settings& bar_opts, on_update on_update)
+ : m_connection(conn), m_sig(emitter), m_log(logger), m_bar_opts(bar_opts), m_on_update(std::move(on_update)) {
+ m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
+}
+
+manager::~manager() {
+ m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
+ deactivate();
+}
+
+void manager::setup(const config& conf, const string& section_name) {
+ unsigned bar_height = m_bar_opts.inner_area().height;
+
+ // Spacing between icons
+ auto spacing = conf.get(section_name, "tray-spacing", ZERO_PX_EXTENT);
+ m_opts.spacing = units_utils::extent_to_pixel_nonnegative(spacing, m_bar_opts.dpi_x);
+
+ // Padding before and after each icon
+ auto padding = conf.get(section_name, "tray-padding", ZERO_PX_EXTENT);
+ m_opts.padding = units_utils::extent_to_pixel_nonnegative(padding, m_bar_opts.dpi_x);
+
+ auto size = conf.get(section_name, "tray-size", percentage_with_offset{66., ZERO_PX_EXTENT});
+ unsigned client_height = std::min(
+ bar_height, units_utils::percentage_with_offset_to_pixel_nonnegative(size, bar_height, m_bar_opts.dpi_y));
+
+ if (client_height == 0) {
+ m_log.warn("tray: tray-size has an effective value of 0px, you will not see any tray icons");
+ }
+
+ m_opts.client_size = {client_height, client_height};
+
+ // Set user-defined foreground and background colors.
+ m_opts.background = conf.get(section_name, "tray-background", m_bar_opts.background);
+ m_opts.foreground = conf.get(section_name, "tray-foreground", m_bar_opts.foreground);
+
+ m_opts.selection_owner = m_bar_opts.x_data.window;
+
+ m_log.info("tray: spacing=%upx padding=%upx size=%upx", m_opts.spacing, m_opts.padding, client_height);
+
+ if (m_bar_opts.x_data.window == XCB_NONE) {
+ m_log.err("tray: No bar window found, disabling tray");
+ return;
+ }
+
+ // Activate the tray manager
+ query_atom();
+ activate();
+}
+
+unsigned manager::get_width() const {
+ return m_tray_width;
+}
+
+bool manager::is_active() const {
+ return m_state == state::ACTIVE;
+}
+
+bool manager::is_inactive() const {
+ return m_state == state::INACTIVE;
+}
+
+bool manager::is_waiting() const {
+ return m_state == state::WAITING;
+}
+
+bool manager::is_visible() const {
+ return is_active() && !m_hidden;
+}
+
+/**
+ * Activate systray management
+ */
+void manager::activate() {
+ if (is_active()) {
+ return;
+ }
+
+ m_log.info("tray: Activating tray manager");
+
+ try {
+ set_tray_colors();
+ set_tray_orientation();
+ } catch (const exception& err) {
+ m_log.err(err.what());
+ m_log.err("Cannot activate tray manager... failed to setup window");
+ deactivate();
+ return;
+ }
+
+ // Attempt to get control of the systray selection
+ xcb_window_t other_owner = XCB_NONE;
+ if (!acquire_selection(other_owner)) {
+ // Transition to WAITING state
+ wait_for_selection(other_owner);
+ return;
+ }
+
+ m_sig.attach(this);
+
+ m_othermanager = XCB_NONE;
+
+ m_state = state::ACTIVE;
+
+ notify_clients();
+}
+
+/**
+ * Transitions tray manager to WAITING state
+ *
+ * @param other window id for current selection owner
+ */
+void manager::wait_for_selection(xcb_window_t other) {
+ if (is_waiting() || other == XCB_NONE) {
+ return;
+ }
+
+ m_log.info("tray: Waiting for systray selection (current owner: %s)", m_connection.id(other));
+
+ m_sig.detach(this);
+
+ m_othermanager = other;
+ track_selection_owner(other);
+
+ m_log.trace("tray: Unembed clients");
+ m_clients.clear();
+
+ m_connection.flush();
+
+ m_state = state::WAITING;
+
+ recalculate_width();
+}
+
+/**
+ * Deactivate systray management
+ */
+void manager::deactivate() {
+ if (is_inactive()) {
+ return;
+ }
+
+ m_log.info("tray: Deactivating tray manager");
+
+ m_sig.detach(this);
+
+ // Unset selection owner if we currently own the atom
+ if (!m_connection.connection_has_error() && is_active()) {
+ m_log.trace("tray: Unset selection owner");
+ m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
+ }
+
+ m_log.trace("tray: Unembed clients");
+ m_clients.clear();
+
+ m_connection.flush();
+
+ m_othermanager = XCB_NONE;
+
+ m_state = state::INACTIVE;
+
+ recalculate_width();
+}
+
+/**
+ * Reconfigure tray
+ */
+void manager::reconfigure() {
+ if (!m_opts.selection_owner) {
+ return;
+ }
+
+ try {
+ reconfigure_clients();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray clients (%s)", err.what());
+ }
+
+ m_connection.flush();
+}
+
+/**
+ * Calculates the total width of the tray and potentially runs the update hook.
+ *
+ * Should be called whenever the number of mapped clients changes
+ */
+void manager::recalculate_width() {
+ m_log.trace("tray: Reconfigure window (hidden=%i, clients=%i)", m_hidden, m_clients.size());
+ unsigned new_width = calculate_w();
+ if (m_tray_width != new_width) {
+ m_tray_width = new_width;
+ m_log.trace("tray: new width (width: %d, clients: %d)", m_tray_width, m_clients.size());
+ m_on_update();
+ }
+}
+
+/**
+ * Reconfigure client positions and mapped state
+ */
+void manager::reconfigure_clients() {
+ m_log.trace("tray: Reconfigure clients");
+
+ // X-position of the start of the tray area
+ int base_x = calculate_x();
+
+ // X-position of the end of the previous tray icon (including padding)
+ unsigned x = 0;
+
+ bool has_error = false;
+
+ unsigned count = 0;
+
+ for (auto& client : m_clients) {
+ try {
+ client->ensure_state();
+
+ if (client->mapped()) {
+ // Calculate start of tray icon
+ unsigned client_x = x + (count > 0 ? m_opts.spacing : 0) + m_opts.padding;
+ client->set_position(base_x + client_x, calculate_client_y());
+ // Add size and padding to get the end position of the icon
+ x = client_x + m_opts.client_size.w + m_opts.padding;
+ count++;
+ }
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure %s, removing ... (%s)", client->name(), err.what());
+ client.reset();
+ has_error = true;
+ }
+ }
+
+ if (has_error) {
+ clean_clients();
+ }
+
+ // Some clients may have been (un)mapped or even removed
+ recalculate_width();
+ // The final x position should match the width of the entire tray
+ assert(x == m_tray_width);
+}
+
+/**
+ * Redraw client windows.
+ */
+void manager::redraw_clients() {
+ if (!is_visible()) {
+ return;
+ }
+
+ m_log.trace("tray: Refreshing clients");
+
+ for (auto& client : m_clients) {
+ try {
+ if (client->mapped()) {
+ client->update_bg();
+ }
+ } catch (const std::exception& e) {
+ m_log.err("tray: Failed to clear %s (%s)", client->name(), e.what());
+ }
+ }
+
+ m_connection.flush();
+}
+
+/**
+ * Find the systray selection atom
+ */
+void manager::query_atom() {
+ m_log.trace("tray: Find systray selection atom for the default screen");
+ string name{"_NET_SYSTEM_TRAY_S" + to_string(m_connection.default_screen())};
+ auto reply = m_connection.intern_atom(false, name.length(), name.c_str());
+ m_atom = reply.atom();
+}
+
+/**
+ * Set _NET_SYSTEM_TRAY_COLORS atom used by clients when determing icon theme
+ */
+void manager::set_tray_colors() {
+ m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to 0x%08x", m_opts.foreground);
+
+ auto r = m_opts.foreground.red_i();
+ auto g = m_opts.foreground.green_i();
+ auto b = m_opts.foreground.blue_i();
+
+ const uint16_t r16 = (r << 8) | r;
+ const uint16_t g16 = (g << 8) | g;
+ const uint16_t b16 = (b << 8) | b;
+
+ const array<uint32_t, 12> colors = {
+ r16, g16, b16, // normal
+ r16, g16, b16, // error
+ r16, g16, b16, // warning
+ r16, g16, b16, // success
+ };
+
+ m_connection.change_property(XCB_PROP_MODE_REPLACE, m_opts.selection_owner, _NET_SYSTEM_TRAY_COLORS,
+ XCB_ATOM_CARDINAL, 32, colors.size(), colors.data());
+}
+
+/**
+ * Set the _NET_SYSTEM_TRAY_ORIENTATION atom
+ */
+void manager::set_tray_orientation() {
+ const uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
+ m_log.trace("tray: Set _NET_SYSTEM_TRAY_ORIENTATION to 0x%x", orientation);
+ m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_opts.selection_owner, _NET_SYSTEM_TRAY_ORIENTATION,
+ XCB_ATOM_CARDINAL, 32, 1, &orientation);
+}
+
+/**
+ * Acquire the systray selection
+ *
+ * @param other_owner is set to the current owner if the function fails
+ * @returns Whether we acquired the selection
+ */
+bool manager::acquire_selection(xcb_window_t& other_owner) {
+ other_owner = XCB_NONE;
+ xcb_window_t owner = m_connection.get_selection_owner(m_atom).owner();
+
+ if (owner == m_opts.selection_owner) {
+ m_log.trace("tray: Already managing the systray selection");
+ return true;
+ } else if (owner == XCB_NONE) {
+ m_log.trace("tray: Change selection owner to %s", m_connection.id(m_opts.selection_owner));
+ m_connection.set_selection_owner_checked(m_opts.selection_owner, m_atom, XCB_CURRENT_TIME);
+ if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_opts.selection_owner) {
+ throw application_error("Failed to get control of the systray selection");
+ }
+ return true;
+ } else {
+ other_owner = owner;
+ m_log.warn("Systray selection already managed (window=%s)", m_connection.id(owner));
+ return false;
+ }
+}
+
+/**
+ * Notify pending clients about the new systray MANAGER
+ */
+void manager::notify_clients() {
+ if (is_active()) {
+ m_log.info("tray: Notifying pending tray clients");
+ auto message = m_connection.make_client_message(MANAGER, m_connection.root());
+ message.data.data32[0] = XCB_CURRENT_TIME;
+ message.data.data32[1] = m_atom;
+ message.data.data32[2] = m_opts.selection_owner;
+ m_connection.send_client_message(message, m_connection.root());
+ }
+}
+
+/**
+ * Track changes to the given selection owner
+ * If it gets destroyed or goes away we can reactivate the tray_manager
+ */
+void manager::track_selection_owner(xcb_window_t owner) {
+ if (owner != XCB_NONE) {
+ const uint32_t mask{XCB_CW_EVENT_MASK};
+ const uint32_t value{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.change_window_attributes(owner, mask, &value);
+ }
+}
+
+/**
+ * Process client docking request
+ */
+void manager::process_docking_request(xcb_window_t win) {
+ m_log.info("tray: Processing docking request from '%s' (%s)", ewmh_util::get_wm_name(win), m_connection.id(win));
+
+ try {
+ auto cl =
+ make_unique<client>(m_log, m_connection, m_opts.selection_owner, win, m_opts.client_size, m_opts.background);
+
+ try {
+ cl->query_xembed();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to query _XEMBED_INFO, removing %s ... (%s)", cl->name(), err.what());
+ return;
+ }
+
+ cl->update_client_attributes();
+
+ cl->reparent();
+
+ cl->add_to_save_set();
+
+ cl->hidden(m_hidden);
+ cl->ensure_state();
+
+ cl->notify_xembed();
+
+ m_clients.emplace_back(std::move(cl));
+ } catch (const std::exception& err) {
+ m_log.err("tray: Failed to setup tray client '%s' (%s) removing... (%s)", ewmh_util::get_wm_name(win),
+ m_connection.id(win), err.what());
+ return;
+ }
+}
+
+/**
+ * Final x-position of the tray window relative to the very top-left bar window.
+ */
+int manager::calculate_x() const {
+ return m_bar_opts.inner_area(false).x + m_pos.x;
+}
+
+/**
+ * Calculates the entire width taken up by the tray area in pixels
+ *
+ * This many pixels need to be reserved on the bar in order to draw the tray.
+ */
+unsigned manager::calculate_w() const {
+ unsigned count =
+ std::count_if(m_clients.begin(), m_clients.end(), [](const auto& client) { return client->mapped(); });
+
+ if (count > 0) {
+ return (count - 1) * m_opts.spacing + count * (2 * m_opts.padding + m_opts.client_size.w);
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Calculate y position of client window to vertically center it in the inner area of the bar.
+ */
+int manager::calculate_client_y() {
+ return m_bar_opts.inner_area(false).y + (m_bar_opts.inner_area(false).height - m_opts.client_size.h) / 2;
+}
+
+/**
+ * Check if the given window is embedded.
+ *
+ * The given window ID can be the ID of the wrapper or the embedded window
+ */
+bool manager::is_embedded(const xcb_window_t& win) {
+ return find_client(win) != nullptr;
+}
+
+/**
+ * Find tray client object from the wrapper or embedded window
+ */
+client* manager::find_client(const xcb_window_t& win) {
+ auto client = std::find_if(m_clients.begin(), m_clients.end(),
+ [win](const auto& client) { return client->match(win) || client->embedder() == win; });
+
+ if (client == m_clients.end()) {
+ return nullptr;
+ } else {
+ return client->get();
+ }
+}
+
+/**
+ * Remove tray client
+ */
+void manager::remove_client(const client& c) {
+ remove_client(c.client_window());
+}
+
+/**
+ * Remove tray client by window
+ */
+void manager::remove_client(xcb_window_t win) {
+ auto old_size = m_clients.size();
+ m_clients.erase(
+ std::remove_if(m_clients.begin(), m_clients.end(), [win](const auto& client) { return client->match(win); }));
+
+ if (old_size != m_clients.size()) {
+ reconfigure();
+ }
+}
+
+/**
+ * Remove all null pointers from client list.
+ *
+ * Removing clients is often done in two steps:
+ * 1. When removing a client during iteration, the unique_ptr is reset.
+ * 2. Afterwards all null pointers are removed from the list.
+ */
+void manager::clean_clients() {
+ m_clients.erase(
+ std::remove_if(m_clients.begin(), m_clients.end(), [](const auto& client) { return client.get() == nullptr; }));
+}
+
+bool manager::change_visibility(bool visible) {
+ if (!is_active() || m_hidden == !visible) {
+ return false;
+ }
+
+ m_log.trace("tray: visibility_change (new_state)", visible ? "visible" : "hidden");
+
+ m_hidden = !visible;
+
+ for (auto& client : m_clients) {
+ client->hidden(m_hidden);
+ client->ensure_state();
+ }
+
+ if (!m_hidden) {
+ redraw_clients();
+ }
+
+ m_connection.flush();
+
+ return true;
+}
+
+/**
+ * Event callback : XCB_EXPOSE
+ */
+void manager::handle(const evt::expose& evt) {
+ if (is_active() && !m_clients.empty() && evt->count == 0) {
+ redraw_clients();
+ }
+}
+
+/**
+ * Event callback : XCB_CLIENT_MESSAGE
+ */
+void manager::handle(const evt::client_message& evt) {
+ if (!is_active()) {
+ return;
+ }
+
+ // Our selection owner window was deleted
+ if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_opts.selection_owner) {
+ m_log.notice("Received WM_DELETE for selection owner");
+ deactivate();
+ } else if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
+ m_log.trace("tray: Received client_message");
+
+ // Docking request
+ if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
+ xcb_window_t win = evt->data.data32[2];
+ if (is_embedded(win)) {
+ m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
+ } else {
+ process_docking_request(win);
+ }
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_CONFIGURE_REQUEST
+ *
+ * Called when a tray client thinks he's part of the free world and
+ * wants to reconfigure its window. This is of course nothing we appreciate
+ * so we return an answer that'll put him in place.
+ */
+void manager::handle(const evt::configure_request& evt) {
+ if (is_active() && is_embedded(evt->window)) {
+ auto client = find_client(evt->window);
+ try {
+ m_log.trace("%s: Client configure request", client->name());
+ client->configure_notify();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure %s, removing... (%s)", client->name(), err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * @see manager::handle(const evt::configure_request&);
+ */
+void manager::handle(const evt::resize_request& evt) {
+ if (is_active() && is_embedded(evt->window)) {
+ auto client = find_client(evt->window);
+ try {
+ m_log.trace("%s: Client resize request", client->name());
+ client->configure_notify();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure %s, removing... (%s)", client->name(), err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_SELECTION_CLEAR
+ */
+void manager::handle(const evt::selection_clear& evt) {
+ if (is_inactive()) {
+ return;
+ } else if (evt->selection != m_atom) {
+ return;
+ } else if (evt->owner != m_opts.selection_owner) {
+ return;
+ }
+
+ m_log.warn("Lost systray selection, deactivating...");
+ wait_for_selection(m_connection.get_selection_owner(m_atom).owner());
+}
+
+/**
+ * Event callback : XCB_PROPERTY_NOTIFY
+ */
+void manager::handle(const evt::property_notify& evt) {
+ if (!is_active()) {
+ return;
+ }
+
+ if (evt->atom != _XEMBED_INFO) {
+ return;
+ }
+
+ auto client = find_client(evt->window);
+
+ if (!client) {
+ return;
+ }
+
+ m_log.trace("%s: _XEMBED_INFO", client->name());
+
+ if (evt->state == XCB_PROPERTY_NEW_VALUE) {
+ m_log.trace("tray: _XEMBED_INFO value has changed");
+ }
+
+ try {
+ client->query_xembed();
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to query _XEMBED_INFO, removing %s ... (%s)", client->name(), err.what());
+ remove_client(*client);
+ return;
+ }
+
+ client->ensure_state();
+}
+
+/**
+ * Event callback : XCB_REPARENT_NOTIFY
+ */
+void manager::handle(const evt::reparent_notify& evt) {
+ if (!is_active()) {
+ return;
+ }
+
+ auto client = find_client(evt->window);
+
+ if (!client) {
+ return;
+ }
+
+ // Tray client was reparented to another window
+ if (evt->parent != client->embedder()) {
+ m_log.info("%s: Received reparent_notify for client, remove...", client->name());
+ remove_client(*client);
+ }
+}
+
+/**
+ * Event callback : XCB_DESTROY_NOTIFY
+ */
+void manager::handle(const evt::destroy_notify& evt) {
+ if (is_waiting() && evt->window == m_othermanager) {
+ m_log.info("Systray selection unmanaged... re-activating");
+ activate();
+ } else if (is_active() && is_embedded(evt->window)) {
+ m_log.info("tray: Received destroy_notify for client, remove...");
+ remove_client(evt->window);
+ redraw_clients();
+ }
+}
+
+/**
+ * Event callback : XCB_MAP_NOTIFY
+ */
+void manager::handle(const evt::map_notify& evt) {
+ if (is_active() && evt->window == m_opts.selection_owner) {
+ m_log.trace("tray: Received map_notify for selection owner");
+ redraw_clients();
+ } else if (is_embedded(evt->window)) {
+ auto client = find_client(evt->window);
+
+ // If we received a notification on the wrapped window, we don't want to do anything.
+ if (client->embedder() != evt->window) {
+ return;
+ }
+
+ m_log.trace("%s: Received map_notify", client->name());
+
+ if (!client->mapped()) {
+ client->mapped(true);
+ reconfigure();
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_UNMAP_NOTIFY
+ */
+void manager::handle(const evt::unmap_notify& evt) {
+ if (is_active() && is_embedded(evt->window)) {
+ auto client = find_client(evt->window);
+
+ // If we received a notification on the wrapped window, we don't want to do anything.
+ if (client->embedder() != evt->window) {
+ return;
+ }
+
+ m_log.trace("%s: Received unmap_notify", client->name());
+
+ if (client->mapped()) {
+ client->mapped(false);
+ reconfigure();
+ }
+ }
+}
+
+bool manager::on(const signals::ui::update_background&) {
+ redraw_clients();
+
+ return false;
+}
+
+bool manager::on(const signals::ui_tray::tray_pos_change& evt) {
+ int new_x = std::max(0, std::min(evt.cast(), (int)(m_bar_opts.size.w - m_tray_width)));
+
+ if (new_x != m_pos.x) {
+ m_pos.x = new_x;
+ reconfigure();
+ }
+
+ return true;
+}
+
+bool manager::on(const signals::ui_tray::tray_visibility& evt) {
+ return change_visibility(evt.cast());
+}
+
+} // namespace tray
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/window.hpp"
+
+#include "components/types.hpp"
+#include "utils/memory.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/randr.hpp"
+
+POLYBAR_NS
+
+/**
+ * Reconfigure the window geometry
+ */
+window window::reconfigure_geom(unsigned short int w, unsigned short int h, short int x, short int y) {
+ uint32_t mask{0};
+ std::array<uint32_t, 32> values{};
+
+ xcb_params_configure_window_t params{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, width, w);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, height, h);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, x, x);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, y, y);
+
+ connection::pack_values(mask, ¶ms, values);
+ configure_checked(mask, values.data());
+
+ return *this;
+}
+
+/**
+ * Reconfigure the window position
+ */
+window window::reconfigure_pos(short int x, short int y) {
+ uint32_t mask{0};
+ std::array<uint32_t, 32> values{};
+
+ xcb_params_configure_window_t params{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, x, x);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, y, y);
+
+ connection::pack_values(mask, ¶ms, values);
+ configure_checked(mask, values.data());
+
+ return *this;
+}
+
+/**
+ * Reconfigure the windows ewmh strut
+ *
+ * Ref: https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45381391268672
+ *
+ * @param w Width of the bar window
+ * @param strut Size of the reserved space. Height of the window, corrected for unaligned monitors
+ * @param x The absolute x-position of the bar window (top-left corner)
+ * @param bottom Whether the bar is at the bottom of the screen
+ */
+window window::reconfigure_struts(uint32_t w, uint32_t strut, uint32_t x, bool bottom) {
+ std::array<uint32_t, 12> values{};
+
+ uint32_t end_x = std::max<int>(0, x + w - 1);
+
+ if (bottom) {
+ values[to_integral(strut::BOTTOM)] = strut;
+ values[to_integral(strut::BOTTOM_START_X)] = x;
+ values[to_integral(strut::BOTTOM_END_X)] = end_x;
+ } else {
+ values[to_integral(strut::TOP)] = strut;
+ values[to_integral(strut::TOP_START_X)] = x;
+ values[to_integral(strut::TOP_END_X)] = end_x;
+ }
+
+ connection().change_property_checked(
+ XCB_PROP_MODE_REPLACE, *this, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4, values.data());
+ connection().change_property_checked(
+ XCB_PROP_MODE_REPLACE, *this, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, 12, values.data());
+
+ return *this;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/winspec.hpp"
+
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+winspec::winspec(connection& conn) : m_connection(conn) {}
+winspec::winspec(connection& conn, const xcb_window_t& window) : m_connection(conn), m_window(window) {}
+
+winspec::operator xcb_window_t() const {
+ return m_window;
+}
+winspec::operator xcb_rectangle_t() const {
+ return {m_x, m_y, m_width, m_height};
+}
+
+xcb_window_t winspec::operator<<(const cw_flush& f) {
+ std::array<uint32_t, 32> values{};
+
+ if (m_window == XCB_NONE) {
+ m_window = m_connection.generate_id();
+ }
+ if (m_parent == XCB_NONE) {
+ m_parent = m_connection.root();
+ }
+
+ if (m_width <= 0) {
+ m_width = 1;
+ }
+ if (m_height <= 0) {
+ m_height = 1;
+ }
+
+ connection::pack_values(m_mask, &m_params, values);
+
+ if (f.checked) {
+ m_connection.create_window_checked(
+ m_depth, m_window, m_parent, m_x, m_y, m_width, m_height, m_border, m_class, m_visual, m_mask, values.data());
+ } else {
+ m_connection.create_window(
+ m_depth, m_window, m_parent, m_x, m_y, m_width, m_height, m_border, m_class, m_visual, m_mask, values.data());
+ }
+
+ return m_window;
+}
+
+winspec& winspec::operator<<(const cw_size& size) {
+ m_width = size.w;
+ m_height = size.h;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_pos& p) {
+ m_x = p.x;
+ m_y = p.y;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_border& b) {
+ m_border = b.border_width;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_class& c) {
+ m_class = c.class_;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_parent& p) {
+ m_parent = p.parent;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_depth& d) {
+ m_depth = d.depth;
+ return *this;
+}
+winspec& winspec::operator<<(const cw_visual& v) {
+ m_visual = v.visualid;
+ return *this;
+}
+
+winspec& winspec::operator<<(const cw_params_back_pixel& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, back_pixel, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_back_pixmap& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, back_pixmap, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_pixel& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_pixel, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_planes& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_planes, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_store& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_store, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_bit_gravity& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, bit_gravity, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_border_pixel& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, border_pixel, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_border_pixmap& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, border_pixmap, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_colormap& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, colormap, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_cursor& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, cursor, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_dont_propagate& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, dont_propagate, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_event_mask& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, event_mask, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_override_redirect& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, override_redirect, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_save_under& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, save_under, p.value);
+ return *this;
+}
+winspec& winspec::operator<<(const cw_params_win_gravity& p) {
+ XCB_AUX_ADD_PARAM(&m_mask, &m_params, win_gravity, p.value);
+ return *this;
+}
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/xembed.hpp"
+
+#include <cassert>
+
+#include "errors.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/ewmh.hpp"
+
+POLYBAR_NS
+
+namespace xembed {
+
+void info::set(const uint32_t* data) {
+ version = data[0];
+ flags = data[1];
+}
+
+uint32_t info::get_version() const {
+ return version;
+}
+
+uint32_t info::get_flags() const {
+ return flags;
+}
+
+bool info::is_mapped() const {
+ return (flags & XEMBED_MAPPED) == XEMBED_MAPPED;
+}
+
+string info::to_string() const {
+ return sstream() << "xembed(version=0x" << std::hex << get_version() << ", flags=0x" << get_flags()
+ << ", XEMBED_MAPPED=" << std::dec << is_mapped() << ")";
+}
+
+/**
+ * Query _XEMBED_INFO for the given window
+ *
+ * @return Whether valid XEMBED info was found
+ */
+bool query(connection& conn, xcb_window_t win, info& data) {
+ auto info = conn.get_property(false, win, _XEMBED_INFO, _XEMBED_INFO, 0L, 2);
+
+ if (info->value_len == 0) {
+ return false;
+ }
+
+ std::vector<uint32_t> xembed_data{info.value<uint32_t>().begin(), info.value<uint32_t>().end()};
+ data.set(xembed_data.data());
+
+ return true;
+}
+
+/**
+ * Send _XEMBED messages
+ */
+void send_message(connection& conn, xcb_window_t target, uint32_t message, uint32_t d1, uint32_t d2, uint32_t d3) {
+ auto msg = conn.make_client_message(_XEMBED, target);
+ msg.data.data32[0] = XCB_CURRENT_TIME;
+ msg.data.data32[1] = message;
+ msg.data.data32[2] = d1;
+ msg.data.data32[3] = d2;
+ msg.data.data32[4] = d3;
+ conn.send_client_message(msg, target);
+}
+
+/**
+ * Send window focus event
+ */
+void send_focus_event(connection& conn, xcb_window_t target) {
+ auto msg = conn.make_client_message(WM_PROTOCOLS, target);
+ msg.data.data32[0] = WM_TAKE_FOCUS;
+ msg.data.data32[1] = XCB_CURRENT_TIME;
+ conn.send_client_message(msg, target);
+}
+
+/**
+ * Acknowledge window embedding
+ */
+void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, uint32_t version) {
+ send_message(conn, win, XEMBED_EMBEDDED_NOTIFY, 0, embedder, std::min(version, XEMBED_MAX_VERSION));
+}
+
+/**
+ * Send window activate notification
+ */
+void notify_activated(connection& conn, xcb_window_t win) {
+ send_message(conn, win, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
+}
+
+/**
+ * Send window deactivate notification
+ */
+void notify_deactivated(connection& conn, xcb_window_t win) {
+ send_message(conn, win, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0);
+}
+
+/**
+ * Send window focused notification
+ */
+void notify_focused(connection& conn, xcb_window_t win, uint32_t focus_type) {
+ send_message(conn, win, XEMBED_FOCUS_IN, focus_type, 0, 0);
+}
+
+/**
+ * Send window unfocused notification
+ */
+void notify_unfocused(connection& conn, xcb_window_t win) {
+ send_message(conn, win, XEMBED_FOCUS_OUT, 0, 0, 0);
+}
+
+/**
+ * Unembed given window
+ */
+void unembed(connection& conn, xcb_window_t win, xcb_window_t root) {
+ assert(win != XCB_NONE);
+ try {
+ conn.unmap_window_checked(win);
+ conn.reparent_window_checked(win, root, 0, 0);
+ } catch (const xpp::x::error::window& err) {
+ // invalid window
+ logger::make().err("tray: Failed to unembed window '%s' (%s)", ewmh_util::get_wm_name(win), conn.id(win));
+ }
+}
+} // namespace xembed
+
+POLYBAR_NS_END
--- /dev/null
+#include "x11/xresources.hpp"
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+template <>
+string xresource_manager::convert(string&& value) const {
+ return forward<string>(value);
+}
+
+template <>
+double xresource_manager::convert(string&& value) const {
+ return std::strtod(value.c_str(), nullptr);
+}
+
+POLYBAR_NS_END
--- /dev/null
+# Download and unpack googletest at configure time {{{
+configure_file(
+ CMakeLists.txt.in
+ ${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt
+ )
+execute_process( COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+ RESULT_VARIABLE result
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)
+
+if(result)
+ message(FATAL_ERROR "CMake step for googletest failed: ${result}")
+endif()
+
+execute_process(COMMAND ${CMAKE_COMMAND} --build .
+ RESULT_VARIABLE result
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
+
+if(result)
+ message(FATAL_ERROR "Build step for googletest failed: ${result}")
+endif()
+
+# Add googletest directly to our build. This defines
+# the gtest, gtest_main, gmock and gmock_main targets.
+add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
+ ${CMAKE_BINARY_DIR}/googletest-build
+ EXCLUDE_FROM_ALL)
+
+# }}}
+
+# Compile all unit tests with 'make all_unit_tests'
+add_custom_target(all_unit_tests
+ COMMENT "Building all unit test")
+
+function(add_unit_test source_file)
+ string(REPLACE "/" "_" testname ${source_file})
+ set(name "unit_test.${testname}")
+
+ add_executable(${name} unit_tests/${source_file}.cpp)
+ get_include_dirs(includes_dir)
+ target_include_directories(${name} PRIVATE ${includes_dir} ${CMAKE_CURRENT_LIST_DIR})
+
+ # Link against gmock (this automatically links against gtest)
+ target_link_libraries(${name} poly gmock_main)
+ add_test(NAME ${name} COMMAND ${name})
+
+ add_dependencies(all_unit_tests ${name})
+endfunction()
+
+add_unit_test(utils/actions)
+add_unit_test(utils/action_router)
+add_unit_test(utils/color)
+add_unit_test(utils/command)
+add_unit_test(utils/env)
+add_unit_test(utils/math)
+add_unit_test(utils/scope)
+add_unit_test(utils/string)
+add_unit_test(utils/file)
+add_unit_test(utils/process)
+add_unit_test(utils/units)
+add_unit_test(components/builder)
+add_unit_test(components/command_line)
+add_unit_test(components/config_parser)
+add_unit_test(drawtypes/label)
+add_unit_test(drawtypes/ramp)
+add_unit_test(drawtypes/iconset)
+add_unit_test(drawtypes/layouticonset)
+add_unit_test(ipc/decoder)
+add_unit_test(ipc/encoder)
+add_unit_test(ipc/util)
+add_unit_test(tags/parser)
+add_unit_test(tags/dispatch)
+add_unit_test(tags/action_context)
+
+# Run make check to build and run all unit tests
+add_custom_target(check
+ COMMAND GTEST_COLOR=1 ctest --output-on-failure
+ DEPENDS all_unit_tests
+ )
--- /dev/null
+cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
+
+project(googletest-download NONE)
+
+include(ExternalProject)
+ExternalProject_Add(googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG main
+ SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
+ BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+)
--- /dev/null
+#pragma once
+
+#include "gtest/gtest.h"
--- /dev/null
+#include "components/builder.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+static const rgba FG = rgba("#00FF00");
+static const rgba BG = rgba("#FF0000");
+
+class BuilderTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ opts.foreground = FG;
+ opts.background = BG;
+ opts.spacing = ZERO_SPACE;
+ }
+
+ bar_settings opts{};
+ builder b{opts};
+};
+
+TEST_F(BuilderTest, empty) {
+ EXPECT_EQ("", b.flush());
+}
+
+TEST_F(BuilderTest, text) {
+ b.node("foo");
+ EXPECT_EQ("foo", b.flush());
+}
+
+TEST_F(BuilderTest, textFont) {
+ b.node("foo", 12);
+ EXPECT_EQ("%{T12}foo%{T-}", b.flush());
+}
+
+using offset_test = std::pair<extent_val, string>;
+class OffsetTest : public BuilderTest, public ::testing::WithParamInterface<offset_test> {};
+
+vector<offset_test> offset_test_list = {
+ {ZERO_PX_EXTENT, ""},
+ {{extent_type::POINT, 0}, ""},
+ {{extent_type::PIXEL, 10}, "%{O10px}"},
+ {{extent_type::PIXEL, -10}, "%{O-10px}"},
+ {{extent_type::POINT, 23}, "%{O23pt}"},
+ {{extent_type::POINT, -1}, "%{O-1pt}"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, OffsetTest, ::testing::ValuesIn(offset_test_list));
+
+TEST_P(OffsetTest, correctness) {
+ b.offset(GetParam().first);
+ EXPECT_EQ(GetParam().second, b.flush());
+}
+
+using spacing_test = std::pair<spacing_val, string>;
+class SpacingTest : public BuilderTest, public ::testing::WithParamInterface<spacing_test> {};
+
+vector<spacing_test> spacing_test_list = {
+ {ZERO_SPACE, ""},
+ {{spacing_type::SPACE, 2}, " "},
+ {{spacing_type::PIXEL, 3}, "%{O3px}"},
+ {{spacing_type::POINT, 4}, "%{O4pt}"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, SpacingTest, ::testing::ValuesIn(spacing_test_list));
+
+TEST_P(SpacingTest, correctness) {
+ b.spacing(GetParam().first);
+ EXPECT_EQ(GetParam().second, b.flush());
+}
+
+TEST_P(SpacingTest, get_spacing_format_string) {
+ b.spacing(GetParam().first);
+ EXPECT_EQ(GetParam().second, b.flush());
+}
+
+TEST_F(BuilderTest, tags) {
+ b.font(10);
+ b.font_close();
+ EXPECT_EQ("%{T10}%{T-}", b.flush());
+
+ b.background(rgba("#f0f0f0"));
+ b.background_close();
+ EXPECT_EQ("%{B#f0f0f0}%{B-}", b.flush());
+
+ b.foreground(rgba("#0f0f0f"));
+ b.foreground_close();
+ EXPECT_EQ("%{F#0f0f0f}%{F-}", b.flush());
+
+ b.overline(rgba("#0e0e0e"));
+ b.overline_close();
+ // Last tag is from auto-closing during flush
+ EXPECT_EQ("%{o#0e0e0e}%{+o}%{-o}%{o-}", b.flush());
+
+ b.underline(rgba("#0d0d0d"));
+ b.underline_close();
+ // Last tag is from auto-closing during flush
+ EXPECT_EQ("%{u#0d0d0d}%{+u}%{-u}%{u-}", b.flush());
+
+ b.control(tags::controltag::R);
+ EXPECT_EQ("%{PR}", b.flush());
+
+ b.action(mousebtn::LEFT, "cmd");
+ b.action_close();
+ EXPECT_EQ("%{A1:cmd:}%{A}", b.flush());
+}
+
+TEST_F(BuilderTest, tagsAutoClose) {
+ b.font(20);
+ EXPECT_EQ("%{T20}%{T-}", b.flush());
+
+ b.background(rgba("#f0f0f0"));
+ EXPECT_EQ("%{B#f0f0f0}%{B-}", b.flush());
+
+ b.foreground(rgba("#0f0f0f"));
+ EXPECT_EQ("%{F#0f0f0f}%{F-}", b.flush());
+
+ b.overline(rgba("#0e0e0e"));
+ EXPECT_EQ("%{o#0e0e0e}%{+o}%{o-}%{-o}", b.flush());
+
+ b.underline(rgba("#0d0d0d"));
+ EXPECT_EQ("%{u#0d0d0d}%{+u}%{u-}%{-u}", b.flush());
+
+ b.action(mousebtn::LEFT, "cmd");
+ EXPECT_EQ("%{A1:cmd:}%{A}", b.flush());
+}
+
+TEST_F(BuilderTest, invalidTags) {
+ EXPECT_THROW(b.control(tags::controltag::NONE), std::runtime_error);
+}
+
+TEST_F(BuilderTest, colorBlending) {
+ b.background(rgba(0x12000000, rgba::type::ALPHA_ONLY));
+ b.background_close();
+
+ EXPECT_EQ("%{B#12ff0000}%{B-}", b.flush());
+
+ b.foreground(rgba(0x34000000, rgba::type::ALPHA_ONLY));
+ b.foreground_close();
+
+ EXPECT_EQ("%{F#3400ff00}%{F-}", b.flush());
+}
--- /dev/null
+#include "components/command_line.hpp"
+
+#include "common/test.hpp"
+#include "utils/string.hpp"
+
+using namespace polybar;
+
+class CommandLine : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ set_cli();
+ }
+
+ virtual void set_cli() {
+ cli = command_line::parser::make("cmd", get_opts());
+ }
+
+ command_line::options get_opts() {
+ // clang-format off
+ return command_line::options {
+ command_line::option{"-f", "--flag", "Flag description"},
+ command_line::option{"-o", "--option", "Option description", "OPTION", {"foo", "bar", "baz"}},
+ };
+ // clang-format on
+ };
+
+ command_line::parser::make_type cli;
+};
+
+TEST_F(CommandLine, hasShort) {
+ cli->process_input(string_util::split("-f", ' '));
+ EXPECT_TRUE(cli->has("flag"));
+ EXPECT_FALSE(cli->has("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("-f -o foo", ' '));
+ EXPECT_TRUE(cli->has("flag"));
+ EXPECT_TRUE(cli->has("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("-o baz", ' '));
+ EXPECT_FALSE(cli->has("flag"));
+ EXPECT_TRUE(cli->has("option"));
+}
+
+TEST_F(CommandLine, hasLong) {
+ cli->process_input(string_util::split("--flag", ' '));
+ EXPECT_TRUE(cli->has("flag"));
+ EXPECT_FALSE(cli->has("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("--flag --option=foo", ' '));
+ EXPECT_TRUE(cli->has("flag"));
+ EXPECT_TRUE(cli->has("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("--option=foo --flag", ' '));
+ EXPECT_TRUE(cli->has("flag"));
+ EXPECT_TRUE(cli->has("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("--option=baz", ' '));
+ EXPECT_FALSE(cli->has("flag"));
+ EXPECT_TRUE(cli->has("option"));
+}
+
+TEST_F(CommandLine, compare) {
+ cli->process_input(string_util::split("-o baz", ' '));
+ EXPECT_TRUE(cli->compare("option", "baz"));
+
+ set_cli();
+ cli->process_input(string_util::split("--option=foo", ' '));
+ EXPECT_TRUE(cli->compare("option", "foo"));
+}
+
+TEST_F(CommandLine, get) {
+ cli->process_input(string_util::split("--option=baz", ' '));
+ EXPECT_EQ("baz", cli->get("option"));
+
+ set_cli();
+ cli->process_input(string_util::split("--option=foo", ' '));
+ EXPECT_EQ("foo", cli->get("option"));
+}
+
+TEST_F(CommandLine, missingValue) {
+ auto input1 = string_util::split("--option", ' ');
+ auto input2 = string_util::split("-o", ' ');
+ auto input3 = string_util::split("--option baz", ' ');
+
+ EXPECT_THROW(cli->process_input(input1), command_line::value_error);
+ set_cli();
+ EXPECT_THROW(cli->process_input(input2), command_line::value_error);
+ set_cli();
+ EXPECT_THROW(cli->process_input(input3), command_line::value_error);
+}
+
+TEST_F(CommandLine, invalidValue) {
+ auto input1 = string_util::split("--option=invalid", ' ');
+ auto input2 = string_util::split("-o invalid_value", ' ');
+
+ EXPECT_THROW(cli->process_input(input1), command_line::value_error);
+ set_cli();
+ EXPECT_THROW(cli->process_input(input2), command_line::value_error);
+}
+
+TEST_F(CommandLine, unrecognized) {
+ auto input1 = string_util::split("-x", ' ');
+ auto input2 = string_util::split("--unrecognized", ' ');
+
+ EXPECT_THROW(cli->process_input(input1), command_line::argument_error);
+ set_cli();
+ EXPECT_THROW(cli->process_input(input2), command_line::argument_error);
+}
--- /dev/null
+#include "components/config_parser.hpp"
+
+#include "common/test.hpp"
+#include "components/logger.hpp"
+
+using namespace polybar;
+using namespace std;
+
+/**
+ * \brief Testing-only subclass of config_parser to change access level
+ */
+class TestableConfigParser : public config_parser {
+ using config_parser::config_parser;
+
+ public:
+ TestableConfigParser(const logger& logger, string&& file)
+ : config_parser(logger, move(file)) {
+ m_files.push_back("test_config");
+ }
+
+ public:
+ using config_parser::get_line_type;
+
+ public:
+ using config_parser::parse_key;
+
+ public:
+ using config_parser::parse_header;
+
+ public:
+ using config_parser::parse_line;
+
+ public:
+ using config_parser::parse_escaped_value;
+
+ public:
+ using config_parser::m_files;
+};
+
+/**
+ * \brief Fixture class
+ */
+class ConfigParser : public ::testing::Test {
+ protected:
+ const logger l = logger(loglevel::NONE);
+ unique_ptr<TestableConfigParser> parser = make_unique<TestableConfigParser>(l, "/dev/zero");
+};
+
+// ParseLineTest {{{
+class ParseLineInValidTest : public ConfigParser, public ::testing::WithParamInterface<string> {};
+
+class ParseLineHeaderTest : public ConfigParser, public ::testing::WithParamInterface<pair<string, string>> {};
+
+class ParseLineKeyTest : public ConfigParser,
+ public ::testing::WithParamInterface<pair<pair<string, string>, string>> {};
+
+vector<string> parse_line_invalid_list = {
+ " # comment",
+ "; comment",
+ "\t#",
+ "",
+ " ",
+ "\t ",
+};
+
+vector<pair<string, string>> parse_line_header_list = {
+ {"section", "\t[section]"},
+ {"section", "\t[section] "},
+ {"bar/name", "\t[bar/name] "},
+};
+
+vector<pair<pair<string, string>, string>> parse_line_key_list = {
+ {{"key", "value"}, " key = value"},
+ {{"key", ""}, " key\t = \"\""},
+ {{"key", "\""}, " key\t = \"\"\""},
+ {{"key", "\\"}, " key = \\"},
+ {{"key", "\\"}, " key = \\\\"},
+ {{"key", "\\val\\ue\\"}, " key = \\val\\ue\\"},
+ {{"key", "\\val\\ue\\"}, " key = \\\\val\\\\ue\\\\"},
+ {{"key", "\\val\\ue\\"}, " key = \"\\val\\ue\\\""},
+ {{"key", "\\val\\ue\\"}, " key = \"\\\\val\\\\ue\\\\\""},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineInValidTest, ::testing::ValuesIn(parse_line_invalid_list));
+
+TEST_P(ParseLineInValidTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ parser->parse_line(line, GetParam());
+
+ EXPECT_FALSE(line.useful);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineHeaderTest, ::testing::ValuesIn(parse_line_header_list));
+
+TEST_P(ParseLineHeaderTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ parser->parse_line(line, GetParam().second);
+
+ EXPECT_TRUE(line.useful);
+
+ EXPECT_TRUE(line.is_header);
+ EXPECT_EQ(GetParam().first, line.header);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineKeyTest, ::testing::ValuesIn(parse_line_key_list));
+
+TEST_P(ParseLineKeyTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ parser->parse_line(line, GetParam().second);
+
+ EXPECT_TRUE(line.useful);
+
+ EXPECT_FALSE(line.is_header);
+ EXPECT_EQ(GetParam().first.first, line.key);
+ EXPECT_EQ(GetParam().first.second, line.value);
+}
+
+TEST_F(ParseLineInValidTest, throwsSyntaxError) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+
+ EXPECT_THROW(parser->parse_line(line, "unknown"), syntax_error);
+ EXPECT_THROW(parser->parse_line(line, "\ufeff"), syntax_error);
+}
+// }}}
+
+// GetLineTypeTest {{{
+
+/**
+ * \brief Class for parameterized tests on get_line_type
+ *
+ * Parameters are pairs of the expected line type and a string that should be
+ * detected as that line type
+ */
+class GetLineTypeTest : public ConfigParser, public ::testing::WithParamInterface<pair<line_type, string>> {};
+
+/**
+ * \brief Helper function generate GetLineTypeTest parameter values
+ */
+vector<pair<line_type, string>> line_type_transform(vector<string>&& in, line_type type) {
+ vector<pair<line_type, string>> out;
+
+ out.reserve(in.size());
+ for (const auto& i : in) {
+ out.emplace_back(type, i);
+ }
+
+ return out;
+}
+
+/**
+ * \brief Parameter values for GetLineTypeTest
+ */
+auto line_type_key = line_type_transform({"a = b", " a =b", " a\t =\t \t b", "a = "}, line_type::KEY);
+auto line_type_header = line_type_transform({"[section]", "[section]", "[section/sub]"}, line_type::HEADER);
+auto line_type_comment = line_type_transform({";abc", "#abc", ";", "#"}, line_type::COMMENT);
+auto line_type_empty = line_type_transform({""}, line_type::EMPTY);
+auto line_type_unknown = line_type_transform({"|a", " |a", "a"}, line_type::UNKNOWN);
+
+/**
+ * Instantiate GetLineTypeTest for the different line types
+ */
+INSTANTIATE_TEST_SUITE_P(LineTypeKey, GetLineTypeTest, ::testing::ValuesIn(line_type_key));
+INSTANTIATE_TEST_SUITE_P(LineTypeHeader, GetLineTypeTest, ::testing::ValuesIn(line_type_header));
+INSTANTIATE_TEST_SUITE_P(LineTypeComment, GetLineTypeTest, ::testing::ValuesIn(line_type_comment));
+INSTANTIATE_TEST_SUITE_P(LineTypeEmpty, GetLineTypeTest, ::testing::ValuesIn(line_type_empty));
+INSTANTIATE_TEST_SUITE_P(LineTypeUnknown, GetLineTypeTest, ::testing::ValuesIn(line_type_unknown));
+
+/**
+ * \brief Parameterized test for get_line_type
+ */
+TEST_P(GetLineTypeTest, correctness) {
+ EXPECT_EQ(GetParam().first, parser->get_line_type(GetParam().second));
+}
+
+// }}}
+
+// ParseKeyTest {{{
+
+/**
+ * \brief Class for parameterized tests on parse_key
+ *
+ * The first element of the pair is the expected key-value pair and the second
+ * element is the string to be parsed, has to be trimmed and valid.
+ */
+class ParseKeyTest : public ConfigParser, public ::testing::WithParamInterface<pair<pair<string, string>, string>> {};
+
+vector<pair<pair<string, string>, string>> parse_key_list = {
+ {{"key", "value"}, "key = value"},
+ {{"key", "value"}, "key=value"},
+ {{"key", "value"}, "key =\"value\""},
+ {{"key", "value"}, "key\t=\t \"value\""},
+ {{"key", "\"value"}, "key = \"value"},
+ {{"key", "value\""}, "key = value\""},
+ {{"key", "= value"}, "key == value"},
+ {{"key", ""}, "key ="},
+ {{"key", ""}, R"(key ="")"},
+ {{"key", "\"\""}, R"(key ="""")"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseKeyTest, ::testing::ValuesIn(parse_key_list));
+
+/**
+ * Parameterized test for parse_key with valid line
+ */
+TEST_P(ParseKeyTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ EXPECT_EQ(GetParam().first, parser->parse_key(line, GetParam().second));
+}
+
+/**
+ * Tests if exception is thrown for invalid key line
+ */
+TEST_F(ParseKeyTest, throwsSyntaxError) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+
+ EXPECT_THROW(parser->parse_key(line, "= empty name"), syntax_error);
+ EXPECT_THROW(parser->parse_key(line, "forbidden char = value"), syntax_error);
+ EXPECT_THROW(parser->parse_key(line, "forbidden\tchar = value"), syntax_error);
+}
+// }}}
+
+// ParseHeaderTest {{{
+
+/**
+ * \brief Class for parameterized tests on parse_key
+ *
+ * The first element of the pair is the expected key-value pair and the second
+ * element is the string to be parsed, has to be trimmed and valid
+ */
+class ParseHeaderTest : public ConfigParser, public ::testing::WithParamInterface<pair<string, string>> {};
+
+vector<pair<string, string>> parse_header_list = {
+ {"section", "[section]"},
+ {"bar/name", "[bar/name]"},
+ {"with_underscore", "[with_underscore]"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseHeaderTest, ::testing::ValuesIn(parse_header_list));
+
+/**
+ * Parameterized test for parse_header with valid line
+ */
+TEST_P(ParseHeaderTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ EXPECT_EQ(GetParam().first, parser->parse_header(line, GetParam().second));
+}
+
+/**
+ * Tests if exception is thrown for invalid header line
+ */
+TEST_F(ParseHeaderTest, throwsSyntaxError) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+
+ EXPECT_THROW(parser->parse_header(line, "[]"), syntax_error);
+ EXPECT_THROW(parser->parse_header(line, "[no_end"), syntax_error);
+ EXPECT_THROW(parser->parse_header(line, "[forbidden char]"), syntax_error);
+ EXPECT_THROW(parser->parse_header(line, "[forbidden\tchar]"), syntax_error);
+
+ // Reserved names
+ EXPECT_THROW(parser->parse_header(line, "[self]"), syntax_error);
+ EXPECT_THROW(parser->parse_header(line, "[BAR]"), syntax_error);
+ EXPECT_THROW(parser->parse_header(line, "[root]"), syntax_error);
+}
+// }}}
+
+// ParseEscapedValueTest {{{
+
+/**
+ * \brief Class for parameterized tests on parse_escaped_value
+ *
+ * The first element of the pair is the expected value and the second
+ * element is the escaped string to be parsed.
+ */
+class ParseEscapedValueTest : public ConfigParser, public ::testing::WithParamInterface<pair<string, string>> {};
+
+vector<pair<string, string>> parse_escaped_value_list = {
+ {"\\", "\\"},
+ {"\\", "\\\\"},
+ {"\\val\\ue\\", "\\val\\ue\\"},
+ {"\\val\\ue\\", "\\\\val\\\\ue\\\\"},
+ {"\"\\val\\ue\\\"", "\"\\val\\ue\\\""},
+ {"\"\\val\\ue\\\"", "\"\\\\val\\\\ue\\\\\""},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseEscapedValueTest, ::testing::ValuesIn(parse_escaped_value_list));
+
+/**
+ * Parameterized test for parse_escaped_value with valid line
+ */
+TEST_P(ParseEscapedValueTest, correctness) {
+ line_t line;
+ line.file_index = 0;
+ line.line_no = 0;
+ string value = GetParam().second;
+ value = parser->parse_escaped_value(line, move(value), "key");
+ EXPECT_EQ(GetParam().first, value);
+}
+// }}}
--- /dev/null
+#include "drawtypes/iconset.hpp"
+
+#include "common/test.hpp"
+
+using namespace std;
+using namespace polybar;
+using namespace polybar::drawtypes;
+
+TEST(IconSet, fuzzyMatchExactMatchFirst) {
+ iconset_t icons = make_shared<iconset>();
+
+ icons->add("1", make_shared<label>("1"));
+ icons->add("10", make_shared<label>("10"));
+
+ label_t ret = icons->get("10", "", true);
+
+ EXPECT_EQ("10", ret->get());
+}
+
+TEST(IconSet, fuzzyMatchLargestSubstring) {
+ iconset_t icons = make_shared<iconset>();
+
+ icons->add("1", make_shared<label>("1"));
+ icons->add("10", make_shared<label>("10"));
+
+ label_t ret = icons->get("10a", "", true);
+
+ EXPECT_EQ("10", ret->get());
+}
+
+TEST(IconSet, fuzzyMatchFallback) {
+ iconset_t icons = make_shared<iconset>();
+
+ icons->add("1", make_shared<label>("1"));
+ icons->add("10", make_shared<label>("10"));
+ icons->add("fallback_id", make_shared<label>("fallback_label"));
+
+ label_t ret = icons->get("b", "fallback_id", true);
+
+ EXPECT_EQ("fallback_label", ret->get());
+}
--- /dev/null
+#include "drawtypes/label.hpp"
+
+#include <memory>
+#include <tuple>
+
+#include "common.hpp"
+#include "common/test.hpp"
+#include "components/types.hpp"
+
+using namespace polybar;
+using namespace std;
+using namespace polybar::drawtypes;
+
+using GetTestParamLabel = tuple<string, bool, int, int, alignment>;
+using GetTestParam = pair<string, GetTestParamLabel>;
+/**
+ * \brief Class for parameterized tests label::get
+ *
+ * The first element of the pair is the expected returned text, the second
+ * element is a 5-tuple (text, ellipsis, minlen, maxlen, alignment)
+ */
+class GetTest : public ::testing::Test, public ::testing::WithParamInterface<GetTestParam> {};
+
+vector<GetTestParam> get_list = {
+ {"...", make_tuple("abcd", true, 0, 3, alignment::RIGHT)},
+ {"abc", make_tuple("abc", true, 0, 3, alignment::RIGHT)},
+ {"abc", make_tuple("abcdefgh", false, 0, 3, alignment::RIGHT)},
+ {"a...", make_tuple("abcdefgh", true, 0, 4, alignment::RIGHT)},
+ {"abcd...", make_tuple("abcdefgh", true, 0, 7, alignment::RIGHT)},
+ {"abcdefgh", make_tuple("abcdefgh", true, 0, 8, alignment::RIGHT)},
+};
+INSTANTIATE_TEST_SUITE_P(Inst, GetTest, ::testing::ValuesIn(get_list));
+
+// No alignment needed
+vector<GetTestParam> get_no_align_list = {
+ {"abc", make_tuple("abc", true, 3, 0, alignment::LEFT)},
+ {"abc", make_tuple("abc", true, 2, 0, alignment::CENTER)},
+ {"abc", make_tuple("abc", true, 1, 0, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(NoAlignment, GetTest, ::testing::ValuesIn(get_no_align_list));
+
+// Left alignment
+vector<GetTestParam> get_left_align_list = {
+ {"a ", make_tuple("a", true, 2, 0, alignment::LEFT)},
+ {"a ", make_tuple("a", true, 3, 0, alignment::LEFT)},
+ {"abcde ", make_tuple("abcde", true, 10, 0, alignment::LEFT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(LeftAlignment, GetTest, ::testing::ValuesIn(get_left_align_list));
+
+// Center alignment
+vector<GetTestParam> get_center_align_list = {
+ {" a ", make_tuple("a", true, 3, 0, alignment::CENTER)},
+ {" a ", make_tuple("a", true, 5, 0, alignment::CENTER)},
+ {" abcd ", make_tuple("abcd", true, 10, 0, alignment::CENTER)},
+};
+
+INSTANTIATE_TEST_SUITE_P(CenterAlignment, GetTest, ::testing::ValuesIn(get_center_align_list));
+
+// Right alignment
+vector<GetTestParam> get_right_align_list = {
+ {" a", make_tuple("a", true, 3, 0, alignment::RIGHT)},
+ {" abc", make_tuple("abc", true, 4, 0, alignment::RIGHT)},
+ {" abc", make_tuple("abc", true, 10, 0, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(RightAlignment, GetTest, ::testing::ValuesIn(get_right_align_list));
+
+vector<GetTestParam> get_min_max_list = {
+ {"a ", make_tuple("a", true, 2, 2, alignment::CENTER)},
+ {"abc ", make_tuple("abc", true, 4, 4, alignment::CENTER)},
+ {"abc", make_tuple("abcd", false, 3, 3, alignment::RIGHT)},
+ {"...", make_tuple("abcd", true, 1, 3, alignment::RIGHT)},
+ {" ", make_tuple("", true, 1, 3, alignment::RIGHT)},
+ {" a", make_tuple("a", true, 2, 3, alignment::RIGHT)},
+ {"...", make_tuple("....", true, 2, 3, alignment::RIGHT)},
+ {"...", make_tuple("....", false, 2, 3, alignment::RIGHT)},
+ {"abc...", make_tuple("abcdefg", true, 6, 6, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(MinMax, GetTest, ::testing::ValuesIn(get_min_max_list));
+
+unique_ptr<label> create_alignment_test_label(GetTestParamLabel params) {
+ /* It's simpler in this case to create a label with all the constructor defaults and then set the relevant
+ * fields individually.
+ */
+ auto test_label = make_unique<label>(get<0>(params));
+ test_label->m_ellipsis = get<1>(params);
+ test_label->m_minlen = get<2>(params);
+ test_label->m_maxlen = get<3>(params);
+ test_label->m_alignment = get<4>(params);
+ return test_label;
+}
+
+TEST_P(GetTest, correctness) {
+ auto m_label = create_alignment_test_label(GetParam().second);
+
+ auto expected = GetParam().first;
+ auto actual = m_label->get();
+ EXPECT_EQ(expected, actual);
+}
+
+TEST_P(GetTest, soundness) {
+ auto m_label = create_alignment_test_label(GetParam().second);
+ auto actual = m_label->get();
+ EXPECT_TRUE(m_label->m_maxlen == 0 || actual.length() <= m_label->m_maxlen) << "Returned text is longer than maxlen";
+ EXPECT_GE(actual.length(), m_label->m_minlen) << "Returned text is shorter than minlen";
+}
--- /dev/null
+#include "drawtypes/layouticonset.hpp"
+
+#include "common/test.hpp"
+
+using namespace std;
+using namespace polybar;
+using namespace polybar::drawtypes;
+
+TEST(LayoutIconSet, get) {
+ layouticonset_t layout_icons = make_shared<layouticonset>(make_shared<label>("default-icon"));
+
+ EXPECT_EQ("default-icon", layout_icons->get("", "")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("any_layout", "")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("", "any_variant")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("any_layout", "any_variant")->get());
+
+ // us;icon => layout 'us' with any variant
+ EXPECT_TRUE(layout_icons->add("us", layouticonset::VARIANT_ANY, make_shared<label>("us--icon")));
+
+ EXPECT_EQ("default-icon", layout_icons->get("", "")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("any_layout", "")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("", "any_variant")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("any_layout", "any_variant")->get());
+ EXPECT_EQ("default-icon", layout_icons->get("Us", "")->get());
+
+ EXPECT_EQ("us--icon", layout_icons->get("us", "")->get());
+ EXPECT_EQ("us--icon", layout_icons->get("us", "undefined_variant")->get());
+
+ // us;colemak;icon => layout 'us' with 'colemak' variant
+ EXPECT_TRUE(layout_icons->add("us", "colemak", make_shared<label>("us-colemak-icon")));
+
+ EXPECT_EQ("us--icon", layout_icons->get("us", "undefined_variant")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "colemak")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "COLEMAK")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "a variant containing CoLeMaK in its description")->get());
+
+ // us;;icon => layout 'us' with no variant
+ EXPECT_TRUE(layout_icons->add("us", "", make_shared<label>("us-no_variant-icon")));
+
+ EXPECT_EQ("us-no_variant-icon", layout_icons->get("us", "")->get());
+ EXPECT_EQ("us--icon", layout_icons->get("us", "undefined_variant")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "colemak")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "COLEMAK")->get());
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "a variant containing CoLeMaK in its description")->get());
+
+ // _;dvorak;icon => any layout with 'dvorak' variant
+ EXPECT_TRUE(layout_icons->add(layouticonset::VARIANT_ANY, "dvorak", make_shared<label>("any_layout-dvorak-icon")));
+ EXPECT_EQ("any_layout-dvorak-icon", layout_icons->get("fr", "dvorak")->get());
+ EXPECT_EQ("any_layout-dvorak-icon", layout_icons->get("fr", "dVORAk")->get());
+ EXPECT_EQ("us--icon", layout_icons->get("us", "dvorak")->get());
+
+ // us;dvorak;icon => layout 'us' with 'dvorak' variant
+ EXPECT_TRUE(layout_icons->add("us", "dvorak", make_shared<label>("us-dvorak-icon")));
+ EXPECT_EQ("any_layout-dvorak-icon", layout_icons->get("fr", "dvorak")->get());
+ EXPECT_EQ("us-dvorak-icon", layout_icons->get("us", "dvorak")->get());
+
+ // _;;icon => any layout with no variant
+ EXPECT_TRUE(layout_icons->add(layouticonset::VARIANT_ANY, "", make_shared<label>("any_layout-no_variant-icon")));
+ EXPECT_EQ("any_layout-no_variant-icon", layout_icons->get("fr", "")->get());
+ EXPECT_EQ("us-no_variant-icon", layout_icons->get("us", "")->get());
+
+ EXPECT_TRUE(layout_icons->add("us", "variant2", make_shared<label>("us-variant2-icon")));
+ EXPECT_EQ("us-colemak-icon", layout_icons->get("us", "a variant containing CoLeMaK & variant2 in its description")->get());
+
+ EXPECT_TRUE(layout_icons->add(layouticonset::VARIANT_ANY, "variant2", make_shared<label>("any_layout-variant2-icon")));
+ EXPECT_EQ("any_layout-dvorak-icon", layout_icons->get("some layout", "a variant containing dvorak & variant2 in its description")->get());
+
+ // us;_;icon => layout 'us' with any variant
+ layouticonset_t layout_icons2 = make_shared<layouticonset>(make_shared<label>("default-icon"));
+ EXPECT_TRUE(layout_icons2->add("us", "_", make_shared<label>("us-any_variant-icon")));
+ EXPECT_EQ("us-any_variant-icon", layout_icons2->get("us", "")->get());
+ EXPECT_EQ("us-any_variant-icon", layout_icons2->get("us", "whatever variant")->get());
+
+ EXPECT_FALSE(layout_icons->add(
+ layouticonset::VARIANT_ANY, layouticonset::VARIANT_ANY, make_shared<label>("any_layout-no_variant-icon")));
+}
--- /dev/null
+#include "drawtypes/ramp.hpp"
+
+#include "common/test.hpp"
+#include "utils/factory.hpp"
+
+using namespace polybar::drawtypes;
+using namespace polybar;
+
+TEST(Ramp, perc) {
+ ramp r;
+ r.add(std::make_shared<label>("test1", 0));
+ r.add(std::make_shared<label>("test2", 0));
+ r.add(std::make_shared<label>("test3", 0));
+ EXPECT_EQ("test1", r.get_by_percentage(33)->get());
+ EXPECT_EQ("test2", r.get_by_percentage(34)->get());
+ EXPECT_EQ("test3", r.get_by_percentage(67)->get());
+ EXPECT_EQ("test1", r.get_by_percentage_with_borders(19, 20, 40)->get());
+ EXPECT_EQ("test2", r.get_by_percentage_with_borders(21, 20, 40)->get());
+ EXPECT_EQ("test2", r.get_by_percentage_with_borders(39, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(41, 20, 40)->get());
+ EXPECT_EQ("test1", r.get_by_percentage_with_borders(20, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(40, 20, 40)->get());
+ r.add(std::make_shared<label>("test4", 0));
+ EXPECT_EQ("test2", r.get_by_percentage_with_borders(29, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(31, 20, 40)->get());
+}
+
+TEST(Ramp, weights) {
+ ramp r;
+ r.add(std::make_shared<label>("test1", 0), 1);
+ r.add(std::make_shared<label>("test2", 0), 2);
+ r.add(std::make_shared<label>("test3", 0), 5);
+
+ EXPECT_EQ("test1", r.get_by_percentage(12)->get());
+ EXPECT_EQ("test2", r.get_by_percentage(13)->get());
+ EXPECT_EQ("test2", r.get_by_percentage(37)->get());
+ EXPECT_EQ("test3", r.get_by_percentage(38)->get());
+
+ EXPECT_EQ("test1", r.get_by_percentage_with_borders(19, 20, 40)->get());
+ EXPECT_EQ("test2", r.get_by_percentage_with_borders(21, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(39, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(41, 20, 40)->get());
+ EXPECT_EQ("test1", r.get_by_percentage_with_borders(20, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(40, 20, 40)->get());
+ r.add(std::make_shared<label>("test4", 0));
+ r.add(std::make_shared<label>("test5", 0));
+ EXPECT_EQ("test2", r.get_by_percentage_with_borders(24, 20, 40)->get());
+ EXPECT_EQ("test3", r.get_by_percentage_with_borders(25, 20, 40)->get());
+}
--- /dev/null
+#include "ipc/decoder.hpp"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "common/test.hpp"
+#include "components/logger.hpp"
+#include "gmock/gmock.h"
+#include "ipc/msg.hpp"
+
+using namespace polybar;
+using namespace ipc;
+using ::testing::InSequence;
+
+static std::vector<uint8_t> get_msg(decltype(MAGIC) magic, uint32_t version, type_t type, const std::string& msg) {
+ std::vector<uint8_t> data(HEADER_SIZE);
+ header* header = reinterpret_cast<ipc::header*>(data.data());
+ std::copy(magic.begin(), magic.end(), header->s.magic);
+ header->s.version = version;
+ header->s.size = msg.size();
+ header->s.type = type;
+
+ data.insert(data.end(), msg.begin(), msg.end());
+
+ return data;
+}
+
+static decltype(MAGIC) MAGIC_WRONG = {'0', '0', '0', '0', '0', '0', '0'};
+static type_t TYPE_ACTION = to_integral(v0::ipc_type::ACTION);
+
+static auto MSG1 = get_msg(MAGIC, VERSION, TYPE_ACTION, "foobar");
+static auto MSG2 = get_msg(MAGIC, VERSION, TYPE_ACTION, "");
+static auto MSG_WRONG1 = get_msg(MAGIC_WRONG, VERSION, TYPE_ACTION, "");
+static auto MSG_WRONG2 = get_msg(MAGIC, 120, TYPE_ACTION, "");
+static auto MSG_WRONG3 = get_msg(MAGIC_WRONG, 120, TYPE_ACTION, "");
+
+class MockCallback {
+ public:
+ MOCK_METHOD(void, cb, (uint8_t version, type_t, const vector<uint8_t>&));
+};
+
+static logger null_logger(loglevel::NONE);
+
+class DecoderTest : public ::testing::Test {
+ protected:
+ MockCallback cb;
+ decoder dec{null_logger, [this](uint8_t version, auto type, const auto& data) { cb.cb(version, type, data); }};
+};
+
+TEST_F(DecoderTest, single_msg1) {
+ EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(1);
+ EXPECT_NO_THROW(dec.on_read(MSG1.data(), MSG1.size()));
+}
+
+TEST_F(DecoderTest, single_msg2) {
+ EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG2.begin() + HEADER_SIZE, MSG2.end()))).Times(1);
+ EXPECT_NO_THROW(dec.on_read(MSG2.data(), MSG2.size()));
+}
+
+TEST_F(DecoderTest, single_msg_wrong1) {
+ EXPECT_THROW(dec.on_read(MSG_WRONG1.data(), MSG_WRONG1.size()), decoder::error);
+ // After an error, any further read fails
+ EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
+}
+
+TEST_F(DecoderTest, single_msg_wrong2) {
+ EXPECT_THROW(dec.on_read(MSG_WRONG2.data(), MSG_WRONG2.size()), decoder::error);
+ // After an error, any further read fails
+ EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
+}
+
+TEST_F(DecoderTest, single_msg_wrong3) {
+ EXPECT_THROW(dec.on_read(MSG_WRONG3.data(), MSG_WRONG3.size()), decoder::error);
+ // After an error, any further read fails
+ EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
+}
+
+TEST_F(DecoderTest, byte_by_byte) {
+ EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(1);
+ for (const uint8_t c : MSG1) {
+ EXPECT_NO_THROW(dec.on_read(&c, 1));
+ }
+}
+
+TEST_F(DecoderTest, multiple) {
+ static constexpr int NUM_ITER = 10;
+ {
+ InSequence seq;
+ EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(NUM_ITER);
+ }
+
+ for (int i = 0; i < NUM_ITER; i++) {
+ EXPECT_NO_THROW(dec.on_read(MSG1.data(), MSG1.size()));
+ }
+}
--- /dev/null
+#include "ipc/encoder.hpp"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "common/test.hpp"
+#include "gmock/gmock.h"
+#include "ipc/decoder.hpp"
+#include "ipc/msg.hpp"
+
+using namespace polybar;
+using namespace ipc;
+using ::testing::InSequence;
+
+static logger null_logger(loglevel::TRACE);
+
+class MockCallback {
+ public:
+ MOCK_METHOD(void, cb, (uint8_t version, type_t, const vector<uint8_t>&));
+};
+
+class EncoderTest : public ::testing::Test, public testing::WithParamInterface<string> {
+ protected:
+ MockCallback cb;
+ decoder dec{null_logger, [this](uint8_t version, auto type, const auto& data) { cb.cb(version, type, data); }};
+};
+
+vector<string> encoder_list = {
+ ""s,
+ "foo"s,
+ "\0\1"s,
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, EncoderTest, ::testing::ValuesIn(encoder_list));
+
+TEST_P(EncoderTest, simple) {
+ auto param = GetParam();
+ const auto encoded = encode(TYPE_ERR, param);
+ const header* h = reinterpret_cast<const header*>(encoded.data());
+
+ EXPECT_EQ(std::memcmp(h->s.magic, MAGIC.data(), MAGIC.size()), 0);
+ EXPECT_EQ(h->s.version, 0);
+ EXPECT_EQ(h->s.size, param.size());
+ EXPECT_EQ(h->s.type, TYPE_ERR);
+ if (!param.empty()) {
+ EXPECT_EQ(std::memcmp(encoded.data() + HEADER_SIZE, param.data(), param.size()), 0);
+ }
+}
+
+TEST_P(EncoderTest, roundtrip) {
+ auto param = GetParam();
+ auto payload = vector<uint8_t>(param.begin(), param.end());
+ const auto encoded = encode(TYPE_ERR, param);
+
+ EXPECT_CALL(cb, cb(0, TYPE_ERR, payload)).Times(1);
+ EXPECT_NO_THROW(dec.on_read(encoded.data(), encoded.size()));
+}
--- /dev/null
+#include "ipc/util.hpp"
+
+#include "common/test.hpp"
+#include "ipc/msg.hpp"
+
+using namespace polybar;
+using namespace ipc;
+
+TEST(GetSocketPath, RoundTrip) {
+ EXPECT_EQ(123, get_pid_from_socket(get_socket_path(123)));
+ EXPECT_EQ(1, get_pid_from_socket(get_socket_path(1)));
+
+ EXPECT_EQ(-1, get_pid_from_socket(get_glob_socket_path()));
+}
+
+TEST(PidFromSocket, EdgeCases) {
+ EXPECT_EQ(-1, get_pid_from_socket(""));
+ EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.txt"));
+ EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.sock"));
+ EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo..sock"));
+ EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.bar.sock"));
+}
--- /dev/null
+#include "tags/action_context.hpp"
+
+#include "common/test.hpp"
+#include "components/logger.hpp"
+
+using namespace polybar;
+using namespace std;
+using namespace tags;
+
+TEST(ActionCtxtTest, dblClick) {
+ action_context ctxt;
+
+ EXPECT_FALSE(ctxt.has_double_click());
+
+ ctxt.action_open(mousebtn::DOUBLE_LEFT, "", alignment::LEFT, 0);
+ ctxt.action_close(mousebtn::DOUBLE_LEFT, alignment::LEFT, 1);
+
+ EXPECT_TRUE(ctxt.has_double_click());
+
+ ctxt.reset();
+
+ ctxt.action_open(mousebtn::DOUBLE_MIDDLE, "", alignment::LEFT, 0);
+ ctxt.action_close(mousebtn::DOUBLE_MIDDLE, alignment::LEFT, 1);
+
+ EXPECT_TRUE(ctxt.has_double_click());
+
+ ctxt.reset();
+
+ ctxt.action_open(mousebtn::DOUBLE_RIGHT, "", alignment::LEFT, 0);
+ ctxt.action_close(mousebtn::DOUBLE_RIGHT, alignment::LEFT, 1);
+
+ EXPECT_TRUE(ctxt.has_double_click());
+}
+
+TEST(ActionCtxtTest, closing) {
+ action_context ctxt;
+
+ auto id1 = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 0);
+ auto id2 = ctxt.action_open(mousebtn::RIGHT, "", alignment::CENTER, 0);
+ auto id3 = ctxt.action_open(mousebtn::RIGHT, "", alignment::LEFT, 0);
+ auto id4 = ctxt.action_open(mousebtn::MIDDLE, "", alignment::LEFT, 0);
+
+ EXPECT_NE(NO_ACTION, id1);
+ EXPECT_NE(NO_ACTION, id2);
+ EXPECT_NE(NO_ACTION, id3);
+ EXPECT_NE(NO_ACTION, id4);
+
+ EXPECT_EQ(4, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(id1, mousebtn::LEFT), ctxt.action_close(mousebtn::LEFT, alignment::LEFT, 1));
+ EXPECT_EQ(3, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(id4, mousebtn::MIDDLE), ctxt.action_close(mousebtn::NONE, alignment::LEFT, 1));
+ EXPECT_EQ(2, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(id3, mousebtn::RIGHT), ctxt.action_close(mousebtn::NONE, alignment::LEFT, 1));
+ EXPECT_EQ(1, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(id2, mousebtn::RIGHT), ctxt.action_close(mousebtn::NONE, alignment::CENTER, 1));
+ EXPECT_EQ(0, ctxt.num_unclosed());
+
+ EXPECT_EQ(4, ctxt.num_actions());
+}
+
+TEST(ActionCtxtTest, doubleClosing) {
+ action_context ctxt;
+
+ auto id = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 0);
+
+ EXPECT_NE(NO_ACTION, id);
+
+ EXPECT_EQ(1, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(id, mousebtn::LEFT), ctxt.action_close(mousebtn::LEFT, alignment::LEFT, 1));
+ EXPECT_EQ(0, ctxt.num_unclosed());
+ EXPECT_EQ(make_pair(tags::NO_ACTION, mousebtn::NONE), ctxt.action_close(mousebtn::LEFT, alignment::LEFT, 1));
+ EXPECT_EQ(0, ctxt.num_unclosed());
+}
+
+TEST(ActionCtxtTest, overlapping) {
+ action_context ctxt;
+
+ /*
+ * clang-format off
+ *
+ * Sets up the following overlapping of actions:
+ * 0123456
+ * 1: [--) (LEFT)
+ * 2: [----) (MIDDLE)
+ * 3: [--) (RIGHT)
+ * clang-format on
+ */
+
+ auto id1 = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 0);
+ auto id2 = ctxt.action_open(mousebtn::MIDDLE, "", alignment::LEFT, 1);
+ auto id3 = ctxt.action_open(mousebtn::RIGHT, "", alignment::LEFT, 2);
+ EXPECT_EQ(make_pair(id1, mousebtn::LEFT), ctxt.action_close(mousebtn::LEFT, alignment::LEFT, 3));
+ EXPECT_EQ(make_pair(id3, mousebtn::RIGHT), ctxt.action_close(mousebtn::RIGHT, alignment::LEFT, 6));
+ EXPECT_EQ(make_pair(id2, mousebtn::MIDDLE), ctxt.action_close(mousebtn::MIDDLE, alignment::LEFT, 5));
+
+ auto actions = ctxt.get_actions(2);
+
+ EXPECT_EQ(id1, actions[mousebtn::LEFT]);
+ EXPECT_EQ(id2, actions[mousebtn::MIDDLE]);
+ EXPECT_EQ(id3, actions[mousebtn::RIGHT]);
+
+ EXPECT_EQ(3, ctxt.num_actions());
+ EXPECT_EQ(0, ctxt.num_unclosed());
+}
+
+TEST(ActionCtxtTest, stacking) {
+ action_context ctxt;
+
+ /*
+ * clang-format off
+ *
+ * Sets up the following stacked actions:
+ * 012345678
+ * 1: [-------)
+ * 2: [-----)
+ * 3: [--)
+ * clang-format on
+ */
+
+ auto id1 = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 0);
+ auto id2 = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 1);
+ auto id3 = ctxt.action_open(mousebtn::LEFT, "", alignment::LEFT, 3);
+ EXPECT_EQ(make_pair(id3, mousebtn::LEFT), ctxt.action_close(mousebtn::NONE, alignment::LEFT, 6));
+ EXPECT_EQ(make_pair(id2, mousebtn::LEFT), ctxt.action_close(mousebtn::NONE, alignment::LEFT, 7));
+ EXPECT_EQ(make_pair(id1, mousebtn::LEFT), ctxt.action_close(mousebtn::NONE, alignment::LEFT, 8));
+
+ EXPECT_EQ(id1, ctxt.has_action(mousebtn::LEFT, 0));
+ EXPECT_EQ(id2, ctxt.has_action(mousebtn::LEFT, 1));
+ EXPECT_EQ(id2, ctxt.has_action(mousebtn::LEFT, 2));
+ EXPECT_EQ(id3, ctxt.has_action(mousebtn::LEFT, 3));
+ EXPECT_EQ(id3, ctxt.has_action(mousebtn::LEFT, 4));
+ EXPECT_EQ(id3, ctxt.has_action(mousebtn::LEFT, 5));
+ EXPECT_EQ(id2, ctxt.has_action(mousebtn::LEFT, 6));
+ EXPECT_EQ(id1, ctxt.has_action(mousebtn::LEFT, 7));
+
+ EXPECT_EQ(3, ctxt.num_actions());
+ EXPECT_EQ(0, ctxt.num_unclosed());
+}
+
+TEST(ActionCtxtTest, cmd) {
+ action_context ctxt;
+
+ string cmd = "foobar";
+
+ auto id = ctxt.action_open(mousebtn::DOUBLE_RIGHT, cmd.substr(), alignment::RIGHT, 0);
+
+ EXPECT_EQ(cmd, ctxt.get_action(id));
+}
+
+TEST(ActionCtxtTest, getActionBlocks) {
+ action_context ctxt;
+
+ ctxt.action_open(mousebtn::DOUBLE_MIDDLE, "", alignment::RIGHT, 0);
+ ctxt.action_close(mousebtn::DOUBLE_MIDDLE, alignment::RIGHT, 1);
+
+ const auto& blocks = ctxt.get_blocks();
+
+ ASSERT_EQ(1, blocks.size());
+
+ const auto block = blocks[0];
+
+ EXPECT_EQ("", block.cmd);
+ EXPECT_EQ(0, block.start_x);
+ EXPECT_EQ(1, block.end_x);
+ EXPECT_FALSE(block.is_open);
+ EXPECT_EQ(mousebtn::DOUBLE_MIDDLE, block.button);
+ EXPECT_EQ(alignment::RIGHT, block.align);
+}
--- /dev/null
+#include "tags/dispatch.hpp"
+
+#include "common/test.hpp"
+#include "components/logger.hpp"
+#include "events/signal_emitter.hpp"
+#include "gmock/gmock.h"
+
+using namespace polybar;
+using namespace std;
+using namespace tags;
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::Truly;
+
+namespace polybar {
+ inline bool operator==(const extent_val& a, const extent_val& b) {
+ return a.type == b.type && a.value == b.value;
+ }
+} // namespace polybar
+
+/**
+ * Fake renderer that just tracks the current x-position per alignment.
+ *
+ * Each text character and point is treated as a single pixel.
+ */
+class FakeRenderer : public renderer_interface {
+ public:
+ FakeRenderer(action_context& action_ctxt) : renderer_interface(action_ctxt){};
+
+ void render_offset(const tags::context& ctxt, const extent_val offset) override {
+ EXPECT_NE(alignment::NONE, ctxt.get_alignment());
+ block_x[ctxt.get_alignment()] += offset.value;
+ };
+
+ void render_text(const tags::context& ctxt, const string&& str) override {
+ EXPECT_NE(alignment::NONE, ctxt.get_alignment());
+ block_x[ctxt.get_alignment()] += str.size();
+ };
+
+ void change_alignment(const tags::context& ctxt) override {
+ EXPECT_NE(alignment::NONE, ctxt.get_alignment());
+ };
+
+ double get_x(const tags::context& ctxt) const override {
+ EXPECT_NE(alignment::NONE, ctxt.get_alignment());
+ return block_x.at(ctxt.get_alignment());
+ };
+
+ double get_alignment_start(const alignment) const override {
+ return 0;
+ };
+
+ void apply_tray_position(const tags::context&) override{};
+
+ private:
+ map<alignment, int> block_x = {
+ {alignment::LEFT, 0},
+ {alignment::CENTER, 0},
+ {alignment::RIGHT, 0},
+ };
+};
+
+class MockRenderer : public renderer_interface {
+ public:
+ MockRenderer(action_context& action_ctxt) : renderer_interface(action_ctxt), fake(action_ctxt){};
+
+ MOCK_METHOD(void, render_offset, (const context& ctxt, const extent_val offset), (override));
+ MOCK_METHOD(void, render_text, (const context& ctxt, const string&& str), (override));
+ MOCK_METHOD(void, change_alignment, (const context& ctxt), (override));
+ MOCK_METHOD(double, get_x, (const context& ctxt), (const, override));
+ MOCK_METHOD(double, get_alignment_start, (const alignment align), (const, override));
+ MOCK_METHOD(void, apply_tray_position, (const polybar::tags::context& context), (override));
+
+ void DelegateToFake() {
+ ON_CALL(*this, render_offset).WillByDefault([this](const context& ctxt, const extent_val offset) {
+ fake.render_offset(ctxt, offset);
+ });
+
+ ON_CALL(*this, render_text).WillByDefault([this](const context& ctxt, const string&& str) {
+ fake.render_text(ctxt, std::forward<const string>(str));
+ });
+
+ ON_CALL(*this, get_x).WillByDefault([this](const context& ctxt) { return fake.get_x(ctxt); });
+
+ ON_CALL(*this, get_alignment_start).WillByDefault([this](const alignment a) {
+ return fake.get_alignment_start(a);
+ });
+
+ ON_CALL(*this, apply_tray_position).WillByDefault([this](const context& context) {
+ return fake.apply_tray_position(context);
+ });
+ }
+
+ private:
+ FakeRenderer fake;
+};
+
+static auto match_fg = [](rgba c) { return Property("get_fg", &context::get_fg, c); };
+static auto match_bg = [](rgba c) { return Property("get_bg", &context::get_bg, c); };
+static auto match_ol = [](rgba c) { return Property("get_ol", &context::get_ol, c); };
+static auto match_ul = [](rgba c) { return Property("get_ul", &context::get_ul, c); };
+static auto match_font = [](int font) { return Property("get_font", &context::get_font, font); };
+static auto match_overline = [](bool has) { return Property("has_overline", &context::has_overline, has); };
+static auto match_underline = [](bool has) { return Property("has_underline", &context::has_underline, has); };
+
+static auto match_align = [](alignment align) { return Property("get_alignment", &context::get_alignment, align); };
+static auto match_left_align = match_align(alignment::LEFT);
+static auto match_center_align = match_align(alignment::CENTER);
+static auto match_right_align = match_align(alignment::RIGHT);
+
+class DispatchTest : public ::testing::Test {
+ protected:
+ unique_ptr<action_context> m_action_ctxt = make_unique<action_context>();
+
+ unique_ptr<dispatch> m_dispatch = make_unique<dispatch>(logger(loglevel::NONE), *m_action_ctxt);
+
+ ::testing::NiceMock<MockRenderer> r{*m_action_ctxt};
+ void SetUp() override {
+ r.DelegateToFake();
+ }
+};
+
+TEST_F(DispatchTest, ignoreFormatting) {
+ {
+ InSequence seq;
+ EXPECT_CALL(r, render_offset(_, extent_val{extent_type::PIXEL, 10})).Times(1);
+ EXPECT_CALL(r, render_text(_, string{"abc"})).Times(1);
+ EXPECT_CALL(r, render_text(_, string{"foo"})).Times(1);
+ }
+
+ bar_settings settings;
+ m_dispatch->parse(settings, r, "%{l}%{O10}%{F#ff0000}abc%{F-}foo");
+}
+
+TEST_F(DispatchTest, formatting) {
+ bar_settings settings;
+ rgba bar_fg = settings.foreground;
+ rgba bar_bg = settings.background;
+
+ rgba c1{"#ff0000"};
+ rgba c2{"#00ff00"};
+ rgba c3{"#0000ff"};
+ rgba c4{"#f0f0f0"};
+
+ {
+ InSequence seq;
+ EXPECT_CALL(r, change_alignment(match_left_align)).Times(1);
+ EXPECT_CALL(r, render_offset(_, extent_val{extent_type::PIXEL, 10})).Times(1);
+ EXPECT_CALL(r, render_text(match_fg(c1), string{"abc"})).Times(1);
+ EXPECT_CALL(r, render_text(match_fg(bar_fg), string{"foo"})).Times(1);
+ EXPECT_CALL(r, change_alignment(match_left_align)).Times(1);
+ EXPECT_CALL(r, render_text(AllOf(match_left_align, match_bg(c2), match_fg(bar_fg)), string{"bar"})).Times(1);
+ EXPECT_CALL(r, render_text(AllOf(match_left_align, match_fg(c2), match_bg(bar_fg)), string{"123"})).Times(1);
+ EXPECT_CALL(r, change_alignment(match_center_align)).Times(1);
+ EXPECT_CALL(r, render_text(match_center_align, string{"baz"})).Times(1);
+ EXPECT_CALL(r, change_alignment(match_right_align)).Times(1);
+ EXPECT_CALL(r, render_text(AllOf(match_right_align, match_font(123)), string{"def"})).Times(1);
+ EXPECT_CALL(r, render_text(AllOf(match_right_align, match_fg(bar_fg), match_bg(bar_bg)), string{"ghi"})).Times(1);
+ }
+
+ m_dispatch->parse(settings, r,
+ "%{l}%{O10}%{F#ff0000}abc%{F-}foo%{l}%{B#00ff00}bar%{R}123%{c}baz%{r}%{T123}def%{PR}"
+ "ghi");
+}
+
+TEST_F(DispatchTest, formattingAttributes) {
+ bar_settings settings;
+ rgba bar_ol = settings.overline.color;
+ rgba bar_ul = settings.underline.color;
+
+ rgba c1{"#0000ff"};
+ rgba c2{"#f0f0f0"};
+
+ {
+ InSequence seq;
+ EXPECT_CALL(
+ r, render_text(AllOf(match_ul(c1), match_ol(c2), match_overline(true), match_underline(true)), string{"123"}))
+ .Times(1);
+ EXPECT_CALL(
+ r, render_text(AllOf(match_ul(c1), match_ol(c2), match_overline(false), match_underline(false)), string{"456"}))
+ .Times(1);
+ EXPECT_CALL(
+ r, render_text(AllOf(match_ul(c1), match_ol(c2), match_overline(true), match_underline(true)), string{"789"}))
+ .Times(1);
+ EXPECT_CALL(r, render_text(AllOf(match_ul(bar_ul), match_ol(bar_ol), match_overline(false), match_underline(false)),
+ string{"0"}))
+ .Times(1);
+ }
+
+ m_dispatch->parse(settings, r, "%{l}%{u#0000ff o#f0f0f0 +o +u}123%{-u -o}456%{!u !o}789%{PR}0");
+}
+
+TEST_F(DispatchTest, unclosedActions) {
+ { InSequence seq; }
+
+ bar_settings settings;
+ EXPECT_THROW(m_dispatch->parse(settings, r, "%{l}%{A1:cmd:}foo"), runtime_error);
+}
+
+TEST_F(DispatchTest, actions) {
+ bar_settings settings;
+ m_dispatch->parse(settings, r, "%{l}foo%{A1:cmd:}bar%{A}");
+
+ const auto& actions = m_action_ctxt->get_blocks();
+
+ ASSERT_EQ(1, actions.size());
+
+ const auto& blk = actions[0];
+
+ EXPECT_EQ(3, blk.start_x);
+ EXPECT_EQ(6, blk.end_x);
+ EXPECT_EQ(alignment::LEFT, blk.align);
+ EXPECT_EQ(mousebtn::LEFT, blk.button);
+ EXPECT_EQ("cmd", blk.cmd);
+}
+
+TEST_F(DispatchTest, actionOffsetStart) {
+ bar_settings settings;
+ m_dispatch->parse(settings, r, "%{l}a%{A1:cmd:}%{O-1}bar%{A}");
+
+ const auto& actions = m_action_ctxt->get_blocks();
+
+ ASSERT_EQ(1, actions.size());
+
+ const auto& blk = actions[0];
+
+ EXPECT_EQ(0, blk.start_x);
+ EXPECT_EQ(3, blk.end_x);
+ EXPECT_EQ(alignment::LEFT, blk.align);
+ EXPECT_EQ(mousebtn::LEFT, blk.button);
+ EXPECT_EQ("cmd", blk.cmd);
+}
+
+TEST_F(DispatchTest, actionOffsetEnd) {
+ bar_settings settings;
+ m_dispatch->parse(settings, r, "%{l}a%{A1:cmd:}bar%{O-3}%{A}");
+
+ const auto& actions = m_action_ctxt->get_blocks();
+
+ ASSERT_EQ(1, actions.size());
+
+ const auto& blk = actions[0];
+
+ EXPECT_EQ(1, blk.start_x);
+ EXPECT_EQ(4, blk.end_x);
+ EXPECT_EQ(alignment::LEFT, blk.align);
+ EXPECT_EQ(mousebtn::LEFT, blk.button);
+ EXPECT_EQ("cmd", blk.cmd);
+}
+
+TEST_F(DispatchTest, actionOffsetBefore) {
+ bar_settings settings;
+ m_dispatch->parse(settings, r, "%{l}%{O100 O-100}a%{A1:cmd:}bar%{O-3}%{A}");
+
+ const auto& actions = m_action_ctxt->get_blocks();
+
+ ASSERT_EQ(1, actions.size());
+
+ const auto& blk = actions[0];
+
+ EXPECT_EQ(1, blk.start_x);
+ EXPECT_EQ(4, blk.end_x);
+ EXPECT_EQ(alignment::LEFT, blk.align);
+ EXPECT_EQ(mousebtn::LEFT, blk.button);
+ EXPECT_EQ("cmd", blk.cmd);
+}
--- /dev/null
+#include "tags/parser.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+using namespace tags;
+
+/**
+ * Helper class to test parsed data.
+ *
+ * The expect_* functions will check that the current element corresponds to
+ * what is expected and then move to the next element.
+ *
+ * The assert_* functions are used internally to check certain properties of the
+ * current element.
+ */
+class TestableTagParser : public parser {
+ public:
+ TestableTagParser() : TestableTagParser(""){};
+
+ TestableTagParser(const string&& input) {
+ setup_parser_test(std::move(input));
+ }
+
+ void setup_parser_test(const string& input) {
+ this->set(std::move(input));
+ }
+
+ void expect_done() {
+ EXPECT_FALSE(has_next_element());
+ }
+
+ void expect_text(const string&& exp) {
+ set_current();
+ assert_is_tag(false);
+ EXPECT_EQ(exp, current.data);
+ }
+
+ void expect_color_reset(syntaxtag t) {
+ set_current();
+ assert_format(t);
+ EXPECT_EQ(color_type::RESET, current.tag_data.color.type);
+ }
+
+ void expect_color(syntaxtag t, const string& color) {
+ set_current();
+ assert_format(t);
+ rgba c{color};
+ EXPECT_EQ(color_type::COLOR, current.tag_data.color.type);
+ EXPECT_EQ(c, current.tag_data.color.val);
+ }
+
+ void expect_action_closing(mousebtn exp = mousebtn::NONE) {
+ set_current();
+ assert_format(syntaxtag::A);
+ EXPECT_TRUE(current.tag_data.action.closing);
+ EXPECT_EQ(exp, current.tag_data.action.btn);
+ }
+
+ void expect_action(const string& exp, mousebtn btn) {
+ set_current();
+ assert_format(syntaxtag::A);
+ EXPECT_FALSE(current.tag_data.action.closing);
+ EXPECT_EQ(btn, current.tag_data.action.btn);
+ EXPECT_EQ(exp, current.data);
+ }
+
+ void expect_font_reset() {
+ expect_font(0);
+ }
+
+ void expect_font(unsigned exp) {
+ set_current();
+ assert_format(syntaxtag::T);
+ EXPECT_EQ(exp, current.tag_data.font);
+ }
+
+ void expect_offset_pixel(int exp) {
+ set_current();
+ assert_format(syntaxtag::O);
+ EXPECT_EQ(extent_type::PIXEL, current.tag_data.offset.type);
+ EXPECT_EQ(exp, current.tag_data.offset.value);
+ }
+
+ void expect_offset_points(float exp) {
+ set_current();
+ assert_format(syntaxtag::O);
+ EXPECT_EQ(extent_type::POINT, current.tag_data.offset.type);
+ EXPECT_EQ(exp, current.tag_data.offset.value);
+ }
+
+ void expect_ctrl(controltag exp) {
+ set_current();
+ assert_format(syntaxtag::P);
+ EXPECT_EQ(exp, current.tag_data.ctrl);
+ }
+
+ void expect_alignment(syntaxtag exp) {
+ set_current();
+ assert_format(exp);
+ }
+
+ void expect_activation(attr_activation act, attribute attr) {
+ set_current();
+ assert_type(tag_type::ATTR);
+ EXPECT_EQ(act, current.tag_data.subtype.activation);
+ EXPECT_EQ(attr, current.tag_data.attr);
+ }
+
+ void expect_reverse() {
+ set_current();
+ assert_format(syntaxtag::R);
+ }
+
+ private:
+ void assert_format(syntaxtag exp) {
+ assert_type(tag_type::FORMAT);
+ ASSERT_EQ(exp, current.tag_data.subtype.format);
+ }
+
+ void assert_type(tag_type exp) {
+ assert_is_tag(true);
+ ASSERT_EQ(exp, current.tag_data.type);
+ }
+
+ void assert_is_tag(bool exp) {
+ ASSERT_EQ(exp, current.is_tag);
+ }
+
+ void assert_has() {
+ if (!has_next_element()) {
+ throw std::runtime_error("no next element");
+ }
+ }
+
+ void set_current() {
+ assert_has();
+ current = next_element();
+ }
+
+ element current;
+};
+
+class TagParserTest : public ::testing::Test {
+ protected:
+ TestableTagParser p;
+};
+
+TEST_F(TagParserTest, empty) {
+ p.setup_parser_test("");
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, text) {
+ p.setup_parser_test("text");
+ p.expect_text("text");
+ p.expect_done();
+}
+
+// Single Tag {{{
+
+// Parse Single Color {{{
+/**
+ * <input, tag, colorstring>
+ *
+ * If the color string is empty, this is supposed to be a color reset
+ */
+using single_color = std::tuple<string, syntaxtag, string>;
+
+class ParseSingleColorTest : public TagParserTest, public ::testing::WithParamInterface<single_color> {};
+
+vector<single_color> parse_single_color_list = {
+ {"%{B-}", syntaxtag::B, ""},
+ {"%{F-}", syntaxtag::F, ""},
+ {"%{o-}", syntaxtag::o, ""},
+ {"%{u-}", syntaxtag::u, ""},
+ {"%{B}", syntaxtag::B, ""},
+ {"%{F}", syntaxtag::F, ""},
+ {"%{o}", syntaxtag::o, ""},
+ {"%{u}", syntaxtag::u, ""},
+ {"%{B#f0f0f0}", syntaxtag::B, "#f0f0f0"},
+ {"%{F#abc}", syntaxtag::F, "#abc"},
+ {"%{o#abcd}", syntaxtag::o, "#abcd"},
+ {"%{u#FDE}", syntaxtag::u, "#FDE"},
+ {"%{ u#FDE}", syntaxtag::u, "#FDE"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleColorTest, ::testing::ValuesIn(parse_single_color_list));
+
+TEST_P(ParseSingleColorTest, correctness) {
+ string input;
+ syntaxtag t;
+ string color;
+ std::tie(input, t, color) = GetParam();
+
+ p.setup_parser_test(std::move(input));
+
+ if (color.empty()) {
+ p.expect_color_reset(t);
+ } else {
+ p.expect_color(t, color);
+ }
+
+ p.expect_done();
+}
+
+// }}}
+
+// Parse Single Action {{{
+
+/**
+ * If the third element is an empty string, this is a closing tag.
+ */
+using single_action = std::tuple<string, mousebtn, string>;
+
+class ParseSingleActionTest : public TagParserTest, public ::testing::WithParamInterface<single_action> {};
+
+vector<single_action> parse_single_action_list = {
+ {"%{A:cmd:}", mousebtn::LEFT, "cmd"},
+ {"%{A1:cmd:}", mousebtn::LEFT, "cmd"},
+ {"%{A2:cmd:}", mousebtn::MIDDLE, "cmd"},
+ {"%{A3:cmd:}", mousebtn::RIGHT, "cmd"},
+ {"%{A4:cmd:}", mousebtn::SCROLL_UP, "cmd"},
+ {"%{A5:cmd:}", mousebtn::SCROLL_DOWN, "cmd"},
+ {"%{A6:cmd:}", mousebtn::DOUBLE_LEFT, "cmd"},
+ {"%{A7:cmd:}", mousebtn::DOUBLE_MIDDLE, "cmd"},
+ {"%{A8:cmd:}", mousebtn::DOUBLE_RIGHT, "cmd"},
+ {"%{A}", mousebtn::NONE, ""},
+ {"%{A1}", mousebtn::LEFT, ""},
+ {"%{A2}", mousebtn::MIDDLE, ""},
+ {"%{A3}", mousebtn::RIGHT, ""},
+ {"%{A4}", mousebtn::SCROLL_UP, ""},
+ {"%{A5}", mousebtn::SCROLL_DOWN, ""},
+ {"%{A6}", mousebtn::DOUBLE_LEFT, ""},
+ {"%{A7}", mousebtn::DOUBLE_MIDDLE, ""},
+ {"%{A8}", mousebtn::DOUBLE_RIGHT, ""},
+ {"%{A1:a\\:b:}", mousebtn::LEFT, "a:b"},
+ {"%{A1:\\:\\:\\::}", mousebtn::LEFT, ":::"},
+ {"%{A1:#apps.open.0:}", mousebtn::LEFT, "#apps.open.0"},
+ // https://github.com/polybar/polybar/issues/2040
+ {"%{A1:cmd | awk '{ print $NF }'):}", mousebtn::LEFT, "cmd | awk '{ print $NF }')"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleActionTest, ::testing::ValuesIn(parse_single_action_list));
+
+TEST_P(ParseSingleActionTest, correctness) {
+ string input;
+ mousebtn btn;
+ string cmd;
+ std::tie(input, btn, cmd) = GetParam();
+
+ p.setup_parser_test(std::move(input));
+
+ if (cmd.empty()) {
+ p.expect_action_closing(btn);
+ } else {
+ p.expect_action(cmd, btn);
+ }
+
+ p.expect_done();
+}
+
+// }}}
+
+// Parse Single Activation {{{
+using single_activation = std::tuple<string, attribute, attr_activation>;
+
+class ParseSingleActivationTest : public TagParserTest, public ::testing::WithParamInterface<single_activation> {};
+
+vector<single_activation> parse_single_activation_list = {
+ {"%{+u}", attribute::UNDERLINE, attr_activation::ON},
+ {"%{-u}", attribute::UNDERLINE, attr_activation::OFF},
+ {"%{!u}", attribute::UNDERLINE, attr_activation::TOGGLE},
+ {"%{+o}", attribute::OVERLINE, attr_activation::ON},
+ {"%{-o}", attribute::OVERLINE, attr_activation::OFF},
+ {"%{!o}", attribute::OVERLINE, attr_activation::TOGGLE},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseSingleActivationTest, ::testing::ValuesIn(parse_single_activation_list));
+
+TEST_P(ParseSingleActivationTest, correctness) {
+ string input;
+ attribute attr;
+ attr_activation act;
+ std::tie(input, attr, act) = GetParam();
+ p.setup_parser_test(std::move(input));
+ p.expect_activation(act, attr);
+ p.expect_done();
+}
+
+// }}}
+
+TEST_F(TagParserTest, reverse) {
+ p.setup_parser_test("%{R}");
+ p.expect_reverse();
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, font) {
+ p.setup_parser_test("%{T}");
+ p.expect_font_reset();
+ p.expect_done();
+
+ p.setup_parser_test("%{T-}");
+ p.expect_font_reset();
+ p.expect_done();
+
+ p.setup_parser_test("%{T-123}");
+ p.expect_font_reset();
+ p.expect_done();
+
+ p.setup_parser_test("%{T123}");
+ p.expect_font(123);
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, offset) {
+ p.setup_parser_test("%{O}");
+ p.expect_offset_pixel(0);
+ p.expect_done();
+
+ p.setup_parser_test("%{O0}");
+ p.expect_offset_pixel(0);
+ p.expect_done();
+
+ p.setup_parser_test("%{O-112}");
+ p.expect_offset_pixel(-112);
+ p.expect_done();
+
+ p.setup_parser_test("%{O123}");
+ p.expect_offset_pixel(123);
+ p.expect_done();
+
+ p.setup_parser_test("%{O0pt}");
+ p.expect_offset_points(0);
+ p.expect_done();
+
+ p.setup_parser_test("%{O-112pt}");
+ p.expect_offset_points(-112);
+ p.expect_done();
+
+ p.setup_parser_test("%{O123pt}");
+ p.expect_offset_points(123);
+ p.expect_done();
+
+ p.setup_parser_test("%{O1.5pt}");
+ p.expect_offset_points(1.5);
+ p.expect_done();
+
+ p.setup_parser_test("%{O1.1px}");
+ p.expect_offset_pixel(1);
+ p.expect_done();
+
+ p.setup_parser_test("%{O1.1}");
+ p.expect_offset_pixel(1);
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, alignment) {
+ p.setup_parser_test("%{l}");
+ p.expect_alignment(syntaxtag::l);
+ p.expect_done();
+
+ p.setup_parser_test("%{c}");
+ p.expect_alignment(syntaxtag::c);
+ p.expect_done();
+
+ p.setup_parser_test("%{r}");
+ p.expect_alignment(syntaxtag::r);
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, ctrl) {
+ p.setup_parser_test("%{PR}");
+ p.expect_ctrl(controltag::R);
+ p.expect_done();
+}
+
+/**
+ * Tests the the legacy %{U...} tag first produces %{u...} and then %{o...}
+ */
+TEST_F(TagParserTest, UnderOverLine) {
+ p.setup_parser_test("%{U-}");
+ p.expect_color_reset(syntaxtag::u);
+ p.expect_color_reset(syntaxtag::o);
+ p.expect_done();
+
+ p.setup_parser_test("%{U#12ab}");
+ p.expect_color(syntaxtag::u, "#12ab");
+ p.expect_color(syntaxtag::o, "#12ab");
+ p.expect_done();
+}
+
+// }}}
+
+TEST_F(TagParserTest, compoundTags) {
+ p.setup_parser_test("%{F- B#ff0000 A:cmd:}");
+ p.expect_color_reset(syntaxtag::F);
+ p.expect_color(syntaxtag::B, "#ff0000");
+ p.expect_action("cmd", mousebtn::LEFT);
+ p.expect_done();
+}
+
+TEST_F(TagParserTest, combinations) {
+ p.setup_parser_test("%{r}%{u#4bffdc +u u#4bffdc} 20% abc%{-u u- PR}");
+ p.expect_alignment(syntaxtag::r);
+ p.expect_color(syntaxtag::u, "#4bffdc");
+ p.expect_activation(attr_activation::ON, attribute::UNDERLINE);
+ p.expect_color(syntaxtag::u, "#4bffdc");
+ p.expect_text(" 20% abc");
+ p.expect_activation(attr_activation::OFF, attribute::UNDERLINE);
+ p.expect_color_reset(syntaxtag::u);
+ p.expect_ctrl(controltag::R);
+ p.expect_done();
+}
+
+/**
+ * The type of exception we expect.
+ *
+ * Since we can't directly pass typenames, we go through this enum.
+ */
+enum class exc { ERR, TOKEN, TAG, TAG_END, COLOR, ATTR, FONT, CTRL, OFFSET, BTN };
+
+using exception_test = pair<string, enum exc>;
+class ParseErrorTest : public TagParserTest, public ::testing::WithParamInterface<exception_test> {};
+
+vector<exception_test> parse_error_test = {
+ {"%{F-", exc::TAG_END},
+ {"%{Q", exc::TAG},
+ {"%{", exc::TOKEN},
+ {"%{F#xyz}", exc::COLOR},
+ {"%{Ffoo}", exc::COLOR},
+ {"%{F-abc}", exc::COLOR},
+ {"%{+z}", exc::ATTR},
+ {"%{T-abc}", exc::FONT},
+ {"%{T12a}", exc::FONT},
+ {"%{Tabc}", exc::FONT},
+ {"%{?u", exc::TAG},
+ {"%{PRabc}", exc::CTRL},
+ {"%{P}", exc::CTRL},
+ {"%{PA}", exc::CTRL},
+ {"%{Oabc}", exc::OFFSET},
+ {"%{O123foo}", exc::OFFSET},
+ {"%{O0ptx}", exc::OFFSET},
+ {"%{O0a}", exc::OFFSET},
+ {"%{A2:cmd:cmd:}", exc::TAG_END},
+ {"%{A9}", exc::BTN},
+ {"%{rQ}", exc::TAG_END},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseErrorTest, ::testing::ValuesIn(parse_error_test));
+
+TEST_P(ParseErrorTest, correctness) {
+ string input;
+ exc exception;
+ std::tie(input, exception) = GetParam();
+
+ p.setup_parser_test(input);
+ ASSERT_TRUE(p.has_next_element());
+
+ switch (exception) {
+ case exc::ERR:
+ ASSERT_THROW(p.next_element(), tags::error);
+ break;
+ case exc::TOKEN:
+ ASSERT_THROW(p.next_element(), tags::token_error);
+ break;
+ case exc::TAG:
+ ASSERT_THROW(p.next_element(), tags::unrecognized_tag);
+ break;
+ case exc::TAG_END:
+ ASSERT_THROW(p.next_element(), tags::tag_end_error);
+ break;
+ case exc::COLOR:
+ ASSERT_THROW(p.next_element(), tags::color_error);
+ break;
+ case exc::ATTR:
+ ASSERT_THROW(p.next_element(), tags::unrecognized_attr);
+ break;
+ case exc::FONT:
+ ASSERT_THROW(p.next_element(), tags::font_error);
+ break;
+ case exc::CTRL:
+ ASSERT_THROW(p.next_element(), tags::control_error);
+ break;
+ case exc::OFFSET:
+ ASSERT_THROW(p.next_element(), tags::offset_error);
+ break;
+ case exc::BTN:
+ ASSERT_THROW(p.next_element(), tags::btn_error);
+ break;
+ default:
+ FAIL();
+ }
+}
--- /dev/null
+#include "utils/action_router.hpp"
+
+#include "common/test.hpp"
+#include "gmock/gmock.h"
+
+using namespace polybar;
+using ::testing::InSequence;
+
+class MockModule {
+ public:
+ MOCK_METHOD(void, action1, ());
+ MOCK_METHOD(void, action2, (const string&));
+};
+
+TEST(ActionRouterTest, CallsCorrectFunctions) {
+ MockModule m;
+
+ {
+ InSequence seq;
+ EXPECT_CALL(m, action1()).Times(1);
+ EXPECT_CALL(m, action2("foo")).Times(1);
+ }
+
+ action_router router;
+ router.register_action("action1", [&]() { m.action1(); });
+ router.register_action_with_data("action2", [&](const std::string& data) { m.action2(data); });
+ router.invoke("action1", "");
+ router.invoke("action2", "foo");
+}
+
+TEST(ActionRouterTest, HasAction) {
+ MockModule m;
+ action_router router;
+
+ router.register_action("foo", [&]() { m.action1(); });
+
+ EXPECT_TRUE(router.has_action("foo"));
+ EXPECT_FALSE(router.has_action("bar"));
+}
+
+TEST(ActionRouterTest, ThrowsOnDuplicate) {
+ MockModule m;
+ action_router router;
+
+ router.register_action("foo", [&]() { m.action1(); });
+ EXPECT_THROW(router.register_action("foo", [&]() { m.action1(); }), std::invalid_argument);
+ EXPECT_THROW(router.register_action_with_data("foo", [&](const std::string& data) { m.action2(data); }),
+ std::invalid_argument);
+}
--- /dev/null
+#include "utils/actions.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+using namespace actions_util;
+
+template <typename T1, typename T2, typename T3>
+using triple = std::tuple<T1, T2, T3>;
+
+class GetActionStringTest : public ::testing::TestWithParam<pair<triple<string, string, string>, string>> {};
+
+vector<pair<triple<string, string, string>, string>> get_action_string_list = {
+ {{"foo", "bar", ""}, "#foo.bar"},
+ {{"foo", "bar", "data"}, "#foo.bar.data"},
+ {{"foo", "bar", "data.data2"}, "#foo.bar.data.data2"},
+ {{"a", "b", "c"}, "#a.b.c"},
+ {{"a", "b", ""}, "#a.b"},
+};
+
+TEST_P(GetActionStringTest, correctness) {
+ auto action = GetParam().first;
+ auto exp = GetParam().second;
+
+ auto res = get_action_string(std::get<0>(action), std::get<1>(action), std::get<2>(action));
+
+ EXPECT_EQ(res, exp);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, GetActionStringTest, ::testing::ValuesIn(get_action_string_list));
+
+class ParseActionStringTest : public ::testing::TestWithParam<pair<string, triple<string, string, string>>> {};
+
+vector<pair<string, triple<string, string, string>>> parse_action_string_list = {
+ {"#foo.bar", {"foo", "bar", ""}},
+ {"#foo.bar.", {"foo", "bar", ""}},
+ {"#foo.bar.data", {"foo", "bar", "data"}},
+ {"#foo.bar.data.data2", {"foo", "bar", "data.data2"}},
+ {"#a.b.c", {"a", "b", "c"}},
+ {"#a.b.", {"a", "b", ""}},
+ {"#a.b", {"a", "b", ""}},
+};
+
+TEST_P(ParseActionStringTest, correctness) {
+ auto action_string = GetParam().first;
+ auto exp = GetParam().second;
+
+ auto res = parse_action_string(action_string);
+
+ EXPECT_EQ(res, exp);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringTest, ::testing::ValuesIn(parse_action_string_list));
+
+class ParseActionStringThrowTest : public ::testing::TestWithParam<string> {};
+
+vector<string> parse_action_string_throw_list = {
+ "#",
+ "#.",
+ "#..",
+ "#handler..",
+ "#.action.",
+ "#.action.data",
+ "#..data",
+ "#.data",
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringThrowTest, ::testing::ValuesIn(parse_action_string_throw_list));
+
+TEST_P(ParseActionStringThrowTest, correctness) {
+ EXPECT_THROW(parse_action_string(GetParam()), std::runtime_error);
+}
--- /dev/null
+#include "utils/color.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(Rgba, constructor) {
+ EXPECT_FALSE(rgba("invalid").has_color());
+
+ EXPECT_FALSE(rgba("#f").has_color());
+
+ EXPECT_FALSE(rgba("#-abc").has_color());
+ EXPECT_FALSE(rgba("#xyz").has_color());
+
+ EXPECT_EQ(rgba::type::ALPHA_ONLY, rgba{"#12"}.get_type());
+
+ EXPECT_EQ(0xff000000, rgba{"#ff"}.value());
+
+ EXPECT_EQ(0xffffffff, rgba{"#fff"}.value());
+
+ EXPECT_EQ(0xFF889900, rgba{"#890"}.value());
+
+ EXPECT_EQ(0xaa889900, rgba{"#a890"}.value());
+
+ EXPECT_EQ(0x55888777, rgba{"#55888777"}.value());
+
+ EXPECT_EQ(0x88aaaaaa, rgba{"#88aaaaaa"}.value());
+
+ EXPECT_EQ(0x00aaaaaa, rgba{"#00aaaaaa"}.value());
+
+ EXPECT_EQ(0x00FFFFFF, rgba{"#00FFFFFF"}.value());
+}
+
+TEST(Rgba, parse) {
+ EXPECT_EQ(0xffffffff, rgba{"#fff"}.value());
+ EXPECT_EQ(0xffffffff, rgba{"fff"}.value());
+ EXPECT_EQ(0xff112233, rgba{"#123"}.value());
+ EXPECT_EQ(0xff112233, rgba{"123"}.value());
+ EXPECT_EQ(0xff888888, rgba{"#888888"}.value());
+ EXPECT_EQ(0xff888888, rgba{"888888"}.value());
+ EXPECT_EQ(0x00aa00aa, rgba{"#00aa00aa"}.value());
+ EXPECT_EQ(0x00aa00aa, rgba{"00aa00aa"}.value());
+ EXPECT_EQ(0x11223344, rgba{"#1234"}.value());
+ EXPECT_EQ(0x11223344, rgba{"1234"}.value());
+ EXPECT_EQ(0xaa000000, rgba{"#aa"}.value());
+ EXPECT_EQ(0xaa000000, rgba{"aa"}.value());
+}
+
+TEST(Rgba, string) {
+ EXPECT_EQ("#11223344", static_cast<string>(rgba{"#1234"}));
+ EXPECT_EQ("#12000000", static_cast<string>(rgba{"#12"}));
+}
+
+TEST(Rgba, eq) {
+ rgba v(0x12, rgba::type::NONE);
+
+ EXPECT_TRUE(v == rgba(0, rgba::type::NONE));
+ EXPECT_TRUE(v == rgba(0x11, rgba::type::NONE));
+ EXPECT_FALSE(v == rgba{0x123456});
+
+ v = rgba{0xCC123456};
+
+ EXPECT_TRUE(v == rgba{0xCC123456});
+ EXPECT_FALSE(v == rgba(0xCC123456, rgba::type::NONE));
+
+ v = rgba{"#aa"};
+
+ EXPECT_TRUE(v == rgba(0xaa000000, rgba::type::ALPHA_ONLY));
+ EXPECT_FALSE(v == rgba(0xaa000000, rgba::type::ARGB));
+ EXPECT_FALSE(v == rgba(0xab000000, rgba::type::ALPHA_ONLY));
+}
+
+TEST(Rgba, hasColor) {
+ rgba v{"#"};
+
+ EXPECT_FALSE(v.has_color());
+
+ v = rgba{"#ff"};
+
+ EXPECT_TRUE(v.has_color());
+
+ v = rgba{"#cc123456"};
+
+ EXPECT_TRUE(v.has_color());
+
+ v = rgba(0x1243, rgba::type::NONE);
+
+ EXPECT_FALSE(v.has_color());
+}
+
+TEST(Rgba, isTransparent) {
+ rgba v{"#123456"};
+ EXPECT_FALSE(v.is_transparent());
+
+ v = rgba{"#ff123456"};
+
+ EXPECT_FALSE(v.is_transparent());
+
+ v = rgba{"#fe123456"};
+
+ EXPECT_TRUE(v.is_transparent());
+}
+
+TEST(Rgba, channel) {
+ rgba v{0xCC123456};
+ EXPECT_EQ(0xCC, v.alpha_i());
+ EXPECT_EQ(0x12, v.red_i());
+ EXPECT_EQ(0x34, v.green_i());
+ EXPECT_EQ(0x56, v.blue_i());
+
+ EXPECT_EQ(0xCC / 255.0, rgba{0xCC112233}.alpha_d());
+ EXPECT_EQ(0x99 / 255.0, rgba{0x88449933}.green_d());
+}
+
+TEST(Rgba, applyAlphaTo) {
+ rgba v{0xAA000000, rgba::type::ALPHA_ONLY};
+ rgba modified = v.apply_alpha_to(rgba{0xCC123456});
+ EXPECT_EQ(0xAA123456, modified.value());
+
+ v = rgba{0xCC999999};
+ modified = v.apply_alpha_to(rgba{0x00123456});
+ EXPECT_EQ(0xCC123456, modified.value());
+}
+
+TEST(Rgba, tryApplyAlphaTo) {
+ rgba v{0xAA000000, rgba::type::ALPHA_ONLY};
+ rgba modified = v.try_apply_alpha_to(rgba{0xCC123456});
+ EXPECT_EQ(0xAA123456, modified.value());
+
+ v = rgba{0xCC999999};
+ modified = v.try_apply_alpha_to(rgba{0x00123456});
+ EXPECT_EQ(0xCC999999, modified.value());
+}
+
+TEST(ColorUtil, simplify) {
+ EXPECT_EQ("#111", color_util::simplify_hex("#FF111111"));
+ EXPECT_EQ("#234", color_util::simplify_hex("#ff223344"));
+ EXPECT_EQ("#ee223344", color_util::simplify_hex("#ee223344"));
+ EXPECT_EQ("#234567", color_util::simplify_hex("#ff234567"));
+ EXPECT_EQ("#00223344", color_util::simplify_hex("#00223344"));
+}
--- /dev/null
+#include "utils/command.hpp"
+
+#include <unistd.h>
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+static logger null_logger(loglevel::NONE);
+
+TEST(Command, status) {
+ // Test for command<output_policy::IGNORED>::exec(bool);
+ {
+ command<output_policy::IGNORED> cmd(null_logger, "echo polybar > /dev/null");
+ int status = cmd.exec();
+
+ EXPECT_EQ(status, EXIT_SUCCESS);
+ }
+
+ // Test for command<output_policy::REDIRECTED>::exec(bool);
+ {
+ command<output_policy::REDIRECTED> cmd(null_logger, "echo polybar");
+ int status = cmd.exec();
+
+ EXPECT_EQ(status, EXIT_SUCCESS);
+ }
+}
+
+TEST(Command, status_async) {
+ {
+ command<output_policy::IGNORED> cmd(null_logger, "echo polybar > /dev/null");
+ EXPECT_EQ(cmd.exec(false), EXIT_SUCCESS);
+
+ cmd.wait();
+
+ EXPECT_FALSE(cmd.is_running());
+ EXPECT_EQ(cmd.get_exit_status(), EXIT_SUCCESS);
+ }
+}
+
+TEST(Command, output) {
+ command<output_policy::REDIRECTED> cmd(null_logger, "echo polybar");
+ string str;
+ cmd.exec(false);
+ cmd.tail([&str](string&& string) { str = string; });
+ cmd.wait();
+
+ EXPECT_EQ(str, "polybar");
+}
--- /dev/null
+#include "utils/env.hpp"
+
+#include "common/test.hpp"
+#include "stdlib.h"
+
+using namespace polybar;
+
+static constexpr auto INEXISTENT_ENV = "POLYBAR_INEXISTENT";
+
+TEST(Env, has) {
+ EXPECT_EQ(true, env_util::has("HOME"));
+ unsetenv(INEXISTENT_ENV);
+ EXPECT_EQ(false, env_util::has(INEXISTENT_ENV));
+ setenv(INEXISTENT_ENV, "123", false);
+ EXPECT_EQ(true, env_util::has(INEXISTENT_ENV));
+ unsetenv(INEXISTENT_ENV);
+ EXPECT_EQ(false, env_util::has(INEXISTENT_ENV));
+}
+
+TEST(Env, get) {
+ unsetenv(INEXISTENT_ENV);
+ EXPECT_EQ("fallback", env_util::get(INEXISTENT_ENV, "fallback"));
+ setenv(INEXISTENT_ENV, "123", false);
+ EXPECT_EQ("123", env_util::get(INEXISTENT_ENV, "fallback"));
+ unsetenv(INEXISTENT_ENV);
+ EXPECT_EQ("fallback", env_util::get(INEXISTENT_ENV, "fallback"));
+}
--- /dev/null
+#include "utils/file.hpp"
+
+#include <iomanip>
+#include <iostream>
+
+#include "common/test.hpp"
+#include "utils/command.hpp"
+#include "utils/env.hpp"
+
+using namespace polybar;
+
+using expand_test_t = pair<string, string>;
+class ExpandTest : public testing::TestWithParam<expand_test_t> {};
+
+vector<expand_test_t> expand_absolute_test_list = {
+ {"~/foo", env_util::get("HOME") + "/foo"},
+ {"$HOME/foo", env_util::get("HOME") + "/foo"},
+ {"/scratch/polybar", "/scratch/polybar"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ExpandTest, ::testing::ValuesIn(expand_absolute_test_list));
+
+TEST_P(ExpandTest, absolute) {
+ EXPECT_EQ(file_util::expand(GetParam().first), GetParam().second);
+}
+
+TEST_P(ExpandTest, relativeToAbsolute) {
+ EXPECT_EQ(file_util::expand(GetParam().first, "/scratch"), GetParam().second);
+}
+
+using expand_relative_test_t = std::tuple<string, string, string>;
+class ExpandRelativeTest : public testing::TestWithParam<expand_relative_test_t> {};
+
+vector<expand_relative_test_t> expand_relative_test_list = {
+ {"../test", "/scratch", "/scratch/../test"},
+ {"modules/battery", "/scratch/polybar", "/scratch/polybar/modules/battery"},
+ {"/tmp/foo", "/scratch", "/tmp/foo"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ExpandRelativeTest, ::testing::ValuesIn(expand_relative_test_list));
+
+TEST_P(ExpandRelativeTest, correctness) {
+ string path, relative_to, expected;
+ std::tie(path, relative_to, expected) = GetParam();
+ EXPECT_EQ(file_util::expand(path, relative_to), expected);
+}
--- /dev/null
+#include "utils/math.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(Math, cap) {
+ EXPECT_EQ(8, math_util::cap<int>(8, 0, 10));
+ EXPECT_EQ(0, math_util::cap<int>(-8, 0, 10));
+ EXPECT_EQ(10, math_util::cap<int>(15, 0, 10));
+ EXPECT_EQ(20.5f, math_util::cap<float>(20.5f, 0.0f, 30.0f));
+ EXPECT_EQ(1.0f, math_util::cap<float>(1.0f, 0.0f, 2.0f));
+ EXPECT_EQ(-2.0f, math_util::cap<float>(-2.0f, -5.0f, 5.0f));
+ EXPECT_EQ(0, math_util::cap<float>(1.0f, 0.0f, 0.0f));
+}
+
+TEST(Math, unbounded_percentage) {
+ EXPECT_EQ(101.0f, (math_util::unbounded_percentage<float, float>(101.0f, 0.0f, 100.0f)));
+ EXPECT_EQ(102, (math_util::unbounded_percentage<float, int>(101.5f, 0.0f, 100.0f)));
+ EXPECT_EQ(110.0f, (math_util::unbounded_percentage<float, float>(12.0f, -10.0f, 10.0f)));
+ EXPECT_EQ(150.0f, (math_util::unbounded_percentage<float, float>(11.5f, 10.0f, 11.0f)));
+ EXPECT_EQ(-50.0f, (math_util::unbounded_percentage<float, float>(-50.0f, 0.0f, 100.0f)));
+ EXPECT_EQ(-50.0f, (math_util::unbounded_percentage<float, float>(9.5f, 10.0f, 11.0f)));
+}
+
+TEST(Math, percentage) {
+ EXPECT_EQ(55.0f, (math_util::percentage<float, float>(5.5f, 0.0f, 10.0f)));
+ EXPECT_EQ(56, (math_util::percentage<float, int>(5.55f, 0.0f, 10.0f)));
+ EXPECT_EQ(43.75f, (math_util::percentage<float, float>(5.25f, 0.0f, 12.0f)));
+ EXPECT_EQ(42, (math_util::percentage<int, int>(5, 0, 12)));
+ EXPECT_EQ(20.5f, (math_util::percentage<float, float>(20.5f, 0.0f, 100.0f)));
+ EXPECT_EQ(70.0f, (math_util::percentage<float, float>(4.5f, 1.0f, 6.0f)));
+ EXPECT_EQ(21, (math_util::percentage<float, int>(20.5f, 0.0f, 100.0f)));
+ EXPECT_EQ(50, (math_util::percentage<int, int>(4, 2, 6)));
+ EXPECT_EQ(50, (math_util::percentage<int, int>(0, -10, 10)));
+ EXPECT_EQ(0, (math_util::percentage<int, int>(-10, -10, 10)));
+ EXPECT_EQ(100, (math_util::percentage<int, int>(10, -10, 10)));
+ EXPECT_EQ(10, (math_util::percentage(10, 0, 100)));
+}
+
+TEST(Math, percentageToValue) {
+ EXPECT_EQ(3, math_util::percentage_to_value(50, 5));
+ EXPECT_EQ(2.5f, (math_util::percentage_to_value<int, float>(50, 5)));
+ EXPECT_EQ(0, math_util::percentage_to_value(0, 5));
+ EXPECT_EQ(1, math_util::percentage_to_value(10, 5));
+ EXPECT_EQ(1, math_util::percentage_to_value(20, 5));
+ EXPECT_EQ(2, math_util::percentage_to_value(30, 5));
+ EXPECT_EQ(2, math_util::percentage_to_value(40, 5));
+ EXPECT_EQ(3, math_util::percentage_to_value(50, 5));
+ EXPECT_EQ(5, math_util::percentage_to_value(100, 5));
+ EXPECT_EQ(5, math_util::percentage_to_value(200, 5));
+ EXPECT_EQ(0, math_util::percentage_to_value(-30, 5));
+}
+
+TEST(Math, rangedPercentageToValue) {
+ EXPECT_EQ(250, math_util::percentage_to_value(50, 200, 300));
+ EXPECT_EQ(3, math_util::percentage_to_value(50, 1, 5));
+}
+
+TEST(Math, roundToNearest10) {
+ EXPECT_EQ(50, math_util::nearest_10(52));
+ EXPECT_EQ(10, math_util::nearest_10(9.1));
+ EXPECT_EQ(100, math_util::nearest_10(95.0));
+ EXPECT_EQ(90, math_util::nearest_10(94.9));
+}
+
+TEST(Math, roundToNearest5) {
+ EXPECT_EQ(50, math_util::nearest_5(52));
+ EXPECT_EQ(10, math_util::nearest_5(9.1));
+ EXPECT_EQ(95, math_util::nearest_5(95.0));
+ EXPECT_EQ(95, math_util::nearest_5(94.9));
+ EXPECT_EQ(0, math_util::nearest_5(1));
+ EXPECT_EQ(100, math_util::nearest_5(99.99));
+}
--- /dev/null
+#include "utils/process.hpp"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <thread>
+
+#include "common/test.hpp"
+
+using namespace polybar;
+using namespace process_util;
+
+TEST(SpawnAsync, is_async) {
+ pid_t pid = spawn_async([] { exec_sh("sleep 0.1"); });
+ int status;
+
+ pid_t res = process_util::wait_for_completion_nohang(pid, &status);
+
+ ASSERT_NE(res, -1);
+
+ EXPECT_FALSE(WIFEXITED(status));
+}
+
+TEST(SpawnAsync, exit_code) {
+ pid_t pid = spawn_async([] { exec_sh("exit 42"); });
+ int status = 0;
+ pid_t res = waitpid(pid, &status, 0);
+
+ EXPECT_EQ(res, pid);
+
+ EXPECT_EQ(WEXITSTATUS(status), 42);
+}
+
+TEST(SpawnAsync, env) {
+ pid_t pid = spawn_async([] { exec_sh("exit $EXIT", {{"EXIT", "45"}}); });
+ int status = 0;
+ pid_t res = waitpid(pid, &status, 0);
+
+ EXPECT_EQ(res, pid);
+
+ EXPECT_EQ(WEXITSTATUS(status), 45);
+}
--- /dev/null
+#include "utils/scope.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(Scope, onExit) {
+ auto flag = false;
+ {
+ EXPECT_FALSE(flag);
+ scope_util::on_exit handler([&] { flag = true; });
+ EXPECT_FALSE(flag);
+ {
+ scope_util::on_exit handler([&] { flag = true; });
+ }
+ EXPECT_TRUE(flag);
+ flag = false;
+ }
+ EXPECT_TRUE(flag);
+}
--- /dev/null
+#include "utils/string.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(String, ends_with) {
+ EXPECT_TRUE(string_util::ends_with("foo", "foo"));
+ EXPECT_TRUE(string_util::ends_with("foobar", "bar"));
+ EXPECT_TRUE(string_util::ends_with("foobar", ""));
+ EXPECT_FALSE(string_util::ends_with("foo", "bar"));
+ EXPECT_FALSE(string_util::ends_with("foo", "Foo"));
+ EXPECT_FALSE(string_util::ends_with("", "Foo"));
+}
+
+TEST(String, upper) {
+ EXPECT_EQ("FOO", string_util::upper("FOO"));
+ EXPECT_EQ("FOO", string_util::upper("FoO"));
+ EXPECT_EQ("FOO", string_util::upper("FOo"));
+ EXPECT_EQ("FOO", string_util::upper("Foo"));
+}
+
+TEST(String, lower) {
+ EXPECT_EQ("bar", string_util::lower("BAR"));
+}
+
+TEST(String, compare) {
+ EXPECT_TRUE(string_util::compare("foo", "foo"));
+ EXPECT_TRUE(string_util::compare("foo", "Foo"));
+ EXPECT_FALSE(string_util::compare("foo", "bar"));
+}
+
+TEST(String, contains) {
+ EXPECT_TRUE(string_util::contains("fooooobar", "foo"));
+ EXPECT_TRUE(string_util::contains("barrrrrrfoo", "foo"));
+ EXPECT_TRUE(string_util::contains("barrfoobazzz", "foo"));
+ EXPECT_TRUE(string_util::contains("foo", "foo"));
+ EXPECT_TRUE(string_util::contains("foobar", "foo"));
+ EXPECT_TRUE(string_util::contains("foobar", "bar"));
+ EXPECT_FALSE(string_util::contains("foo", "Foo"));
+ EXPECT_FALSE(string_util::contains("foo", "bar"));
+ EXPECT_FALSE(string_util::contains("foobar", "baz"));
+ EXPECT_FALSE(string_util::contains("foobAr", "bar"));
+}
+
+TEST(String, contains_ignore_case) {
+ EXPECT_TRUE(string_util::contains_ignore_case("fooooobar", "foo"));
+ EXPECT_TRUE(string_util::contains_ignore_case("barrrrrrfoo", "foo"));
+ EXPECT_TRUE(string_util::contains_ignore_case("barrfoobazzz", "foo"));
+ EXPECT_TRUE(string_util::contains_ignore_case("fooooobar", "fOO"));
+ EXPECT_TRUE(string_util::contains_ignore_case("barrrrrrfoo", "FOo"));
+ EXPECT_TRUE(string_util::contains_ignore_case("barrfoobazzz", "FoO"));
+ EXPECT_TRUE(string_util::contains_ignore_case("foo", "Foo"));
+ EXPECT_FALSE(string_util::contains_ignore_case("foo", "bar"));
+ EXPECT_TRUE(string_util::contains_ignore_case("foo", ""));
+ EXPECT_FALSE(string_util::contains_ignore_case("", "bar"));
+}
+
+TEST(String, replace) {
+ EXPECT_EQ("a.c", string_util::replace("abc", "b", "."));
+ EXPECT_EQ("a.a", string_util::replace("aaa", "a", ".", 1, 2));
+ EXPECT_EQ(".aa", string_util::replace("aaa", "a", ".", 0, 2));
+ EXPECT_EQ("Foo bxr baz", string_util::replace("Foo bar baz", "a", "x"));
+ EXPECT_EQ("foxoobar", string_util::replace("foooobar", "o", "x", 2, 3));
+ EXPECT_EQ("foooobar", string_util::replace("foooobar", "o", "x", 0, 1));
+}
+
+TEST(String, replaceAll) {
+ EXPECT_EQ("Foo bxr bxzx", string_util::replace_all("Foo bar baza", "a", "x"));
+ EXPECT_EQ("hoohoohoo", string_util::replace_all("hehehe", "he", "hoo"));
+ EXPECT_EQ("hoohehe", string_util::replace_all("hehehe", "he", "hoo", 0, 2));
+ EXPECT_EQ("hehehoo", string_util::replace_all("hehehe", "he", "hoo", 4));
+ EXPECT_EQ("hehehe", string_util::replace_all("hehehe", "he", "hoo", 0, 1));
+ EXPECT_EQ("113113113", string_util::replace_all("131313", "3", "13"));
+}
+
+TEST(String, squeeze) {
+ EXPECT_EQ("Squeze", string_util::squeeze("Squeeeeeze", 'e'));
+ EXPECT_EQ("bar baz foobar", string_util::squeeze("bar baz foobar", ' '));
+}
+
+TEST(String, strip) {
+ EXPECT_EQ("Strp", string_util::strip("Striip", 'i'));
+ EXPECT_EQ("test\n", string_util::strip_trailing_newline("test\n\n"));
+}
+
+TEST(String, trim) {
+ EXPECT_EQ("x x", string_util::trim(" x x "));
+ EXPECT_EQ("testxx", string_util::ltrim("xxtestxx", 'x'));
+ EXPECT_EQ("xxtest", string_util::rtrim("xxtestxx", 'x'));
+ EXPECT_EQ("test", string_util::trim("xxtestxx", 'x'));
+}
+
+TEST(String, trimPredicate) {
+ EXPECT_EQ("x\t x", string_util::trim("\t x\t x ", isspace));
+ EXPECT_EQ("x\t x", string_util::trim("x\t x ", isspace));
+}
+
+TEST(String, join) {
+ EXPECT_EQ("A, B, C", string_util::join({"A", "B", "C"}, ", "));
+}
+
+TEST(String, split) {
+ {
+ vector<string> strings = string_util::split("A,B,C", ',');
+ EXPECT_EQ(3, strings.size());
+ EXPECT_EQ("A", strings[0]);
+ EXPECT_EQ("B", strings[1]);
+ EXPECT_EQ("C", strings[2]);
+ }
+
+ {
+ vector<string> strings = string_util::split(",A,,B,,C,", ',');
+ EXPECT_EQ(3, strings.size());
+ EXPECT_EQ("A", strings[0]);
+ EXPECT_EQ("B", strings[1]);
+ EXPECT_EQ("C", strings[2]);
+ }
+}
+
+TEST(String, tokenize) {
+ {
+ vector<string> strings = string_util::tokenize("A,B,C", ',');
+ EXPECT_EQ(3, strings.size());
+ EXPECT_EQ("A", strings[0]);
+ EXPECT_EQ("B", strings[1]);
+ EXPECT_EQ("C", strings[2]);
+ }
+
+ {
+ using namespace std::string_literals;
+ vector<string> strings = string_util::tokenize(",A,,B,,C,", ',');
+ vector<string> result{""s, "A"s, ""s, "B"s, ""s, "C"s, ""s};
+
+ EXPECT_TRUE(strings == result);
+ }
+}
+
+TEST(String, findNth) {
+ EXPECT_EQ(0, string_util::find_nth("foobarfoobar", 0, "f", 1));
+ EXPECT_EQ(6, string_util::find_nth("foobarfoobar", 0, "f", 2));
+ EXPECT_EQ(7, string_util::find_nth("foobarfoobar", 0, "o", 3));
+}
+
+TEST(String, hash) {
+ unsigned long hashA1{string_util::hash("foo")};
+ unsigned long hashA2{string_util::hash("foo")};
+ unsigned long hashB1{string_util::hash("Foo")};
+ unsigned long hashB2{string_util::hash("Bar")};
+ EXPECT_EQ(hashA2, hashA1);
+ EXPECT_NE(hashB1, hashA1);
+ EXPECT_NE(hashB2, hashA1);
+ EXPECT_NE(hashB2, hashB1);
+}
+
+TEST(String, floatingPoint) {
+ EXPECT_EQ("1.26", string_util::floating_point(1.2599, 2));
+ EXPECT_EQ("2", string_util::floating_point(1.7, 0));
+ EXPECT_EQ("1.7770000000", string_util::floating_point(1.777, 10));
+}
+
+TEST(String, filesize) {
+ EXPECT_EQ("3.000 MiB", string_util::filesize_mib(3 * 1024, 3));
+ EXPECT_EQ("3.195 MiB", string_util::filesize_mib(3 * 1024 + 200, 3));
+ EXPECT_EQ("3 MiB", string_util::filesize_mib(3 * 1024 + 400));
+ EXPECT_EQ("4 MiB", string_util::filesize_mib(3 * 1024 + 800));
+ EXPECT_EQ("3.195 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 200 * 1024, 3));
+ EXPECT_EQ("3 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 400 * 1024));
+ EXPECT_EQ("4 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 800 * 1024));
+ EXPECT_EQ("3 B", string_util::filesize(3));
+ EXPECT_EQ("3 KB", string_util::filesize(3 * 1024));
+ EXPECT_EQ("3 MB", string_util::filesize(3 * 1024 * 1024));
+ EXPECT_EQ("3 GB", string_util::filesize((unsigned long long)3 * 1024 * 1024 * 1024));
+ EXPECT_EQ("3 TB", string_util::filesize((unsigned long long)3 * 1024 * 1024 * 1024 * 1024));
+}
+
+// utf8_to_ucs4 {{{
+class Utf8ToUCS4AsciiTest : public testing::TestWithParam<string> {};
+
+const vector<string> utf8_to_ucs4_ascii_list = {"", "Hello World", "\n", "\0", "\u007f"};
+
+INSTANTIATE_TEST_SUITE_P(Inst, Utf8ToUCS4AsciiTest, testing::ValuesIn(utf8_to_ucs4_ascii_list));
+
+/**
+ * Test that the conversion to ucs4 works correctly with pure ASCII strings.
+ */
+TEST_P(Utf8ToUCS4AsciiTest, correctness) {
+ string_util::unicode_charlist result_list{};
+ string str = GetParam();
+
+ bool valid = string_util::utf8_to_ucs4(str, result_list);
+ ASSERT_TRUE(valid);
+
+ ASSERT_EQ(str.size(), result_list.size());
+
+ for (size_t i = 0; i < str.size(); i++) {
+ const auto& unicode_char = result_list[i];
+ auto c = str[i];
+
+ // Matches the single byte character
+ EXPECT_EQ(c, unicode_char.codepoint);
+ // Is at the same offset as in the original string
+ EXPECT_EQ(i, unicode_char.offset);
+ // Only takes a single byte
+ EXPECT_EQ(1, unicode_char.length);
+ }
+}
+
+// String containing a single codepoint and the expected numerical codepoint
+using single_test_t = std::pair<string, uint32_t>;
+class Utf8ToUCS4SingleTest : public testing::TestWithParam<single_test_t> {};
+
+const vector<single_test_t> utf8_to_ucs4_single_list = {
+ {" ", 0x20}, // Single ASCII character
+ {"\u007f", 0x7f}, // End of 1 byte range
+ {"\u0080", 0x80}, // Start of 2 byte range
+ {"\u07ff", 0x7ff}, // End of 2 byte range
+ {"\u0800", 0x800}, // Start of 3 byte range
+ {"\uffff", 0xffff}, // End of 3 byte range
+ {"\U00010000", 0x10000}, // Start of 4 byte range
+ {"\U0010ffff", 0x10ffff}, // End of 4 byte range
+ {"\U0001f600", 0x1f600}, // Grinning face emoji
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, Utf8ToUCS4SingleTest, testing::ValuesIn(utf8_to_ucs4_single_list));
+
+/**
+ * Test that the conversion to ucs4 works correctly with a single UTF8 character
+ */
+TEST_P(Utf8ToUCS4SingleTest, correctness) {
+ string_util::unicode_charlist result_list{};
+ const auto [str, codepoint] = GetParam();
+
+ bool valid = string_util::utf8_to_ucs4(str, result_list);
+ ASSERT_TRUE(valid);
+
+ ASSERT_EQ(1, result_list.size());
+
+ auto unicode_char = result_list.front();
+
+ EXPECT_EQ(0, unicode_char.offset);
+ // Must encompass entire string
+ EXPECT_EQ(str.size(), unicode_char.length);
+ // Must match expected codepoint
+ EXPECT_EQ(codepoint, unicode_char.codepoint);
+}
+
+class Utf8ToUCS4InvalidTest : public testing::TestWithParam<string> {};
+
+const vector<string> utf8_to_ucs4_invalid_list = {
+ "\x80", // continuation byte without leading byte
+ "\xa0", // 2 byte code point with only leading byte
+ "\xe0", // 3 byte code point with only leading byte
+ "\xf0", // 4 byte code point with only leading byte
+ "\xf0\x80\x80", // 4 byte code point with only 3 bytes
+ "\xe0\xf0\x80", // 3 byte code point, 2nd byte has no continuation prefix
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, Utf8ToUCS4InvalidTest, testing::ValuesIn(utf8_to_ucs4_invalid_list));
+
+/**
+ * Tests that the conversion correctly returns false for invalid strings.
+ */
+TEST_P(Utf8ToUCS4InvalidTest, correctness) {
+ string_util::unicode_charlist result_list{};
+ const auto str = GetParam();
+ bool valid = string_util::utf8_to_ucs4(str, result_list);
+ EXPECT_FALSE(valid);
+ EXPECT_EQ(0, result_list.size());
+}
+
+/**
+ * Tests that the conversion works with partially valid strings and that invalid parts are dropped.
+ */
+TEST(String, utf8ToUCS4Partial) {
+ string_util::unicode_charlist result_list{};
+ string str = "\xe0\x70\x80"; // a valid ascii character between two invalid characters
+ bool valid = string_util::utf8_to_ucs4(str, result_list);
+ EXPECT_FALSE(valid);
+ EXPECT_EQ(1, result_list.size());
+
+ EXPECT_EQ(0x70, result_list[0].codepoint);
+ EXPECT_EQ(1, result_list[0].offset);
+ EXPECT_EQ(1, result_list[0].length);
+}
+// }}}
--- /dev/null
+#include "utils/units.hpp"
+
+#include "common/test.hpp"
+#include "utils/units.hpp"
+
+using namespace polybar;
+using namespace units_utils;
+
+namespace polybar {
+ bool operator==(const extent_val lhs, const extent_val rhs) {
+ return lhs.type == rhs.type && lhs.value == rhs.value;
+ }
+
+ bool operator==(const spacing_val lhs, const spacing_val rhs) {
+ return lhs.type == rhs.type && lhs.value == rhs.value;
+ }
+} // namespace polybar
+
+/**
+ * \brief Class for parameterized tests on geom_format_to_pixels
+ *
+ * The first element in the tuple is the expected return value, the second
+ * value represents the format string. The max value is always 1000 and dpi is always 96
+ */
+class GeomFormatToPixelsTest : public ::testing::Test,
+ public ::testing::WithParamInterface<pair<int, percentage_with_offset>> {};
+
+vector<pair<int, percentage_with_offset>> to_pixels_no_offset_list = {
+ {1000, percentage_with_offset{100.}},
+ {0, percentage_with_offset{0.}},
+ {1000, percentage_with_offset{150.}},
+ {100, percentage_with_offset{10.}},
+ {0, percentage_with_offset{0., ZERO_PX_EXTENT}},
+ {1234, percentage_with_offset{0., extent_val{extent_type::PIXEL, 1234}}},
+ {1, percentage_with_offset{0., extent_val{extent_type::PIXEL, 1}}},
+};
+
+vector<pair<int, percentage_with_offset>> to_pixels_with_pixels_list = {
+ {1000, percentage_with_offset{100., ZERO_PX_EXTENT}},
+ {1010, percentage_with_offset{100., extent_val{extent_type::PIXEL, 10}}},
+ {990, percentage_with_offset{100., extent_val{extent_type::PIXEL, -10}}},
+ {10, percentage_with_offset{0., extent_val{extent_type::PIXEL, 10}}},
+ {1000, percentage_with_offset{99., extent_val{extent_type::PIXEL, 10}}},
+ {-90, percentage_with_offset{1., extent_val{extent_type::PIXEL, -100}}},
+};
+
+vector<pair<int, percentage_with_offset>> to_pixels_with_points_list = {
+ {1013, percentage_with_offset{100., extent_val{extent_type::POINT, 10}}},
+ {987, percentage_with_offset{100., extent_val{extent_type::POINT, -10}}},
+ {1003, percentage_with_offset{99., extent_val{extent_type::POINT, 10}}},
+ {13, percentage_with_offset{0., extent_val{extent_type::POINT, 10}}},
+};
+
+INSTANTIATE_TEST_SUITE_P(NoOffset, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_no_offset_list));
+
+INSTANTIATE_TEST_SUITE_P(WithPixels, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_with_pixels_list));
+
+INSTANTIATE_TEST_SUITE_P(WithPoints, GeomFormatToPixelsTest, ::testing::ValuesIn(to_pixels_with_points_list));
+
+static constexpr int MAX_WIDTH = 1000;
+static constexpr int DPI = 96;
+
+TEST_P(GeomFormatToPixelsTest, correctness) {
+ int exp = GetParam().first;
+ percentage_with_offset geometry = GetParam().second;
+ EXPECT_DOUBLE_EQ(exp, percentage_with_offset_to_pixel(geometry, MAX_WIDTH, DPI));
+}
+
+TEST(UnitsUtils, point_to_pixel) {
+ EXPECT_EQ(72, point_to_pixel(72, 72));
+ EXPECT_EQ(96, point_to_pixel(72, 96));
+ EXPECT_EQ(48, point_to_pixel(36, 96));
+ EXPECT_EQ(-48, point_to_pixel(-36, 96));
+}
+
+TEST(UnitsUtils, extent_to_pixel) {
+ EXPECT_EQ(100, extent_to_pixel_nonnegative({extent_type::PIXEL, 100}, 0));
+ EXPECT_EQ(48, extent_to_pixel_nonnegative({extent_type::POINT, 36}, 96));
+
+ EXPECT_EQ(0, extent_to_pixel_nonnegative({extent_type::PIXEL, -100}, 0));
+ EXPECT_EQ(0, extent_to_pixel_nonnegative({extent_type::POINT, -36}, 96));
+}
+
+TEST(UnitsUtils, parse_extent_unit) {
+ EXPECT_EQ(extent_type::PIXEL, parse_extent_unit("px"));
+ EXPECT_EQ(extent_type::POINT, parse_extent_unit("pt"));
+
+ EXPECT_EQ(extent_type::PIXEL, parse_extent_unit(""));
+
+ EXPECT_THROW(parse_extent_unit("foo"), std::runtime_error);
+}
+
+TEST(UnitsUtils, parse_extent) {
+ EXPECT_EQ((extent_val{extent_type::PIXEL, 100}), parse_extent("100px"));
+ EXPECT_EQ((extent_val{extent_type::POINT, 36}), parse_extent("36pt"));
+
+ EXPECT_EQ((extent_val{extent_type::PIXEL, -100}), parse_extent("-100px"));
+ EXPECT_EQ((extent_val{extent_type::POINT, -36}), parse_extent("-36pt"));
+
+ EXPECT_EQ((extent_val{extent_type::PIXEL, 100}), parse_extent("100"));
+ EXPECT_EQ((extent_val{extent_type::PIXEL, -100}), parse_extent("-100"));
+
+ EXPECT_THROW(parse_extent("100foo"), std::runtime_error);
+}
+
+TEST(UnitsUtils, extent_to_string) {
+ EXPECT_EQ("100px", extent_to_string({extent_type::PIXEL, 100}));
+ EXPECT_EQ("36pt", extent_to_string({extent_type::POINT, 36}));
+
+ EXPECT_EQ("-100px", extent_to_string({extent_type::PIXEL, -100}));
+ EXPECT_EQ("-36pt", extent_to_string({extent_type::POINT, -36}));
+}
+
+TEST(UnitsUtils, parse_spacing_unit) {
+ EXPECT_EQ(spacing_type::PIXEL, parse_spacing_unit("px"));
+ EXPECT_EQ(spacing_type::POINT, parse_spacing_unit("pt"));
+
+ EXPECT_EQ(spacing_type::SPACE, parse_spacing_unit(""));
+
+ EXPECT_THROW(parse_spacing_unit("foo"), std::runtime_error);
+}
+
+TEST(UnitsUtils, parse_spacing) {
+ EXPECT_EQ((spacing_val{spacing_type::PIXEL, 100}), parse_spacing("100px"));
+ EXPECT_EQ((spacing_val{spacing_type::POINT, 36}), parse_spacing("36pt"));
+
+ EXPECT_EQ((spacing_val{spacing_type::SPACE, 100}), parse_spacing("100"));
+
+ EXPECT_THROW(parse_spacing("-100px"), std::runtime_error);
+ EXPECT_THROW(parse_spacing("-36pt"), std::runtime_error);
+ EXPECT_THROW(parse_spacing("-100"), std::runtime_error);
+ EXPECT_THROW(parse_spacing("100foo"), std::runtime_error);
+}
--- /dev/null
+# Polybar version information
+# Update this on every release
+# This is used to create the version string if a git repo is not available
+3.7.0