pax_global_header00006660000000000000000000000064146077042760014527gustar00rootroot0000000000000052 comment=a2d30b96fbb026621ec11692272f0117f35a00fd yambar-1.11.0/000077500000000000000000000000001460770427600130625ustar00rootroot00000000000000yambar-1.11.0/.builds/000077500000000000000000000000001460770427600144225ustar00rootroot00000000000000yambar-1.11.0/.builds/alpine-x64.yml000066400000000000000000000035671460770427600170470ustar00rootroot00000000000000image: alpine/latest packages: - musl-dev - eudev-libs - eudev-dev - linux-headers - meson - ninja - gcc - scdoc - libxcb-dev - xcb-util-wm-dev - xcb-util-cursor-dev - cairo-dev - yaml-dev - wayland-dev - wayland-protocols - wlroots-dev - json-c-dev - libmpdclient-dev - alsa-lib-dev - pulseaudio-dev - pipewire-dev - ttf-dejavu - gcovr - python3 - py3-pip - flex - bison sources: - https://git.sr.ht/~dnkl/yambar # triggers: # - action: email # condition: failure # to: tasks: - codespell: | pip install codespell cd yambar ~/.local/bin/codespell README.md CHANGELOG.md *.c *.h doc/*.scd - fcft: | cd yambar/subprojects git clone https://codeberg.org/dnkl/fcft.git cd ../.. - setup: | mkdir -p bld/debug bld/release bld/x11-only bld/wayland-only bld/plugs-are-shared meson --buildtype=debug -Db_coverage=true yambar bld/debug meson --buildtype=minsize yambar bld/release meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled yambar bld/x11-only meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled yambar bld/wayland-only meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true yambar bld/plugs-are-shared - build: | ninja -C bld/debug -k0 ninja -C bld/release -k0 ninja -C bld/x11-only -k0 ninja -C bld/wayland-only -k0 ninja -C bld/plugs-are-shared -k0 - tests: | meson test -C bld/debug --print-errorlogs meson test -C bld/release --print-errorlogs meson test -C bld/x11-only --print-errorlogs meson test -C bld/wayland-only --print-errorlogs meson test -C bld/plugs-are-shared --print-errorlogs - coverage: | ninja -C bld/debug coverage-html ninja -C bld/debug coverage-text tail -2 bld/debug/meson-logs/coverage.txt yambar-1.11.0/.clang-format000066400000000000000000000007141460770427600154370ustar00rootroot00000000000000--- BasedOnStyle: GNU IndentWidth: 4 --- Language: Cpp Standard: Auto PointerAlignment: Right ColumnLimit: 120 BreakBeforeBraces: Custom BraceWrapping: AfterEnum: false AfterClass: false SplitEmptyFunction: true AfterFunction: true AfterStruct: false SpaceBeforeParens: ControlStatements Cpp11BracedListStyle: true WhitespaceSensitiveMacros: - REGISTER_CORE_PARTICLE - REGISTER_CORE_DECORATION - REGISTER_CORE_PLUGIN - REGISTER_CORE_MODULE yambar-1.11.0/.editorconfig000066400000000000000000000004171460770427600155410ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = space indent_size = 4 max_line_length = 70 [{meson.build,PKGBUILD}] indent_size = 2 [*.scd] indent_style = tab trim_trailing_whitespace = false yambar-1.11.0/.gitignore000066400000000000000000000000661460770427600150540ustar00rootroot00000000000000/bld/ /pkg/ /src/ /subprojects/* !/subprojects/*.wrap yambar-1.11.0/.gitmodules000066400000000000000000000002101460770427600152300ustar00rootroot00000000000000[submodule "external/wlr-protocols"] path = external/wlr-protocols url = https://github.com/swaywm/wlr-protocols.git branch = master yambar-1.11.0/.woodpecker.yaml000066400000000000000000000075171460770427600162000ustar00rootroot00000000000000steps: - name: codespell when: - event: [manual, pull_request] - event: [push, tag] branch: [master, releases/*] image: alpine:latest commands: - apk add openssl - apk add python3 - apk add py3-pip - python3 -m venv codespell-venv - source codespell-venv/bin/activate - pip install codespell - codespell README.md CHANGELOG.md *.c *.h doc/*.scd - deactivate - name: subprojects when: - event: [manual, pull_request] - event: [push, tag] branch: [master, releases/*] image: alpine:latest commands: - apk add git - mkdir -p subprojects && cd subprojects - git clone https://codeberg.org/dnkl/tllist.git - git clone https://codeberg.org/dnkl/fcft.git - cd .. - name: x64 when: - event: [manual, pull_request] - event: [push, tag] branch: [master, releases/*] depends_on: [subprojects] image: alpine:latest commands: - apk update - apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc - apk add pixman-dev freetype-dev fontconfig-dev - apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev - apk add wayland-dev wayland-protocols wlroots-dev - apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev - apk add ttf-dejavu - apk add git - apk add flex bison # Debug - apk add gcovr - mkdir -p bld/debug-x64 - cd bld/debug-x64 - meson --buildtype=debug -Db_coverage=true ../.. - ninja -k0 - meson test --print-errorlogs - ninja coverage-html - mv meson-logs/coveragereport ../../coverage - ninja coverage-text - tail -2 meson-logs/coverage.txt - ./yambar --version - cd ../.. # Release - mkdir -p bld/release-x64 - cd bld/release-x64 - meson --buildtype=minsize ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. # X11 only - mkdir -p bld/x11-only - cd bld/x11-only - meson --buildtype=debug -Dbackend-x11=enabled -Dbackend-wayland=disabled ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. # Wayland only - mkdir -p bld/wayland-only - cd bld/wayland-only - meson --buildtype=debug -Dbackend-x11=disabled -Dbackend-wayland=enabled ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. - name: x86 when: - event: [manual, pull_request] - event: [push, tag] branch: [master, releases/*] depends_on: [subprojects] image: i386/alpine:latest commands: - apk add musl-dev eudev-libs eudev-dev linux-headers meson ninja gcc scdoc - apk add pixman-dev freetype-dev fontconfig-dev - apk add libxcb-dev xcb-util-wm-dev xcb-util-cursor-dev yaml-dev - apk add wayland-dev wayland-protocols wlroots-dev - apk add json-c-dev libmpdclient-dev alsa-lib-dev pulseaudio-dev pipewire-dev - apk add ttf-dejavu - apk add git - apk add flex bison # Debug - mkdir -p bld/debug-x86 - cd bld/debug-x86 - meson --buildtype=debug ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. # Release - mkdir -p bld/release-x86 - cd bld/release-x86 - meson --buildtype=minsize ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. # Plugins as shared modules - mkdir -p bld/shared-modules - cd bld/shared-modules - meson --buildtype=debug -Dcore-plugins-as-shared-libraries=true ../../ - ninja -k0 - meson test --print-errorlogs - ./yambar --version - cd ../.. yambar-1.11.0/CHANGELOG.md000066400000000000000000000434701460770427600147030ustar00rootroot00000000000000# Changelog * [1.11.0](#1-11-0) * [1.10.0](#1-10-0) * [1.9.0](#1-9-0) * [1.8.0](#1-8-0) * [1.7.0](#1-7-0) * [1.6.2](#1-6-2) * [1.6.1](#1-6-1) * [1.6.0](#1-6-0) * [1.5.0](#1-5-0) ## 1.11.0 ### Added * battery: current smoothing, for improved discharge estimates. * battery: scale option, for batteries that report 'charge' at a different scale than 'current'. * network: new `quality` tag (Wi-Fi only). * Read alternative config from pipes and FIFOs (e.g. `--config /dev/stdin`) ([#340][340]). * Added `overlay` and `background` as possible `layer` values ([#372][372]). [340]: https://codeberg.org/dnkl/yambar/pulls/340 [372]: https://codeberg.org/dnkl/yambar/issues/372 ### Changed * log-level: default to `warning` * network: use dynlist instead of fixed name ([#355][355]) [355]: https://codeberg.org/dnkl/yambar/pulls/355 ### Fixed * Compiler error _‘fmt’ may be used uninitialized_ ([#311][311]). * map: conditions failing to match when they contain multiple, quoted tag values ([#302][302]). * Crash when hidden by an opaque window. * Bar not resizing itself when the screen resolution is changed ([#330][330]). * i3/sway: incorrect empty/title state of workspaces ([#343][343]). * mem: state updated on each bar redraw ([#352][352]). * script: buffer overflow when reading large amounts of data. * i3/sway: module fails when reloading config file ([#361][361]). * Worked around bug in gcc causing a compilation error ([#350][350]). * Miscalculation of list width in presence of empty particles ([#369][369]). * Log-level not respected by syslog. [311]: https://codeberg.org/dnkl/yambar/issues/311 [302]: https://codeberg.org/dnkl/yambar/issues/302 [330]: https://codeberg.org/dnkl/yambar/issues/330 [343]: https://codeberg.org/dnkl/yambar/issues/343 [352]: https://codeberg.org/dnkl/yambar/issues/352 [361]: https://codeberg.org/dnkl/yambar/issues/361 [350]: https://codeberg.org/dnkl/yambar/issues/350 [369]: https://codeberg.org/dnkl/yambar/issues/369 ### Contributors * Delgan * Haden Collins * Jordan Isaacs * kotyk * Leonardo Hernández Hernández * oob * rdbo * Sertonix * steovd * Väinö Mäkelä * Yiyu Zhou ## 1.10.0 ### Added * Field width tag format option ([#246][246]) * river: support for ‘layout’ events. * dwl: support for specifying name of tags ([#256][256]) * i3/sway: extend option `sort`; use `native` to sort numbered workspaces only. * modules/dwl: handle the appid status ([#284][284]) * battery: also show estimation for time to full ([#303][303]). * on-click: tilde expansion ([#307][307]) * script: tilde expansion of `path` ([#307][307]). [246]: https://codeberg.org/dnkl/yambar/issues/246 [256]: https://codeberg.org/dnkl/yambar/pulls/256 [284]: https://codeberg.org/dnkl/yambar/pulls/284 [307]: https://codeberg.org/dnkl/yambar/issues/307 ### Changed * disk-io: `interval` renamed to `poll-interval` * mem: `interval` renamed to `poll-interval` * battery/network/script: `poll-interval` unit changed from seconds to milliseconds ([#244][244]). * all modules: minimum poll interval changed from 500ms to 250ms. * network: do not use IPv6 link-local ([#281][281]) [244]: https://codeberg.org/dnkl/yambar/issues/244 [281]: https://codeberg.org/dnkl/yambar/pulls/281 ### Fixed * Build failures for certain combinations of enabled and disabled plugins ([#239][239]). * Documentation for the `cpu` module; `interval` has been renamed to `poll-interval` ([#241][241]). * battery: module was not thread safe. * dwl module reporting only the last part of the title ([#251][251]) * i3/sway: regression; persistent workspaces shown twice ([#253][253]). * pipewire: use `roundf()` instead of `ceilf()` for more accuracy ([#262][262]) * Crash when a yaml anchor has a value that already exists in the target yaml node ([#286][286]). * battery: Fix time conversion in battery estimation ([#303][303]). * battery: poll timeout being reset when receiving irrelevant udev notification (leading to battery status never updating, in worst case) ([#305][305]). [239]: https://codeberg.org/dnkl/yambar/issues/239 [241]: https://codeberg.org/dnkl/yambar/issues/241 [251]: https://codeberg.org/dnkl/yambar/pulls/251 [253]: https://codeberg.org/dnkl/yambar/issues/253 [262]: https://codeberg.org/dnkl/yambar/issues/262 [286]: https://codeberg.org/dnkl/yambar/issues/286 [305]: https://codeberg.org/dnkl/yambar/issues/305 ### Contributors * Leonardo Gibrowski Faé (Horus) * Armin Fisslthaler * Ben Brown * David Bimmler * Leonardo Hernández Hernández * Ogromny * Oleg Hahm * Stanislav Ochotnický * tiosgz * Yutaro Ohno ## 1.9.0 ### Added * Support for specifying number of decimals when printing a float tag ([#200][200]). * Support for custom font fallbacks ([#153][153]). * overline: new decoration ([#153][153]). * i3/sway: boolean option `strip-workspace-numbers`. * font-shaping: new inheritable configuration option, allowing you to configure whether strings should be _shaped_ using HarfBuzz, or not ([#159][159]). * river: support for the new “mode” event present in version 3 of the river status manager protocol, in the form of a new tag, _”mode”_, in the `title` particle. * network: request link stats and expose under tags `dl-speed` and `ul-speed` when `poll-interval` is set. * new module: disk-io. * new module: pulse ([#223][223]). * alsa: `dB` tag ([#202][202]). * mpd: `file` tag ([#219][219]). * pipewire: add a new module for pipewire ([#224][224]) * on-click: support `next`/`previous` mouse buttons ([#228][228]). * dwl: add a new module for DWL ([#218][218]) * sway: support for workspace ‘rename’ and ‘move’ events ([#216][216]). [153]: https://codeberg.org/dnkl/yambar/issues/153 [159]: https://codeberg.org/dnkl/yambar/issues/159 [200]: https://codeberg.org/dnkl/yambar/issues/200 [202]: https://codeberg.org/dnkl/yambar/issues/202 [218]: https://codeberg.org/dnkl/yambar/pulls/218 [219]: https://codeberg.org/dnkl/yambar/pulls/219 [223]: https://codeberg.org/dnkl/yambar/pulls/223 [224]: https://codeberg.org/dnkl/yambar/pulls/224 [228]: https://codeberg.org/dnkl/yambar/pulls/228 [216]: https://codeberg.org/dnkl/yambar/issues/216 ### Changed * All modules are now compile-time optional. * Minimum required meson version is now 0.59. * Float tags are now treated as floats instead of integers when formatted with the `kb`/`kib`/`mb`/`mib`/`gb`/`gib` string particle formatters. * network: `tx-bitrate` and `rx-bitrate` are now in bits/s instead of Mb/s. Use the `mb` string formatter to render these tags as before (e.g. `string: {text: "{tx-bitrate:mb}"}`). * i3: newly created, and **unfocused** workspaces are now considered non-empty ([#191][191]) * alsa: use dB instead of raw volume values, if possible, when calculating the `percent` tag ([#202][202]) * cpu: `content` particle is now a template instantiated once for each core, and once for the total CPU usage. See **yambar-modules-cpu**(5) for more information ([#207][207]). * **BREAKING CHANGE**: overhaul of the `map` particle. Instead of specifying a `tag` and then an array of `values`, you must now simply use an array of `conditions`, that consist of: ` ` where `` is one of: `== != < <= > >=` Note that boolean tags must be used as is: `online` `~online # use '~' to match for their falsehood` As an example, if you previously had something like: ``` map: tag: State values: unrecognized: ... ``` You would now write it as: ``` map: conditions: State == unrecognized: ... ``` Note that if `` contains any non-alphanumerical characters, it **must** be surrounded by `""`: `State == "very confused!!!"` Finally, you can mix and match conditions using the boolean operators `&&` and `||`: ``` && && ( || ) # parenthesis work ~( && ) # '~' can be applied to any condition ``` For a more thorough explanation, see the updated map section in the man page for yambar-particles([#137][137], [#175][175] and [#][182]). [137]: https://codeberg.org/dnkl/yambar/issues/137 [175]: https://codeberg.org/dnkl/yambar/issues/172 [182]: https://codeberg.org/dnkl/yambar/issues/182 [191]: https://codeberg.org/dnkl/yambar/issues/191 [202]: https://codeberg.org/dnkl/yambar/issues/202 [207]: https://codeberg.org/dnkl/yambar/issues/207 ### Fixed * i3: fixed “missing workspace indicator” (_err: modules/i3.c:94: workspace reply/event without 'name' and/or 'output', and/or 'focus' properties_). * Slow/laggy behavior when quickly spawning many `on-click` handlers, e.g. when handling mouse wheel events ([#169][169]). * cpu: don’t error out on systems where SMT has been disabled ([#172][172]). * examples/dwl-tags: updated parsing of `output` name ([#178][178]). * sway-xkb: don’t crash when Sway sends an _”added”_ event for a device yambar is already tracking ([#177][177]). * Crash when a particle is “too wide”, and tries to render outside the bar ([#198][198]). * string: crash when failing to convert string to UTF-32. * script: only first transaction processed when receiving multiple transactions in a single batch ([#221][221]). * network: missing SSID (recent kernels, or possibly wireless drivers, no longer provide the SSID in the `NL80211_CMD_NEW_STATION` response) ([#226][226]). * sway-xkb: crash when compositor presents multiple inputs with identical IDs ([#229][229]). [169]: https://codeberg.org/dnkl/yambar/issues/169 [172]: https://codeberg.org/dnkl/yambar/issues/172 [178]: https://codeberg.org/dnkl/yambar/issues/178 [177]: https://codeberg.org/dnkl/yambar/issues/177 [198]: https://codeberg.org/dnkl/yambar/issues/198 [221]: https://codeberg.org/dnkl/yambar/issues/221 [226]: https://codeberg.org/dnkl/yambar/issues/226 [229]: https://codeberg.org/dnkl/yambar/issues/229 ### Contributors * Baptiste Daroussin * Horus * Johannes * Leonardo Gibrowski Faé * Leonardo Neumann * Midgard * Ogromny * Peter Rice * Timur Celik * Willem van de Krol * hiog ## 1.8.0 ### Added * ramp: can now have custom min and max values ([#103](https://codeberg.org/dnkl/yambar/issues/103)). * border: new decoration. * i3/sway: new boolean tag: `empty` ([#139](https://codeberg.org/dnkl/yambar/issues/139)). * mem: a module handling system memory monitoring * cpu: a module offering cpu usage monitoring * removables: support for audio CDs ([#146](https://codeberg.org/dnkl/yambar/issues/146)). * removables: new boolean tag: `audio`. ### Changed * fcft >= 3.0 is now required. * Made `libmpdclient` an optional dependency * battery: unknown battery states are now mapped to ‘unknown’, instead of ‘discharging’. * Wayland: the bar no longer exits when the monitor is disabled/unplugged ([#106](https://codeberg.org/dnkl/yambar/issues/106)). ### Fixed * `left-margin` and `right-margin` from being rejected as invalid options. * Crash when `udev_monitor_receive_device()` returned `NULL`. This affected the “backlight”, “battery” and “removables” modules ([#109](https://codeberg.org/dnkl/yambar/issues/109)). * foreign-toplevel: update bar when a top-level is closed. * Bar not being mapped on an output before at least one module has “refreshed” it ([#116](https://codeberg.org/dnkl/yambar/issues/116)). * network: failure to retrieve wireless attributes (SSID, RX/TX bitrate, signal strength etc). * Integer options that were supposed to be >= 0 were incorrectly allowed, leading to various bad things; including yambar crashing, or worse, the compositor crashing ([#129](https://codeberg.org/dnkl/yambar/issues/129)). * kib/kb, mib/mb and gib/gb formatters were inverted. ### Contributors * [sochotnicky](https://codeberg.org/sochotnicky) * Alexandre Acebedo * anb * Baptiste Daroussin * Catterwocky * horus645 * Jan Beich * mz * natemaia * nogerine * Soc Virnyl S. Estela * Vincent Fischer ## 1.7.0 ### Added * i3: `persistent` attribute, allowing persistent workspaces ([#72](https://codeberg.org/dnkl/yambar/issues/72)). * bar: `border.{left,right,top,bottom}-width`, allowing the width of each side of the border to be configured individually. `border.width` is now a short-hand for setting all four borders to the same value ([#77](https://codeberg.org/dnkl/yambar/issues/77)). * bar: `layer: top|bottom`, allowing the layer which the bar is rendered on to be changed. Wayland only - ignored on X11. * river: `all-monitors: false|true`. * `-d,--log-level=info|warning|error|none` command line option ([#84](https://codeberg.org/dnkl/yambar/issues/84)). * river: support for the river-status protocol, version 2 (‘urgent’ views). * `online` tag to the `alsa` module. * alsa: `volume` and `muted` options, allowing you to configure which channels to use as source for the volume level and muted state. * foreign-toplevel: Wayland module that provides information about currently opened windows. * alsa: support for capture devices. * network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags. * network: `poll-interval` option (for the new `signal` and `*-bitrate` tags). * tags: percentage tag formatter, for range tags: `{tag_name:%}`. * tags: kb/mb/gb, and kib/mib/gib tag formatters. * clock: add a config option to show UTC time. ### Changed * bar: do not add `spacing` around empty (zero-width) modules. * alsa: do not error out if we fail to connect to the ALSA device, or if we get disconnected. Instead, keep retrying until we succeed ([#86](https://codeberg.org/dnkl/yambar/issues/86)). ### Fixed * `yambar --backend=wayland` always erroring out with _”yambar was compiled without the Wayland backend”_. * Regression: `{where}` tag not being expanded in progress-bar `on-click` handlers. * `alsa` module causing yambar to use 100% CPU if the ALSA device is disconnected ([#61](https://codeberg.org/dnkl/yambar/issues/61)). ### Contributors * [paemuri](https://codeberg.org/paemuri) * [ericonr](https://codeberg.org/ericonr) * [Nulo](https://nulo.in) ## 1.6.2 ### Added * Text shaping support. * Support for middle and right mouse buttons, mouse wheel and trackpad scrolling ([#39](https://codeberg.org/dnkl/yambar/issues/39)). * script: polling mode. See the new `poll-interval` option ([#67](https://codeberg.org/dnkl/yambar/issues/67)). ### Changed * doc: split up **yambar-modules**(5) into multiple man pages, one for each module ([#15](https://codeberg.org/dnkl/yambar/issues/15)). * fcft >= 2.4.0 is now required. * sway-xkb: non-keyboard inputs are now ignored ([#51](https://codeberg.org/dnkl/yambar/issues/51)). * battery: don’t terminate (causing last status to “freeze”) when failing to update; retry again later ([#44](https://codeberg.org/dnkl/yambar/issues/44)). * battery: differentiate "Not Charging" and "Discharging" in state tag of battery module. ([#57](https://codeberg.org/dnkl/yambar/issues/57)). * string: use HORIZONTAL ELLIPSIS instead of three regular periods when truncating a string ([#73](https://codeberg.org/dnkl/yambar/issues/73)). ### Fixed * Crash when merging non-dictionary anchors in the YAML configuration ([#32](https://codeberg.org/dnkl/yambar/issues/32)). * Crash in the `ramp` particle when the tag’s value was out-of-bounds ([#45](https://codeberg.org/dnkl/yambar/issues/45)). * Crash when a string particle contained `{}` ([#48](https://codeberg.org/dnkl/yambar/issues/48)). * `script` module rejecting range tag end values containing the digit `9` ([#60](https://codeberg.org/dnkl/yambar/issues/60)). ### Contributors * [novakane](https://codeberg.org/novakane) * [mz](https://codeberg.org/mz) ## 1.6.1 ### Changed * i3: workspaces with numerical names are sorted separately from non-numerically named workspaces ([#30](https://codeberg.org/dnkl/yambar/issues/30)). ### Fixed * mpd: `elapsed` tag not working (regression, introduced in 1.6.0). * Wrong background color for (semi-) transparent backgrounds. * battery: stats sometimes getting stuck at 0, or impossibly large values ([#25](https://codeberg.org/dnkl/yambar/issues/25)). ## 1.6.0 ### Added * alsa: `percent` tag. This is an integer tag that represents the current volume as a percentage value ([#10](https://codeberg.org/dnkl/yambar/issues/10)). * river: added documentation ([#9](https://codeberg.org/dnkl/yambar/issues/9)). * script: new module, adds support for custom user scripts ([#11](https://codeberg.org/dnkl/yambar/issues/11)). * mpd: `volume` tag. This is a range tag that represents MPD's current volume in percentage (0-100) * i3: `sort` configuration option, that controls how the workspace list is sorted. Can be set to one of `none`, `ascending` or `descending`. Default is `none` ([#17](https://codeberg.org/dnkl/yambar/issues/17)). * i3: `mode` tag: the name of the currently active mode ### Fixed * YAML parsing error messages being replaced with a generic _“unknown error”_. * Memory leak when a YAML parsing error was encountered. * clock: update every second when necessary ([#12](https://codeberg.org/dnkl/yambar/issues/12)). * mpd: fix compilation with clang ([#16](https://codeberg.org/dnkl/yambar/issues/16)). * Crash when the alpha component in a color value was 0. * XCB: Fallback to non-primary monitor when the primary monitor is disconnected ([#20](https://codeberg.org/dnkl/yambar/issues/20)) ### Contributors * [JorwLNKwpH](https://codeberg.org/JorwLNKwpH) * [optimus-prime](https://codeberg.org/optimus-prime) ## 1.5.0 ### Added * battery: support for drivers that use `charge_*` (instead of `energy_*`) sys files. * removables: SD card support. * removables: new `ignore` property. * Wayland: multi-seat support. * **Experimental**: 'river': new module for the river Wayland compositor. ### Changed * Requires fcft-2.2.x. * battery: a poll value of 0 disables polling. ### Fixed * mpd: check of return value from `thrd_create`. * battery: handle 'manufacturer' and 'model_name' not being present. * Wayland: handle runtime scaling changes. yambar-1.11.0/LICENSE000066400000000000000000000020561460770427600140720ustar00rootroot00000000000000MIT License Copyright (c) 2018 Daniel Eklöf 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. yambar-1.11.0/PKGBUILD000066400000000000000000000016351460770427600142130ustar00rootroot00000000000000pkgname=yambar pkgver=1.11.0 pkgrel=1 pkgdesc="Simplistic and highly configurable status panel for X and Wayland" changelog=CHANGELOG.md arch=('x86_64' 'aarch64') url=https://codeberg.org/dnkl/yambar license=(mit) makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.1') depends=( 'libxcb' 'xcb-util' 'xcb-util-cursor' 'xcb-util-wm' 'wayland' 'pixman' 'libyaml' 'alsa-lib' 'libudev.so' 'json-c' 'libmpdclient' 'libpulse' 'pipewire' 'fcft>=3.0.0' 'fcft<4.0.0') optdepends=('xcb-util-errors: better X error messages') source=() pkgver() { cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || head -3 ../meson.build | grep version | cut -d "'" -f 2 } build() { meson --buildtype=release --prefix=/usr --wrap-mode=nofallback -Db_lto=true -Dbackend-x11=enabled -Dbackend-wayland=enabled ../ ninja } package() { DESTDIR="${pkgdir}/" ninja install } yambar-1.11.0/PKGBUILD.wayland-only000066400000000000000000000015331460770427600166250ustar00rootroot00000000000000pkgname=yambar-wayland pkgver=1.11.0 pkgrel=1 pkgdesc="Simplistic and highly configurable status panel for Wayland" arch=('x86_64' 'aarch64') url=https://codeberg.org/dnkl/yambar license=(mit) conflicts=('yambar') provides=('yambar') makedepends=('meson' 'ninja' 'scdoc' 'tllist>=1.0.1') depends=( 'wayland' 'pixman' 'libyaml' 'alsa-lib' 'libudev.so' 'json-c' 'libmpdclient' 'libpulse' 'pipewire' 'fcft>=3.0.0' 'fcft<4.0.0') source=() changelog=CHANGELOG.md pkgver() { cd ../.git &> /dev/null && git describe --tags --long | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || head -3 ../meson.build | grep version | cut -d "'" -f 2 } build() { meson --buildtype=release --prefix=/usr --wrap-mode=nofallback -Db_lto=true -Dbackend-x11=disabled -Dbackend-wayland=enabled ../ ninja } package() { DESTDIR="${pkgdir}/" ninja install } yambar-1.11.0/README.md000066400000000000000000000067261460770427600143540ustar00rootroot00000000000000[![CI status](https://ci.codeberg.org/api/badges/dnkl/yambar/status.svg)](https://ci.codeberg.org/dnkl/yambar) # Yambar [![Packaging status](https://repology.org/badge/vertical-allrepos/yambar.svg)](https://repology.org/project/yambar/versions) ## Index 1. [Introduction](#introduction) 1. [Configuration](#configuration) 1. [Modules](#modules) 1. [Installation](#installation) 1. [Bugs](#bugs) ## Introduction ![screenshot](screenshot.png "Example configuration") **yambar** is a lightweight and configurable status panel (_bar_, for short) for X11 and Wayland, that goes to great lengths to be both CPU and battery efficient - polling is only done when **absolutely** necessary. It has a number of _modules_ that provide information in the form of _tags_. For example, the _clock_ module has a _date_ tag that contains the current date. The modules do not know _how_ to present the information though. This is instead done by _particles_. And the user, you, decides _which_ particles (and thus _how_ to present the data) to use. Furthermore, each particle can have a _decoration_ - a background color or a graphical underline, for example. There is **no** support for images or icons. use an icon font (e.g. _Font Awesome_, or _Material Icons_) if you want a graphical representation. There are a number of modules and particles builtin. More can be added as plugins. You can even write your own! To summarize: a _bar_ displays information provided by _modules_, using _particles_ and _decorations_. **How** is configured by you. ## Configuration Yambar is configured using YAML, in `~/.config/yambar/config.yml`. It must define a top-level dictionary named **bar**: ```yaml bar: height: 26 location: top background: 000000ff right: - clock: content: - string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"} - string: {text: "{date}", right-margin: 5} - string: {text: , font: "Font Awesome 6 Free:style=solid:size=12"} - string: {text: "{time}"} ``` For details, see the man pages (**yambar**(5) is a good start). Example configurations can be found in [examples](examples/configurations). ## Modules Available modules: * alsa * backlight * battery * clock * cpu * disk-io * dwl * foreign-toplevel * i3 (and Sway) * label * mem * mpd * network * pipewire * pulse * removables * river * script (see script [examples](examples/scripts)) * sway-xkb * xkb (_XCB backend only_) * xwindow (_XCB backend only_) ## Installation To build, first, create a build directory, and switch to it: ```sh mkdir -p bld/release && cd bld/release ``` Second, configure the build (if you intend to install it globally, you might also want `--prefix=/usr`): ```sh meson setup --buildtype=release ../.. ``` Optionally, explicitly disable a backend (or enable, if you want a configuration error if not all dependencies are met) by adding either `-Dbackend-x11=disabled|enabled` or `-Dbackend-wayland=disabled|enabled` to the meson command line. Three, build it: ```sh ninja ``` Optionally, install it: ```sh ninja install ``` ## Bugs Please report bugs to https://codeberg.org/dnkl/yambar/issues The report should contain the following: * Which Wayland compositor (and version) you are running * Yambar version (`yambar --version`) * Log output from yambar (start yambar from a terminal) * If reporting a crash, please try to provide a `bt full` backtrace **with symbols** (i.e. use a debug build) * Steps to reproduce. The more details the better yambar-1.11.0/bar/000077500000000000000000000000001460770427600136265ustar00rootroot00000000000000yambar-1.11.0/bar/backend.h000066400000000000000000000010321460770427600153620ustar00rootroot00000000000000#pragma once #include #include "bar.h" struct backend { bool (*setup)(struct bar *bar); void (*cleanup)(struct bar *bar); void (*loop)(struct bar *bar, void (*expose)(const struct bar *bar), void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y)); void (*commit)(const struct bar *bar); void (*refresh)(const struct bar *bar); void (*set_cursor)(struct bar *bar, const char *cursor); const char *(*output_name)(const struct bar *bar); }; yambar-1.11.0/bar/bar.c000066400000000000000000000370701460770427600145450ustar00rootroot00000000000000#include "bar.h" #include "private.h" #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "bar" #define LOG_ENABLE_DBG 0 #include "../log.h" #if defined(ENABLE_X11) #include "xcb.h" #endif #if defined(ENABLE_WAYLAND) #include "wayland.h" #endif #define max(x, y) ((x) > (y) ? (x) : (y)) /* * Calculate total width of left/center/rigth groups. * Note: begin_expose() must have been called */ static void calculate_widths(const struct private *b, int *left, int *center, int *right) { *left = 0; *center = 0; *right = 0; for (size_t i = 0; i < b->left.count; i++) { struct exposable *e = b->left.exps[i]; if (e->width > 0) *left += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->center.count; i++) { struct exposable *e = b->center.exps[i]; if (e->width > 0) *center += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->right.count; i++) { struct exposable *e = b->right.exps[i]; if (e->width > 0) *right += b->left_spacing + e->width + b->right_spacing; } /* No spacing on the edges (that's what the margins are for) */ if (*left > 0) *left -= b->left_spacing + b->right_spacing; if (*center > 0) *center -= b->left_spacing + b->right_spacing; if (*right > 0) *right -= b->left_spacing + b->right_spacing; assert(*left >= 0); assert(*center >= 0); assert(*right >= 0); } static void expose(const struct bar *_bar) { const struct private *bar = _bar->private; pixman_image_t *pix = bar->pix; pixman_image_fill_rectangles(PIXMAN_OP_SRC, pix, &bar->background, 1, &(pixman_rectangle16_t){0, 0, bar->width, bar->height_with_border}); pixman_image_fill_rectangles( PIXMAN_OP_OVER, pix, &bar->border.color, 4, (pixman_rectangle16_t[]){ /* Left */ {0, 0, bar->border.left_width, bar->height_with_border}, /* Right */ {bar->width - bar->border.right_width, 0, bar->border.right_width, bar->height_with_border}, /* Top */ {bar->border.left_width, 0, bar->width - bar->border.left_width - bar->border.right_width, bar->border.top_width}, /* Bottom */ {bar->border.left_width, bar->height_with_border - bar->border.bottom_width, bar->width - bar->border.left_width - bar->border.right_width, bar->border.bottom_width}, }); for (size_t i = 0; i < bar->left.count; i++) { struct module *m = bar->left.mods[i]; struct exposable *e = bar->left.exps[i]; if (e != NULL) e->destroy(e); bar->left.exps[i] = module_begin_expose(m); assert(bar->left.exps[i]->width >= 0); } for (size_t i = 0; i < bar->center.count; i++) { struct module *m = bar->center.mods[i]; struct exposable *e = bar->center.exps[i]; if (e != NULL) e->destroy(e); bar->center.exps[i] = module_begin_expose(m); assert(bar->center.exps[i]->width >= 0); } for (size_t i = 0; i < bar->right.count; i++) { struct module *m = bar->right.mods[i]; struct exposable *e = bar->right.exps[i]; if (e != NULL) e->destroy(e); bar->right.exps[i] = module_begin_expose(m); assert(bar->right.exps[i]->width >= 0); } int left_width, center_width, right_width; calculate_widths(bar, &left_width, ¢er_width, &right_width); int y = bar->border.top_width; int x = bar->border.left_width + bar->left_margin - bar->left_spacing; pixman_region32_t clip; pixman_region32_init_rect( &clip, bar->border.left_width + bar->left_margin, bar->border.top_width, (bar->width - bar->left_margin - bar->right_margin - bar->border.left_width - bar->border.right_width), bar->height); pixman_image_set_clip_region32(pix, &clip); pixman_region32_fini(&clip); for (size_t i = 0; i < bar->left.count; i++) { const struct exposable *e = bar->left.exps[i]; e->expose(e, pix, x + bar->left_spacing, y, bar->height); if (e->width > 0) x += bar->left_spacing + e->width + bar->right_spacing; } x = bar->width / 2 - center_width / 2 - bar->left_spacing; for (size_t i = 0; i < bar->center.count; i++) { const struct exposable *e = bar->center.exps[i]; e->expose(e, pix, x + bar->left_spacing, y, bar->height); if (e->width > 0) x += bar->left_spacing + e->width + bar->right_spacing; } x = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width); for (size_t i = 0; i < bar->right.count; i++) { const struct exposable *e = bar->right.exps[i]; e->expose(e, pix, x + bar->left_spacing, y, bar->height); if (e->width > 0) x += bar->left_spacing + e->width + bar->right_spacing; } bar->backend.iface->commit(_bar); } static void refresh(const struct bar *bar) { const struct private *b = bar->private; b->backend.iface->refresh(bar); } static void set_cursor(struct bar *bar, const char *cursor) { struct private *b = bar->private; b->backend.iface->set_cursor(bar, cursor); } static const char * output_name(const struct bar *bar) { const struct private *b = bar->private; return b->backend.iface->output_name(bar); } static void on_mouse(struct bar *_bar, enum mouse_event event, enum mouse_button btn, int x, int y) { struct private *bar = _bar->private; if ((y < bar->border.top_width || y >= (bar->height_with_border - bar->border.bottom_width)) || (x < bar->border.left_width || x >= (bar->width - bar->border.right_width))) { set_cursor(_bar, "left_ptr"); return; } int left_width, center_width, right_width; calculate_widths(bar, &left_width, ¢er_width, &right_width); int mx = bar->border.left_width + bar->left_margin - bar->left_spacing; for (size_t i = 0; i < bar->left.count; i++) { struct exposable *e = bar->left.exps[i]; if (e->width == 0) continue; mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) e->on_mouse(e, _bar, event, btn, x - mx, y); return; } mx += e->width + bar->right_spacing; } mx = bar->width / 2 - center_width / 2 - bar->left_spacing; for (size_t i = 0; i < bar->center.count; i++) { struct exposable *e = bar->center.exps[i]; if (e->width == 0) continue; mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) e->on_mouse(e, _bar, event, btn, x - mx, y); return; } mx += e->width + bar->right_spacing; } mx = bar->width - (right_width + bar->left_spacing + bar->right_margin + bar->border.right_width); for (size_t i = 0; i < bar->right.count; i++) { struct exposable *e = bar->right.exps[i]; if (e->width == 0) continue; mx += bar->left_spacing; if (x >= mx && x < mx + e->width) { if (e->on_mouse != NULL) e->on_mouse(e, _bar, event, btn, x - mx, y); return; } mx += e->width + bar->right_spacing; } set_cursor(_bar, "left_ptr"); } static void set_module_thread_name(thrd_t id, struct module *mod) { char title[16]; if (mod->description != NULL) strncpy(title, mod->description(mod), sizeof(title)); else strncpy(title, "mod:", sizeof(title)); title[15] = '\0'; if (pthread_setname_np(id, title) < 0) LOG_ERRNO("failed to set thread title"); } static int run(struct bar *_bar) { struct private *bar = _bar->private; bar->height_with_border = bar->height + bar->border.top_width + bar->border.bottom_width; if (!bar->backend.iface->setup(_bar)) { bar->backend.iface->cleanup(_bar); if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) LOG_ERRNO("failed to signal abort"); return 1; } set_cursor(_bar, "left_ptr"); expose(_bar); /* Start modules */ thrd_t thrd_left[max(bar->left.count, 1)]; thrd_t thrd_center[max(bar->center.count, 1)]; thrd_t thrd_right[max(bar->right.count, 1)]; for (size_t i = 0; i < bar->left.count; i++) { struct module *mod = bar->left.mods[i]; mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_left[i], (int (*)(void *))bar->left.mods[i]->run, mod); set_module_thread_name(thrd_left[i], mod); } for (size_t i = 0; i < bar->center.count; i++) { struct module *mod = bar->center.mods[i]; mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_center[i], (int (*)(void *))bar->center.mods[i]->run, mod); set_module_thread_name(thrd_center[i], mod); } for (size_t i = 0; i < bar->right.count; i++) { struct module *mod = bar->right.mods[i]; mod->abort_fd = _bar->abort_fd; thrd_create(&thrd_right[i], (int (*)(void *))bar->right.mods[i]->run, mod); set_module_thread_name(thrd_right[i], mod); } LOG_DBG("all modules started"); bar->backend.iface->loop(_bar, &expose, &on_mouse); LOG_DBG("shutting down"); /* Wait for modules to terminate */ int ret = 0; int mod_ret; for (size_t i = 0; i < bar->left.count; i++) { thrd_join(thrd_left[i], &mod_ret); if (mod_ret != 0) { const struct module *m = bar->left.mods[i]; LOG_ERR("module: LEFT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret); } ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; } for (size_t i = 0; i < bar->center.count; i++) { thrd_join(thrd_center[i], &mod_ret); if (mod_ret != 0) { const struct module *m = bar->center.mods[i]; LOG_ERR("module: CENTER #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret); } ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; } for (size_t i = 0; i < bar->right.count; i++) { thrd_join(thrd_right[i], &mod_ret); if (mod_ret != 0) { const struct module *m = bar->right.mods[i]; LOG_ERR("module: RIGHT #%zu (%s): non-zero exit value: %d", i, m->description(m), mod_ret); } ret = ret == 0 && mod_ret != 0 ? mod_ret : ret; } LOG_DBG("modules joined"); bar->backend.iface->cleanup(_bar); LOG_DBG("bar exiting"); return ret; } static void destroy(struct bar *bar) { struct private *b = bar->private; for (size_t i = 0; i < b->left.count; i++) { struct module *m = b->left.mods[i]; struct exposable *e = b->left.exps[i]; if (e != NULL) e->destroy(e); m->destroy(m); } for (size_t i = 0; i < b->center.count; i++) { struct module *m = b->center.mods[i]; struct exposable *e = b->center.exps[i]; if (e != NULL) e->destroy(e); m->destroy(m); } for (size_t i = 0; i < b->right.count; i++) { struct module *m = b->right.mods[i]; struct exposable *e = b->right.exps[i]; if (e != NULL) e->destroy(e); m->destroy(m); } free(b->left.mods); free(b->left.exps); free(b->center.mods); free(b->center.exps); free(b->right.mods); free(b->right.exps); free(b->monitor); free(b->backend.data); free(bar->private); free(bar); } struct bar * bar_new(const struct bar_config *config) { void *backend_data = NULL; const struct backend *backend_iface = NULL; switch (config->backend) { case BAR_BACKEND_AUTO: #if defined(ENABLE_X11) && !defined(ENABLE_WAYLAND) backend_data = bar_backend_xcb_new(); backend_iface = &xcb_backend_iface; #elif !defined(ENABLE_X11) && defined(ENABLE_WAYLAND) backend_data = bar_backend_wayland_new(); backend_iface = &wayland_backend_iface; #else if (getenv("WAYLAND_DISPLAY") != NULL) { backend_data = bar_backend_wayland_new(); backend_iface = &wayland_backend_iface; } else { backend_data = bar_backend_xcb_new(); backend_iface = &xcb_backend_iface; } #endif break; case BAR_BACKEND_XCB: #if defined(ENABLE_X11) backend_data = bar_backend_xcb_new(); backend_iface = &xcb_backend_iface; #else LOG_ERR("yambar was compiled without the XCB backend"); return NULL; #endif break; case BAR_BACKEND_WAYLAND: #if defined(ENABLE_WAYLAND) backend_data = bar_backend_wayland_new(); backend_iface = &wayland_backend_iface; #else LOG_ERR("yambar was compiled without the Wayland backend"); return NULL; #endif break; } if (backend_data == NULL) return NULL; struct private *priv = calloc(1, sizeof(*priv)); priv->monitor = config->monitor != NULL ? strdup(config->monitor) : NULL; priv->layer = config->layer; priv->location = config->location; priv->height = config->height; priv->background = config->background; priv->left_spacing = config->left_spacing; priv->right_spacing = config->right_spacing; priv->left_margin = config->left_margin; priv->right_margin = config->right_margin; priv->trackpad_sensitivity = config->trackpad_sensitivity; priv->border.left_width = config->border.left_width; priv->border.right_width = config->border.right_width; priv->border.top_width = config->border.top_width; priv->border.bottom_width = config->border.bottom_width; priv->border.color = config->border.color; priv->border.left_margin = config->border.left_margin; priv->border.right_margin = config->border.right_margin; priv->border.top_margin = config->border.top_margin; priv->border.bottom_margin = config->border.bottom_margin; priv->left.mods = malloc(config->left.count * sizeof(priv->left.mods[0])); priv->left.exps = calloc(config->left.count, sizeof(priv->left.exps[0])); priv->center.mods = malloc(config->center.count * sizeof(priv->center.mods[0])); priv->center.exps = calloc(config->center.count, sizeof(priv->center.exps[0])); priv->right.mods = malloc(config->right.count * sizeof(priv->right.mods[0])); priv->right.exps = calloc(config->right.count, sizeof(priv->right.exps[0])); priv->left.count = config->left.count; priv->center.count = config->center.count; priv->right.count = config->right.count; priv->backend.data = backend_data; priv->backend.iface = backend_iface; for (size_t i = 0; i < priv->left.count; i++) priv->left.mods[i] = config->left.mods[i]; for (size_t i = 0; i < priv->center.count; i++) priv->center.mods[i] = config->center.mods[i]; for (size_t i = 0; i < priv->right.count; i++) priv->right.mods[i] = config->right.mods[i]; struct bar *bar = calloc(1, sizeof(*bar)); bar->private = priv; bar->run = &run; bar->destroy = &destroy; bar->refresh = &refresh; bar->set_cursor = &set_cursor; bar->output_name = &output_name; for (size_t i = 0; i < priv->left.count; i++) priv->left.mods[i]->bar = bar; for (size_t i = 0; i < priv->center.count; i++) priv->center.mods[i]->bar = bar; for (size_t i = 0; i < priv->right.count; i++) priv->right.mods[i]->bar = bar; return bar; } yambar-1.11.0/bar/bar.h000066400000000000000000000026061460770427600145470ustar00rootroot00000000000000#pragma once #include "../color.h" #include "../font-shaping.h" #include "../module.h" struct bar { int abort_fd; void *private; int (*run)(struct bar *bar); void (*destroy)(struct bar *bar); void (*refresh)(const struct bar *bar); void (*set_cursor)(struct bar *bar, const char *cursor); const char *(*output_name)(const struct bar *bar); }; enum bar_location { BAR_TOP, BAR_BOTTOM }; enum bar_layer { BAR_LAYER_OVERLAY, BAR_LAYER_TOP, BAR_LAYER_BOTTOM, BAR_LAYER_BACKGROUND }; enum bar_backend { BAR_BACKEND_AUTO, BAR_BACKEND_XCB, BAR_BACKEND_WAYLAND }; struct bar_config { enum bar_backend backend; const char *monitor; enum bar_layer layer; enum bar_location location; enum font_shaping font_shaping; int height; int left_spacing, right_spacing; int left_margin, right_margin; int trackpad_sensitivity; pixman_color_t background; struct { int left_width, right_width; int top_width, bottom_width; pixman_color_t color; int left_margin, right_margin; int top_margin, bottom_margin; } border; struct { struct module **mods; size_t count; } left; struct { struct module **mods; size_t count; } center; struct { struct module **mods; size_t count; } right; }; struct bar *bar_new(const struct bar_config *config); yambar-1.11.0/bar/meson.build000066400000000000000000000027651460770427600160020ustar00rootroot00000000000000bar_backends = [] if backend_x11 bar_x11 = declare_dependency(sources: ['xcb.c', 'xcb.h'], dependencies: [xcb_stuff]) bar_backends += [bar_x11] endif if backend_wayland wayland_protocols = dependency('wayland-protocols') wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') wscanner = dependency('wayland-scanner', native: true) wscanner_prog = find_program( wscanner.get_variable('wayland_scanner'), native: true) wl_proto_headers = [] wl_proto_src = [] foreach prot : [ '../external/wlr-layer-shell-unstable-v1.xml', wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml', wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml'] wl_proto_headers += custom_target( prot.underscorify() + '-client-header', output: '@BASENAME@.h', input: prot, command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@']) wl_proto_src += custom_target( prot.underscorify() + '-private-code', output: '@BASENAME@.c', input: prot, command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) endforeach bar_wayland = declare_dependency( sources: ['wayland.c', 'wayland.h'] + wl_proto_src + wl_proto_headers, dependencies: [wayland_client, wayland_cursor, tllist]) bar_backends += [bar_wayland] endif bar = declare_dependency( sources: ['bar.c', 'bar.h', 'private.h', 'backend.h'], dependencies: bar_backends + [threads]) install_headers('bar.h', subdir: 'yambar/bar') yambar-1.11.0/bar/private.h000066400000000000000000000020471460770427600154540ustar00rootroot00000000000000#pragma once #include "../bar/bar.h" #include "backend.h" struct private { /* From bar_config */ char *monitor; enum bar_layer layer; enum bar_location location; int height; int left_spacing, right_spacing; int left_margin, right_margin; int trackpad_sensitivity; pixman_color_t background; struct { int left_width, right_width; int top_width, bottom_width; pixman_color_t color; int left_margin, right_margin; int top_margin, bottom_margin; } border; struct { struct module **mods; struct exposable **exps; size_t count; } left; struct { struct module **mods; struct exposable **exps; size_t count; } center; struct { struct module **mods; struct exposable **exps; size_t count; } right; /* Calculated run-time */ int width; int height_with_border; pixman_image_t *pix; struct { void *data; const struct backend *iface; } backend; }; yambar-1.11.0/bar/wayland.c000066400000000000000000001236701460770427600154420ustar00rootroot00000000000000#include "wayland.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "bar:wayland" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../stride.h" #include "private.h" #if !defined(MFD_NOEXEC_SEAL) #define MFD_NOEXEC_SEAL 0 #endif struct buffer { bool busy; size_t width; size_t height; size_t size; void *mmapped; struct wl_buffer *wl_buf; pixman_image_t *pix; }; struct monitor { struct wayland_backend *backend; struct wl_output *output; struct zxdg_output_v1 *xdg; char *name; uint32_t wl_name; int x; int y; int width_mm; int height_mm; int width_px; int height_px; int scale; }; struct seat { struct wayland_backend *backend; struct wl_seat *seat; char *name; uint32_t id; struct wl_pointer *wl_pointer; struct { uint32_t serial; int x; int y; struct wl_surface *surface; struct wl_cursor_theme *theme; struct wl_cursor *cursor; const char *xcursor; int scale; } pointer; }; struct wayland_backend { struct bar *bar; struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_surface *surface; struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_layer_surface_v1 *layer_surface; struct wl_shm *shm; tll(struct seat) seats; struct seat *active_seat; tll(struct monitor) monitors; const struct monitor *monitor; char *last_mapped_monitor; int scale; struct zxdg_output_manager_v1 *xdg_output_manager; /* TODO: set directly in bar instead */ int width, height; /* Used to signal e.g. refresh */ int pipe_fds[2]; /* We're already waiting for a frame done callback */ bool render_scheduled; tll(struct buffer) buffers; /* List of SHM buffers */ struct buffer *next_buffer; /* Bar is rendering to this one */ struct buffer *pending_buffer; /* Finished, but not yet rendered */ struct wl_callback *frame_callback; double aggregated_scroll; bool have_discrete; void (*bar_on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y); }; static void seat_destroy(struct seat *seat) { if (seat == NULL) return; free(seat->name); if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); if (seat->wl_pointer != NULL) wl_pointer_release(seat->wl_pointer); if (seat->pointer.surface != NULL) wl_surface_destroy(seat->pointer.surface); if (seat->seat != NULL) wl_seat_release(seat->seat); } void * bar_backend_wayland_new(void) { struct wayland_backend *backend = calloc(1, sizeof(struct wayland_backend)); backend->pipe_fds[0] = backend->pipe_fds[1] = -1; return backend; } static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { // printf("SHM format: 0x%08x\n", format); } static const struct wl_shm_listener shm_listener = { .format = &shm_format, }; static void update_cursor_surface(struct wayland_backend *backend, struct seat *seat) { if (seat->pointer.serial == 0 || seat->pointer.cursor == NULL || seat->pointer.surface == NULL) { return; } struct wl_cursor_image *image = seat->pointer.cursor->images[0]; const int scale = seat->pointer.scale; wl_surface_set_buffer_scale(seat->pointer.surface, scale); wl_surface_attach(seat->pointer.surface, wl_cursor_image_get_buffer(image), 0, 0); wl_pointer_set_cursor(seat->wl_pointer, seat->pointer.serial, seat->pointer.surface, image->hotspot_x / scale, image->hotspot_y / scale); wl_surface_damage_buffer(seat->pointer.surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(seat->pointer.surface); wl_display_flush(backend->display); } static void reload_cursor_theme(struct seat *seat, int new_scale) { if (seat->pointer.theme != NULL && seat->pointer.scale == new_scale) return; if (seat->pointer.theme != NULL) { wl_cursor_theme_destroy(seat->pointer.theme); seat->pointer.theme = NULL; seat->pointer.cursor = NULL; } unsigned cursor_size = 24; const char *cursor_theme = getenv("XCURSOR_THEME"); { const char *env_cursor_size = getenv("XCURSOR_SIZE"); if (env_cursor_size != NULL) { unsigned size; if (sscanf(env_cursor_size, "%u", &size) == 1) cursor_size = size; } } LOG_INFO("%s: cursor theme: %s, size: %u, scale: %d", seat->name, cursor_theme, cursor_size, new_scale); struct wl_cursor_theme *theme = wl_cursor_theme_load(cursor_theme, cursor_size * new_scale, seat->backend->shm); if (theme == NULL) { LOG_ERR("%s: failed to load cursor theme", seat->name); return; } seat->pointer.scale = new_scale; seat->pointer.theme = theme; } static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; struct wayland_backend *backend = seat->backend; seat->pointer.serial = serial; seat->pointer.x = wl_fixed_to_int(surface_x) * backend->scale; seat->pointer.y = wl_fixed_to_int(surface_y) * backend->scale; backend->active_seat = seat; reload_cursor_theme(seat, backend->monitor->scale); update_cursor_surface(backend, seat); } static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { struct seat *seat = data; struct wayland_backend *backend = seat->backend; backend->have_discrete = false; if (backend->active_seat == seat) backend->active_seat = NULL; } static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; struct wayland_backend *backend = seat->backend; seat->pointer.x = wl_fixed_to_int(surface_x) * backend->scale; seat->pointer.y = wl_fixed_to_int(surface_y) * backend->scale; backend->active_seat = seat; backend->bar_on_mouse(backend->bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE, seat->pointer.x, seat->pointer.y); } static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct seat *seat = data; struct wayland_backend *backend = seat->backend; if (state == WL_POINTER_BUTTON_STATE_PRESSED) backend->active_seat = seat; else { enum mouse_button btn; switch (button) { case BTN_LEFT: btn = MOUSE_BTN_LEFT; break; case BTN_MIDDLE: btn = MOUSE_BTN_MIDDLE; break; case BTN_RIGHT: btn = MOUSE_BTN_RIGHT; break; case BTN_SIDE: btn = MOUSE_BTN_PREVIOUS; break; case BTN_EXTRA: btn = MOUSE_BTN_NEXT; break; default: return; } backend->bar_on_mouse(backend->bar, ON_MOUSE_CLICK, btn, seat->pointer.x, seat->pointer.y); } } static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; struct seat *seat = data; struct wayland_backend *backend = seat->backend; struct private *bar = backend->bar->private; backend->active_seat = seat; if (backend->have_discrete) return; const double amount = wl_fixed_to_double(value); if ((backend->aggregated_scroll > 0 && amount < 0) || (backend->aggregated_scroll < 0 && amount > 0)) { backend->aggregated_scroll = amount; } else backend->aggregated_scroll += amount; enum mouse_button btn = backend->aggregated_scroll > 0 ? MOUSE_BTN_WHEEL_DOWN : MOUSE_BTN_WHEEL_UP; const double step = bar->trackpad_sensitivity; const double adjust = backend->aggregated_scroll > 0 ? -step : step; while (fabs(backend->aggregated_scroll) >= step) { backend->bar_on_mouse(backend->bar, ON_MOUSE_CLICK, btn, seat->pointer.x, seat->pointer.y); backend->aggregated_scroll += adjust; } } static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { struct seat *seat = data; struct wayland_backend *backend = seat->backend; backend->have_discrete = false; } static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { } static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; struct seat *seat = data; struct wayland_backend *backend = seat->backend; backend->aggregated_scroll = 0.; } static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; struct seat *seat = data; struct wayland_backend *backend = seat->backend; backend->have_discrete = true; enum mouse_button btn = discrete > 0 ? MOUSE_BTN_WHEEL_DOWN : MOUSE_BTN_WHEEL_UP; int count = abs(discrete); for (int32_t i = 0; i < count; i++) { backend->bar_on_mouse(backend->bar, ON_MOUSE_CLICK, btn, seat->pointer.x, seat->pointer.y); } } static const struct wl_pointer_listener pointer_listener = { .enter = wl_pointer_enter, .leave = wl_pointer_leave, .motion = wl_pointer_motion, .button = wl_pointer_button, .axis = wl_pointer_axis, .frame = wl_pointer_frame, .axis_source = wl_pointer_axis_source, .axis_stop = wl_pointer_axis_stop, .axis_discrete = wl_pointer_axis_discrete, }; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { struct seat *seat = data; if (caps & WL_SEAT_CAPABILITY_POINTER) { if (seat->wl_pointer == NULL) { assert(seat->pointer.surface == NULL); seat->pointer.surface = wl_compositor_create_surface(seat->backend->compositor); if (seat->pointer.surface == NULL) { LOG_ERR("%s: failed to create pointer surface", seat->name); return; } seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } } else { if (seat->wl_pointer != NULL) { wl_pointer_release(seat->wl_pointer); wl_surface_destroy(seat->pointer.surface); if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); seat->wl_pointer = NULL; seat->pointer.surface = NULL; seat->pointer.theme = NULL; seat->pointer.cursor = NULL; } } } static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) { struct seat *seat = data; free(seat->name); seat->name = strdup(name); } static const struct wl_seat_listener seat_listener = { .capabilities = seat_handle_capabilities, .name = seat_handle_name, }; static void output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct monitor *mon = data; mon->width_mm = physical_width; mon->height_mm = physical_height; } static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { } static bool update_size(struct wayland_backend *backend); static void refresh(const struct bar *_bar); static void output_done(void *data, struct wl_output *wl_output) { struct monitor *mon = data; if (mon->backend->monitor == mon) { int old_scale = mon->backend->scale; int old_width = mon->backend->width; update_size(mon->backend); if (mon->backend->scale != old_scale || mon->backend->width != old_width) refresh(mon->backend->bar); } } static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct monitor *mon = data; if (mon->scale == factor) return; mon->scale = factor; } #if defined(WL_OUTPUT_NAME_SINCE_VERSION) static void output_name(void *data, struct wl_output *wl_output, const char *name) { struct monitor *mon = data; free(mon->name); mon->name = name != NULL ? strdup(name) : NULL; } #endif #if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION) static void output_description(void *data, struct wl_output *wl_output, const char *description) { } #endif static const struct wl_output_listener output_listener = { .geometry = &output_geometry, .mode = &output_mode, .done = &output_done, .scale = &output_scale, #if defined(WL_OUTPUT_NAME_SINCE_VERSION) .name = &output_name, #endif #if defined(WL_OUTPUT_DESCRIPTION_SINCE_VERSION) .description = &output_description, #endif }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { struct monitor *mon = data; mon->x = x; mon->y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { struct monitor *mon = data; mon->width_px = width; mon->height_px = height; } static bool create_surface(struct wayland_backend *backend); static void destroy_surface(struct wayland_backend *backend); static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { const struct monitor *mon = data; LOG_INFO("monitor: %s: %dx%d+%d+%d (%dx%dmm)", mon->name, mon->width_px, mon->height_px, mon->x, mon->y, mon->width_mm, mon->height_mm); struct wayland_backend *backend = mon->backend; struct private *bar = backend->bar->private; const bool is_mapped = backend->monitor != NULL; if (is_mapped) { assert(backend->surface != NULL); assert(backend->last_mapped_monitor == NULL); return; } const bool output_is_our_configured_monitor = (bar->monitor != NULL && mon->name != NULL && strcmp(bar->monitor, mon->name) == 0); const bool output_is_last_mapped = (backend->last_mapped_monitor != NULL && mon->name != NULL && strcmp(backend->last_mapped_monitor, mon->name) == 0); if (output_is_our_configured_monitor) LOG_DBG("%s: using this monitor (user configured)", mon->name); else if (output_is_last_mapped) LOG_DBG("%s: using this monitor (last mapped)", mon->name); if (output_is_our_configured_monitor || output_is_last_mapped) { /* User specified a monitor, and this is one */ backend->monitor = mon; free(backend->last_mapped_monitor); backend->last_mapped_monitor = NULL; if (create_surface(backend) && update_size(backend)) { if (backend->pipe_fds[1] >= 0) refresh(backend->bar); } } } static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct monitor *mon = data; free(mon->name); mon->name = strdup(name); } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { } static struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = xdg_output_handle_done, .name = xdg_output_handle_name, .description = xdg_output_handle_description, }; static bool verify_iface_version(const char *iface, uint32_t version, uint32_t wanted) { if (version >= wanted) return true; LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version); return false; } static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { LOG_DBG("global: 0x%08x, interface=%s, version=%u", name, interface, version); struct wayland_backend *backend = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { const uint32_t required = 4; if (!verify_iface_version(interface, version, required)) return; backend->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, required); } else if (strcmp(interface, wl_shm_interface.name) == 0) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) return; backend->shm = wl_registry_bind(registry, name, &wl_shm_interface, required); wl_shm_add_listener(backend->shm, &shm_listener, backend); } else if (strcmp(interface, wl_output_interface.name) == 0) { const uint32_t required = 3; if (!verify_iface_version(interface, version, required)) return; struct wl_output *output = wl_registry_bind(registry, name, &wl_output_interface, required); tll_push_back(backend->monitors, ((struct monitor){.backend = backend, .wl_name = name, .output = output})); struct monitor *mon = &tll_back(backend->monitors); wl_output_add_listener(output, &output_listener, mon); /* * The "output" interface doesn't give us the monitors' * identifiers (e.g. "LVDS-1"). Use the XDG output interface * for that. */ assert(backend->xdg_output_manager != NULL); if (backend->xdg_output_manager != NULL) { mon->xdg = zxdg_output_manager_v1_get_xdg_output(backend->xdg_output_manager, mon->output); zxdg_output_v1_add_listener(mon->xdg, &xdg_output_listener, mon); } } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) return; backend->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, required); } else if (strcmp(interface, wl_seat_interface.name) == 0) { const uint32_t required = 5; if (!verify_iface_version(interface, version, required)) return; struct wl_seat *seat = wl_registry_bind(registry, name, &wl_seat_interface, required); assert(seat != NULL); tll_push_back(backend->seats, ((struct seat){.backend = backend, .seat = seat, .id = name})); wl_seat_add_listener(seat, &seat_listener, &tll_back(backend->seats)); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { const uint32_t required = 2; if (!verify_iface_version(interface, version, required)) return; backend->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct wayland_backend *backend = data; tll_foreach(backend->seats, it) { if (it->item.id == name) { if (backend->active_seat == &it->item) backend->active_seat = NULL; seat_destroy(&it->item); return; } } tll_foreach(backend->monitors, it) { struct monitor *mon = &it->item; if (mon->wl_name == name) { LOG_INFO("%s disconnected/disabled", mon->name); if (mon == backend->monitor) { assert(backend->last_mapped_monitor == NULL); backend->last_mapped_monitor = strdup(mon->name); backend->monitor = NULL; } tll_remove(backend->monitors, it); return; } } LOG_WARN("unknown global removed: 0x%08x", name); } static const struct wl_registry_listener registry_listener = { .global = &handle_global, .global_remove = &handle_global_remove, }; static void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t w, uint32_t h) { struct wayland_backend *backend = data; backend->width = w * backend->scale; backend->height = h * backend->scale; zwlr_layer_surface_v1_ack_configure(surface, serial); } static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) { LOG_DBG("layer surface closed by compositor"); struct wayland_backend *backend = data; destroy_surface(backend); } static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = &layer_surface_configure, .closed = &layer_surface_closed, }; static const struct wl_surface_listener surface_listener; static bool create_surface(struct wayland_backend *backend) { assert(tll_length(backend->monitors) > 0); assert(backend->surface == NULL); assert(backend->layer_surface == NULL); struct bar *_bar = backend->bar; struct private *bar = _bar->private; backend->surface = wl_compositor_create_surface(backend->compositor); if (backend->surface == NULL) { LOG_ERR("failed to create panel surface"); return false; } wl_surface_add_listener(backend->surface, &surface_listener, backend); enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; switch (bar->layer) { case BAR_LAYER_BACKGROUND: layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; break; case BAR_LAYER_BOTTOM: layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; break; case BAR_LAYER_TOP: layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; break; case BAR_LAYER_OVERLAY: layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; break; } backend->layer_surface = zwlr_layer_shell_v1_get_layer_surface( backend->layer_shell, backend->surface, backend->monitor != NULL ? backend->monitor->output : NULL, layer, "panel"); if (backend->layer_surface == NULL) { LOG_ERR("failed to create layer shell surface"); return false; } zwlr_layer_surface_v1_add_listener(backend->layer_surface, &layer_surface_listener, backend); /* Aligned to top, maximum width */ enum zwlr_layer_surface_v1_anchor top_or_bottom = bar->location == BAR_TOP ? ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP : ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; zwlr_layer_surface_v1_set_anchor(backend->layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | top_or_bottom); return true; } static void destroy_surface(struct wayland_backend *backend) { if (backend->layer_surface != NULL) zwlr_layer_surface_v1_destroy(backend->layer_surface); if (backend->surface != NULL) wl_surface_destroy(backend->surface); if (backend->frame_callback != NULL) wl_callback_destroy(backend->frame_callback); if (backend->pending_buffer != NULL) backend->pending_buffer->busy = false; if (backend->next_buffer != NULL) backend->next_buffer->busy = false; backend->layer_surface = NULL; backend->surface = NULL; backend->frame_callback = NULL; backend->pending_buffer = NULL; backend->next_buffer = NULL; backend->scale = 0; backend->render_scheduled = false; } static void buffer_release(void *data, struct wl_buffer *wl_buffer) { // printf("buffer release\n"); struct buffer *buffer = data; assert(buffer->busy); buffer->busy = false; } static const struct wl_buffer_listener buffer_listener = { .release = &buffer_release, }; static struct buffer * get_buffer(struct wayland_backend *backend) { tll_foreach(backend->buffers, it) { if (!it->item.busy && it->item.width == backend->width && it->item.height == backend->height) { it->item.busy = true; return &it->item; } } /* * No existing buffer available. Create a new one by: * * 1. open a memory backed "file" with memfd_create() * 2. mmap() the memory file, to be used by the pixman image * 3. create a wayland shm buffer for the same memory file * * The pixman image and the wayland buffer are now sharing memory. */ int pool_fd = -1; void *mmapped = NULL; size_t size = 0; struct wl_shm_pool *pool = NULL; struct wl_buffer *buf = NULL; pixman_image_t *pix = NULL; /* Backing memory for SHM */ #if defined(MEMFD_CREATE) /* * Older kernels reject MFD_NOEXEC_SEAL with EINVAL. Try first * *with* it, and if that fails, try again *without* it. */ errno = 0; pool_fd = memfd_create("yambar-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL); if (pool_fd < 0 && errno == EINVAL) { pool_fd = memfd_create("yambar-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING); } #elif defined(__FreeBSD__) // memfd_create on FreeBSD 13 is SHM_ANON without sealing support pool_fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600); #else char name[] = "/tmp/yambar-wayland-shm-buffer-pool-XXXXXX"; pool_fd = mkostemp(name, O_CLOEXEC); unlink(name); #endif if (pool_fd == -1) { LOG_ERRNO("failed to create SHM backing memory file"); goto err; } /* Total size */ const uint32_t stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, backend->width); size = stride * backend->height; if (ftruncate(pool_fd, size) == -1) { LOG_ERR("failed to truncate SHM pool"); goto err; } mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, pool_fd, 0); if (mmapped == MAP_FAILED) { LOG_ERR("failed to mmap SHM backing memory file"); goto err; } #if defined(MEMFD_CREATE) /* Seal file - we no longer allow any kind of resizing */ /* TODO: wayland mmaps(PROT_WRITE), for some unknown reason, hence we cannot use F_SEAL_FUTURE_WRITE */ if (fcntl(pool_fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK | /*F_SEAL_FUTURE_WRITE |*/ F_SEAL_SEAL) < 0) { LOG_ERRNO("failed to seal SHM backing memory file"); /* This is not a fatal error */ } #endif pool = wl_shm_create_pool(backend->shm, pool_fd, size); if (pool == NULL) { LOG_ERR("failed to create SHM pool"); goto err; } buf = wl_shm_pool_create_buffer(pool, 0, backend->width, backend->height, stride, WL_SHM_FORMAT_ARGB8888); if (buf == NULL) { LOG_ERR("failed to create SHM buffer"); goto err; } /* We use the entire pool for our single buffer */ wl_shm_pool_destroy(pool); pool = NULL; close(pool_fd); pool_fd = -1; pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, backend->width, backend->height, (uint32_t *)mmapped, stride); if (pix == NULL) { LOG_ERR("failed to create pixman image"); goto err; } /* Push to list of available buffers, but marked as 'busy' */ tll_push_back(backend->buffers, ((struct buffer){ .busy = true, .width = backend->width, .height = backend->height, .size = size, .mmapped = mmapped, .wl_buf = buf, .pix = pix, })); struct buffer *ret = &tll_back(backend->buffers); wl_buffer_add_listener(ret->wl_buf, &buffer_listener, ret); return ret; err: if (pix != NULL) pixman_image_unref(pix); if (buf != NULL) wl_buffer_destroy(buf); if (pool != NULL) wl_shm_pool_destroy(pool); if (pool_fd != -1) close(pool_fd); if (mmapped != NULL) munmap(mmapped, size); return NULL; } static int guess_scale(const struct wayland_backend *backend) { if (tll_length(backend->monitors) == 0) return 1; bool all_have_same_scale = true; int last_scale = -1; tll_foreach(backend->monitors, it) { if (last_scale == -1) last_scale = it->item.scale; else if (last_scale != it->item.scale) { all_have_same_scale = false; break; } } if (all_have_same_scale) { assert(last_scale >= 1); return last_scale; } return 1; } static bool update_size(struct wayland_backend *backend) { struct bar *_bar = backend->bar; struct private *bar = _bar->private; const struct monitor *mon = backend->monitor; const int scale = mon != NULL ? mon->scale : guess_scale(backend); assert(backend->surface != NULL); backend->scale = scale; int height = bar->height_with_border; height /= scale; height *= scale; bar->height = height - bar->border.top_width - bar->border.bottom_width; bar->height_with_border = height; zwlr_layer_surface_v1_set_size(backend->layer_surface, 0, bar->height_with_border / scale); zwlr_layer_surface_v1_set_exclusive_zone( backend->layer_surface, (bar->height_with_border + (bar->location == BAR_TOP ? bar->border.bottom_margin : bar->border.top_margin)) / scale); zwlr_layer_surface_v1_set_margin(backend->layer_surface, bar->border.top_margin / scale, bar->border.right_margin / scale, bar->border.bottom_margin / scale, bar->border.left_margin / scale); /* Trigger a 'configure' event, after which we'll have the width */ wl_surface_commit(backend->surface); wl_display_roundtrip(backend->display); if (backend->width == -1 || backend->height != bar->height_with_border) { LOG_ERR("failed to get panel width"); return false; } bar->width = backend->width; /* Reload buffers */ if (backend->next_buffer != NULL) backend->next_buffer->busy = false; backend->next_buffer = get_buffer(backend); assert(backend->next_buffer != NULL && backend->next_buffer->busy); bar->pix = backend->next_buffer->pix; return true; } static bool setup(struct bar *_bar) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; backend->bar = _bar; backend->display = wl_display_connect(NULL); if (backend->display == NULL) { LOG_ERR("failed to connect to wayland; no compositor running?"); return false; } backend->registry = wl_display_get_registry(backend->display); if (backend->registry == NULL) { LOG_ERR("failed to get wayland registry"); return false; } /* Globals */ wl_registry_add_listener(backend->registry, ®istry_listener, backend); wl_display_roundtrip(backend->display); if (backend->compositor == NULL) { LOG_ERR("no compositor"); return false; } if (backend->layer_shell == NULL) { LOG_ERR("no layer shell interface"); return false; } if (backend->shm == NULL) { LOG_ERR("no shared memory buffers interface"); return false; } if (tll_length(backend->monitors) == 0) { LOG_ERR("no monitors"); return false; } /* Trigger listeners registered in previous roundtrip */ wl_display_roundtrip(backend->display); if (backend->surface == NULL && backend->layer_surface == NULL) { if (!create_surface(backend)) return false; if (!update_size(backend)) return false; } assert(backend->monitor == NULL || backend->width / backend->monitor->scale <= backend->monitor->width_px); if (pipe2(backend->pipe_fds, O_CLOEXEC | O_NONBLOCK) == -1) { LOG_ERRNO("failed to create pipe"); return false; } backend->render_scheduled = false; return true; } static void cleanup(struct bar *_bar) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; if (backend->pipe_fds[0] >= 0) close(backend->pipe_fds[0]); if (backend->pipe_fds[1] >= 0) close(backend->pipe_fds[1]); tll_foreach(backend->monitors, it) { struct monitor *mon = &it->item; free(mon->name); if (mon->xdg != NULL) zxdg_output_v1_destroy(mon->xdg); if (mon->output != NULL) wl_output_release(mon->output); tll_remove(backend->monitors, it); } free(backend->last_mapped_monitor); if (backend->xdg_output_manager != NULL) zxdg_output_manager_v1_destroy(backend->xdg_output_manager); tll_foreach(backend->seats, it) seat_destroy(&it->item); tll_free(backend->seats); destroy_surface(backend); tll_foreach(backend->buffers, it) { if (it->item.wl_buf != NULL) wl_buffer_destroy(it->item.wl_buf); if (it->item.pix != NULL) pixman_image_unref(it->item.pix); munmap(it->item.mmapped, it->item.size); tll_remove(backend->buffers, it); } if (backend->layer_shell != NULL) zwlr_layer_shell_v1_destroy(backend->layer_shell); if (backend->compositor != NULL) wl_compositor_destroy(backend->compositor); if (backend->shm != NULL) wl_shm_destroy(backend->shm); if (backend->registry != NULL) wl_registry_destroy(backend->registry); if (backend->display != NULL) { wl_display_flush(backend->display); wl_display_disconnect(backend->display); } /* Destroyed when freeing buffer list */ bar->pix = NULL; } static void loop(struct bar *_bar, void (*expose)(const struct bar *bar), void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y)) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; bool send_abort_to_modules = true; pthread_setname_np(pthread_self(), "bar(wayland)"); backend->bar_on_mouse = on_mouse; while (wl_display_prepare_read(backend->display) != 0) { if (wl_display_dispatch_pending(backend->display) < 0) { LOG_ERRNO("failed to dispatch pending Wayland events"); goto out; } } wl_display_flush(backend->display); while (true) { struct pollfd fds[] = { {.fd = _bar->abort_fd, .events = POLLIN}, {.fd = wl_display_get_fd(backend->display), .events = POLLIN}, {.fd = backend->pipe_fds[0], .events = POLLIN}, }; poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (fds[0].revents & POLLIN) { /* Already done by the bar */ send_abort_to_modules = false; break; } if (fds[1].revents & POLLHUP) { LOG_INFO("disconnected from wayland"); break; } if (fds[2].revents & POLLIN) { bool do_expose = false; /* Coalesce “refresh” commands */ size_t count = 0; while (true) { uint8_t command; ssize_t r = read(backend->pipe_fds[0], &command, sizeof(command)); if (r < 0 && errno == EAGAIN) break; if (r != sizeof(command)) { LOG_ERRNO("failed to read from command pipe"); goto out; } assert(command == 1); if (command == 1) { count++; do_expose = true; } } LOG_DBG("coalesced %zu expose commands", count); if (do_expose) expose(_bar); } if (fds[1].revents & POLLIN) { if (wl_display_read_events(backend->display) < 0) { LOG_ERRNO("failed to read events from the Wayland socket"); goto out; } while (wl_display_prepare_read(backend->display) != 0) { if (wl_display_dispatch_pending(backend->display) < 0) { LOG_ERRNO("failed to dispatch pending Wayland events"); goto out; } } wl_display_flush(backend->display); } } out: if (!send_abort_to_modules) return; if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) { LOG_ERRNO("failed to signal abort to modules"); } // wl_display_cancel_read(backend->display); } static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct wayland_backend *backend = data; free(backend->last_mapped_monitor); backend->last_mapped_monitor = NULL; tll_foreach(backend->monitors, it) { struct monitor *mon = &it->item; if (mon->output != wl_output) continue; if (backend->monitor != mon) { backend->monitor = mon; int old_scale = backend->scale; update_size(backend); if (backend->scale != old_scale) refresh(backend->bar); } break; } } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct wayland_backend *backend = data; const struct monitor *mon = backend->monitor; assert(mon != NULL); assert(mon->output == wl_output); backend->monitor = NULL; assert(backend->last_mapped_monitor == NULL); backend->last_mapped_monitor = mon->name != NULL ? strdup(mon->name) : NULL; } static const struct wl_surface_listener surface_listener = { .enter = &surface_enter, .leave = &surface_leave, }; static void frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data); static const struct wl_callback_listener frame_listener = { .done = &frame_callback, }; static void frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data) { // printf("frame callback\n"); struct private *bar = data; struct wayland_backend *backend = bar->backend.data; backend->render_scheduled = false; assert(wl_callback == backend->frame_callback); wl_callback_destroy(wl_callback); backend->frame_callback = NULL; if (backend->pending_buffer != NULL) { struct buffer *buffer = backend->pending_buffer; assert(buffer->busy); wl_surface_set_buffer_scale(backend->surface, backend->scale); wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0); wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height); struct wl_callback *cb = wl_surface_frame(backend->surface); wl_callback_add_listener(cb, &frame_listener, bar); wl_surface_commit(backend->surface); wl_display_flush(backend->display); backend->frame_callback = cb; backend->pending_buffer = NULL; backend->render_scheduled = true; } else ; // printf("nothing more to do\n"); } static void commit(const struct bar *_bar) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; // printf("commit: %dxl%d\n", backend->width, backend->height); if (backend->next_buffer == NULL) return; assert(backend->next_buffer != NULL); assert(backend->next_buffer->busy); if (backend->render_scheduled) { // printf("already scheduled\n"); if (backend->pending_buffer != NULL) backend->pending_buffer->busy = false; backend->pending_buffer = backend->next_buffer; backend->next_buffer = NULL; } else { // printf("scheduling new frame callback\n"); struct buffer *buffer = backend->next_buffer; assert(buffer->busy); wl_surface_set_buffer_scale(backend->surface, backend->scale); wl_surface_attach(backend->surface, buffer->wl_buf, 0, 0); wl_surface_damage(backend->surface, 0, 0, backend->width, backend->height); struct wl_callback *cb = wl_surface_frame(backend->surface); wl_callback_add_listener(cb, &frame_listener, bar); wl_surface_commit(backend->surface); wl_display_flush(backend->display); backend->render_scheduled = true; backend->frame_callback = cb; } backend->next_buffer = get_buffer(backend); assert(backend->next_buffer != NULL && backend->next_buffer->busy); bar->pix = backend->next_buffer->pix; } static void refresh(const struct bar *_bar) { const struct private *bar = _bar->private; const struct wayland_backend *backend = bar->backend.data; if (write(backend->pipe_fds[1], &(uint8_t){1}, sizeof(uint8_t)) != sizeof(uint8_t)) { LOG_ERRNO("failed to signal 'refresh' to main thread"); } } static void set_cursor(struct bar *_bar, const char *cursor) { struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; struct seat *seat = backend->active_seat; if (seat == NULL || seat->pointer.theme == NULL) return; if (seat->pointer.xcursor != NULL && strcmp(seat->pointer.xcursor, cursor) == 0) return; seat->pointer.xcursor = cursor; seat->pointer.cursor = wl_cursor_theme_get_cursor(seat->pointer.theme, cursor); if (seat->pointer.cursor == NULL) { LOG_ERR("%s: failed to load cursor '%s'", seat->name, cursor); return; } update_cursor_surface(backend, seat); } static const char * bar_output_name(const struct bar *_bar) { const struct private *bar = _bar->private; const struct wayland_backend *backend = bar->backend.data; return backend->monitor != NULL ? backend->monitor->name : NULL; } const struct backend wayland_backend_iface = { .setup = &setup, .cleanup = &cleanup, .loop = &loop, .commit = &commit, .refresh = &refresh, .set_cursor = &set_cursor, .output_name = &bar_output_name, }; yambar-1.11.0/bar/wayland.h000066400000000000000000000001751460770427600154410ustar00rootroot00000000000000#pragma once #include "backend.h" extern const struct backend wayland_backend_iface; void *bar_backend_wayland_new(void); yambar-1.11.0/bar/xcb.c000066400000000000000000000321631460770427600145530ustar00rootroot00000000000000#include "xcb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "private.h" #define LOG_MODULE "bar:xcb" #include "../log.h" #include "../stride.h" #include "../xcb.h" struct xcb_backend { int x, y; xcb_connection_t *conn; xcb_window_t win; xcb_colormap_t colormap; xcb_gc_t gc; xcb_cursor_context_t *cursor_ctx; xcb_cursor_t cursor; const char *xcursor; uint8_t depth; void *client_pixmap; size_t client_pixmap_size; pixman_image_t *pix; }; void * bar_backend_xcb_new(void) { xcb_init(); return calloc(1, sizeof(struct xcb_backend)); } static bool setup(struct bar *_bar) { struct private *bar = _bar->private; struct xcb_backend *backend = bar->backend.data; if (bar->border.left_margin != 0 || bar->border.right_margin != 0 || bar->border.top_margin != 0 || bar->border.bottom_margin) { LOG_WARN("non-zero border margins ignored in X11 backend"); } /* TODO: a lot of this (up to mapping the window) could be done in bar_new() */ xcb_generic_error_t *e; int default_screen; backend->conn = xcb_connect(NULL, &default_screen); if (xcb_connection_has_error(backend->conn) > 0) { LOG_ERR("failed to connect to X"); xcb_disconnect(backend->conn); return false; } xcb_screen_t *screen = xcb_aux_get_screen(backend->conn, default_screen); xcb_randr_get_monitors_reply_t *monitors = xcb_randr_get_monitors_reply(backend->conn, xcb_randr_get_monitors(backend->conn, screen->root, 0), &e); if (e != NULL) { LOG_ERR("failed to get monitor list: %s", xcb_error(e)); free(e); /* TODO: cleanup (disconnect) */ return false; } /* Find monitor coordinates and width/height */ bool found_monitor = false; for (xcb_randr_monitor_info_iterator_t it = xcb_randr_get_monitors_monitors_iterator(monitors); it.rem > 0; xcb_randr_monitor_info_next(&it)) { const xcb_randr_monitor_info_t *mon = it.data; char *name = get_atom_name(backend->conn, mon->name); LOG_INFO("monitor: %s: %ux%u+%u+%u (%ux%umm)", name, mon->width, mon->height, mon->x, mon->y, mon->width_in_millimeters, mon->height_in_millimeters); /* User wants a specific monitor, and this is not the one */ if (bar->monitor != NULL && strcmp(bar->monitor, name) != 0) { free(name); continue; } backend->x = mon->x; backend->y = mon->y; bar->width = mon->width; backend->y += bar->location == BAR_TOP ? 0 : screen->height_in_pixels - bar->height_with_border; found_monitor = true; if ((bar->monitor != NULL && strcmp(bar->monitor, name) == 0) || (bar->monitor == NULL && mon->primary)) { /* Exact match */ free(name); break; } free(name); } free(monitors); if (!found_monitor) { if (bar->monitor == NULL) LOG_ERR("no monitors found"); else LOG_ERR("no monitor '%s'", bar->monitor); /* TODO: cleanup */ return false; } uint8_t depth = 0; xcb_visualtype_t *vis = xcb_aux_find_visual_by_attrs(screen, -1, 32); if (vis != NULL) depth = 32; else { vis = xcb_aux_find_visual_by_attrs(screen, -1, 24); if (vis != NULL) depth = 24; } assert(depth == 32 || depth == 24); assert(vis != NULL); backend->depth = depth; LOG_DBG("using a %hhu-bit visual", depth); backend->colormap = xcb_generate_id(backend->conn); xcb_create_colormap(backend->conn, 0, backend->colormap, screen->root, vis->visual_id); backend->win = xcb_generate_id(backend->conn); xcb_create_window( backend->conn, depth, backend->win, screen->root, backend->x, backend->y, bar->width, bar->height_with_border, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, vis->visual_id, (XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP), (const uint32_t[]){screen->black_pixel, screen->white_pixel, (XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_STRUCTURE_NOTIFY), backend->colormap}); const char *title = "yambar"; xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen(title), title); xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t[]){getpid()}); xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 32, 1, (const uint32_t[]){_NET_WM_WINDOW_TYPE_DOCK}); xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STATE, XCB_ATOM_ATOM, 32, 2, (const uint32_t[]){_NET_WM_STATE_ABOVE, _NET_WM_STATE_STICKY}); xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, (const uint32_t[]){0xffffffff}); /* Always on top */ xcb_configure_window(backend->conn, backend->win, XCB_CONFIG_WINDOW_STACK_MODE, (const uint32_t[]){XCB_STACK_MODE_ABOVE}); uint32_t top_strut, bottom_strut; uint32_t top_pair[2], bottom_pair[2]; if (bar->location == BAR_TOP) { top_strut = bar->height_with_border; top_pair[0] = backend->x; top_pair[1] = backend->x + bar->width - 1; bottom_strut = 0; bottom_pair[0] = bottom_pair[1] = 0; } else { bottom_strut = bar->height_with_border; bottom_pair[0] = backend->x; bottom_pair[1] = backend->x + bar->width - 1; top_strut = 0; top_pair[0] = top_pair[1] = 0; } uint32_t strut[] = { /* left/right/top/bottom */ 0, 0, top_strut, bottom_strut, /* start/end pairs for left/right/top/bottom */ 0, 0, 0, 0, top_pair[0], top_pair[1], bottom_pair[0], bottom_pair[1], }; xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4, strut); xcb_change_property(backend->conn, XCB_PROP_MODE_REPLACE, backend->win, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, 12, strut); backend->gc = xcb_generate_id(backend->conn); xcb_create_gc(backend->conn, backend->gc, backend->win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES, (const uint32_t[]){screen->white_pixel, 0}); const uint32_t stride = stride_for_format_and_width(PIXMAN_a8r8g8b8, bar->width); backend->client_pixmap_size = stride * bar->height_with_border; backend->client_pixmap = malloc(backend->client_pixmap_size); backend->pix = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, bar->width, bar->height_with_border, (uint32_t *)backend->client_pixmap, stride); bar->pix = backend->pix; xcb_map_window(backend->conn, backend->win); if (xcb_cursor_context_new(backend->conn, screen, &backend->cursor_ctx) < 0) LOG_WARN("failed to create XCB cursor context"); xcb_flush(backend->conn); return true; } static void cleanup(struct bar *_bar) { struct private *bar = _bar->private; struct xcb_backend *backend = bar->backend.data; if (backend->conn == NULL) return; if (backend->cursor != 0) xcb_free_cursor(backend->conn, backend->cursor); if (backend->cursor_ctx != NULL) xcb_cursor_context_free(backend->cursor_ctx); if (backend->pix != NULL) pixman_image_unref(backend->pix); free(backend->client_pixmap); if (backend->gc != 0) xcb_free_gc(backend->conn, backend->gc); if (backend->win != 0) xcb_destroy_window(backend->conn, backend->win); if (backend->colormap != 0) xcb_free_colormap(backend->conn, backend->colormap); xcb_flush(backend->conn); xcb_disconnect(backend->conn); backend->conn = NULL; } static void loop(struct bar *_bar, void (*expose)(const struct bar *bar), void (*on_mouse)(struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y)) { struct private *bar = _bar->private; struct xcb_backend *backend = bar->backend.data; pthread_setname_np(pthread_self(), "bar(xcb)"); const int fd = xcb_get_file_descriptor(backend->conn); while (true) { struct pollfd fds[] = {{.fd = _bar->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}}; poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (fds[0].revents && POLLIN) break; if (fds[1].revents & POLLHUP) { LOG_WARN("disconnected from XCB"); if (write(_bar->abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) { LOG_ERRNO("failed to signal abort to modules"); } break; } for (xcb_generic_event_t *e = xcb_wait_for_event(backend->conn); e != NULL; e = xcb_poll_for_event(backend->conn)) { switch (XCB_EVENT_RESPONSE_TYPE(e)) { case 0: LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)e)); break; case XCB_EXPOSE: expose(_bar); break; case XCB_MOTION_NOTIFY: { const xcb_motion_notify_event_t *evt = (void *)e; on_mouse(_bar, ON_MOUSE_MOTION, MOUSE_BTN_NONE, evt->event_x, evt->event_y); break; } case XCB_BUTTON_PRESS: break; case XCB_BUTTON_RELEASE: { const xcb_button_release_event_t *evt = (void *)e; switch (evt->detail) { case 1: case 2: case 3: case 4: case 5: on_mouse(_bar, ON_MOUSE_CLICK, evt->detail, evt->event_x, evt->event_y); break; } break; } case XCB_DESTROY_NOTIFY: LOG_WARN("unimplemented event: XCB_DESTROY_NOTIFY"); break; case XCB_REPARENT_NOTIFY: case XCB_CONFIGURE_NOTIFY: case XCB_MAP_NOTIFY: case XCB_MAPPING_NOTIFY: /* Just ignore */ break; default: LOG_ERR("unsupported event: %d", XCB_EVENT_RESPONSE_TYPE(e)); break; } free(e); xcb_flush(backend->conn); } } } static void commit(const struct bar *_bar) { const struct private *bar = _bar->private; const struct xcb_backend *backend = bar->backend.data; xcb_put_image(backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, backend->win, backend->gc, bar->width, bar->height_with_border, 0, 0, 0, backend->depth, backend->client_pixmap_size, backend->client_pixmap); xcb_flush(backend->conn); } static void refresh(const struct bar *_bar) { const struct private *bar = _bar->private; const struct xcb_backend *backend = bar->backend.data; /* Send an event to handle refresh from main thread */ /* Note: docs say that all X11 events are 32 bytes, reglardless of * the size of the event structure */ xcb_expose_event_t *evt = calloc(32, 1); *evt = (xcb_expose_event_t){.response_type = XCB_EXPOSE, .window = backend->win, .x = 0, .y = 0, .width = bar->width, .height = bar->height, .count = 1}; xcb_send_event(backend->conn, false, backend->win, XCB_EVENT_MASK_EXPOSURE, (char *)evt); xcb_flush(backend->conn); free(evt); } static void set_cursor(struct bar *_bar, const char *cursor) { struct private *bar = _bar->private; struct xcb_backend *backend = bar->backend.data; if (backend->cursor_ctx == NULL) return; if (backend->xcursor != NULL && strcmp(backend->xcursor, cursor) == 0) return; if (backend->cursor != 0) xcb_free_cursor(backend->conn, backend->cursor); backend->cursor = xcb_cursor_load_cursor(backend->cursor_ctx, cursor); xcb_change_window_attributes(backend->conn, backend->win, XCB_CW_CURSOR, &backend->cursor); } static const char * output_name(const struct bar *_bar) { /* Not implemented */ return NULL; } const struct backend xcb_backend_iface = { .setup = &setup, .cleanup = &cleanup, .loop = &loop, .commit = &commit, .refresh = &refresh, .set_cursor = &set_cursor, .output_name = &output_name, }; yambar-1.11.0/bar/xcb.h000066400000000000000000000001651460770427600145550ustar00rootroot00000000000000#pragma once #include "backend.h" extern const struct backend xcb_backend_iface; void *bar_backend_xcb_new(void); yambar-1.11.0/char32.c000066400000000000000000000031741460770427600143150ustar00rootroot00000000000000#include "char32.h" #include #include #include #include #if defined __has_include #if __has_include() #include #endif #endif #define LOG_MODULE "char32" #define LOG_ENABLE_DBG 0 #include "log.h" /* * For now, assume we can map directly to the corresponding wchar_t * functions. This is true if: * * - both data types have the same size * - both use the same encoding (though we require that encoding to be UTF-32) */ _Static_assert(sizeof(wchar_t) == sizeof(char32_t), "wchar_t vs. char32_t size mismatch"); #if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__ #error "char32_t does not use UTF-32" #endif #if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) #error "wchar_t does not use UTF-32" #endif size_t c32len(const char32_t *s) { return wcslen((const wchar_t *)s); } char32_t * ambstoc32(const char *src) { if (src == NULL) return NULL; const size_t src_len = strlen(src); char32_t *ret = malloc((src_len + 1) * sizeof(ret[0])); if (ret == NULL) return NULL; mbstate_t ps = {0}; char32_t *out = ret; const char *in = src; const char *const end = src + src_len + 1; size_t chars = 0; size_t rc; while ((rc = mbrtoc32(out, in, end - in, &ps)) != 0) { switch (rc) { case (size_t)-1: case (size_t)-2: case (size_t)-3: goto err; } in += rc; out++; chars++; } *out = U'\0'; ret = realloc(ret, (chars + 1) * sizeof(ret[0])); return ret; err: free(ret); return NULL; } yambar-1.11.0/char32.h000066400000000000000000000001761460770427600143210ustar00rootroot00000000000000#pragma once #include #include size_t c32len(const char32_t *s); char32_t *ambstoc32(const char *src); yambar-1.11.0/color.h000066400000000000000000000001441460770427600143500ustar00rootroot00000000000000#pragma once struct rgba { double red; double green; double blue; double alpha; }; yambar-1.11.0/completions/000077500000000000000000000000001460770427600154165ustar00rootroot00000000000000yambar-1.11.0/completions/meson.build000066400000000000000000000002041460770427600175540ustar00rootroot00000000000000zsh_install_dir = join_paths(get_option('datadir'), 'zsh/site-functions') install_data('zsh/_yambar', install_dir: zsh_install_dir) yambar-1.11.0/completions/zsh/000077500000000000000000000000001460770427600162225ustar00rootroot00000000000000yambar-1.11.0/completions/zsh/_yambar000066400000000000000000000015131460770427600175570ustar00rootroot00000000000000#compdef yambar _arguments \ -s \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ '(-h --help)'{-h,--help}'[show help message and quit]' \ '(-b --backend)'{-b,--backend}'[backend to use (default: auto)]:backend:(xcb wayland auto)' \ '(-c --config)'{-c,--config}'[alternative configuration file]:filename:_files' \ '(-C --validate)'{-C,--validate}'[verify configuration then quit]' \ '(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running]:pidfile:_files' \ '(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-s --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging]' yambar-1.11.0/config-verify.c000066400000000000000000000320321460770427600157750ustar00rootroot00000000000000#include "config.h" #include #include #include #define LOG_MODULE "config:verify" #define LOG_ENABLE_DBG 0 #include "log.h" #include "plugin.h" const char * conf_err_prefix(const keychain_t *chain, const struct yml_node *node) { static char msg[4096]; int idx = 0; idx += snprintf(&msg[idx], sizeof(msg) - idx, "%zu:%zu: ", yml_source_line(node), yml_source_column(node)); tll_foreach(*chain, key) idx += snprintf(&msg[idx], sizeof(msg) - idx, "%s.", key->item); /* Remove trailing "." */ msg[idx - 1] = '\0'; return msg; } bool conf_verify_string(keychain_t *chain, const struct yml_node *node) { const char *s = yml_value_as_string(node); if (s == NULL) { LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } return true; } bool conf_verify_int(keychain_t *chain, const struct yml_node *node) { if (yml_value_is_int(node)) return true; LOG_ERR("%s: value is not an integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node)); return false; } bool conf_verify_unsigned(keychain_t *chain, const struct yml_node *node) { if (yml_value_is_int(node) && yml_value_as_int(node) >= 0) return true; LOG_ERR("%s: value is not a positive integer: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node)); return false; } bool conf_verify_bool(keychain_t *chain, const struct yml_node *node) { if (yml_value_is_bool(node)) return true; LOG_ERR("%s: value is not a boolean: '%s'", conf_err_prefix(chain, node), yml_value_as_string(node)); return false; } bool conf_verify_list(keychain_t *chain, const struct yml_node *node, bool (*verify)(keychain_t *chain, const struct yml_node *node)) { if (!yml_is_list(node)) { LOG_ERR("%s: value is not a list", conf_err_prefix(chain, node)); return false; } for (struct yml_list_iter iter = yml_list_iter(node); iter.node != NULL; yml_list_next(&iter)) { if (!verify(chain, iter.node)) return false; } return true; } bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count) { const char *s = yml_value_as_string(node); if (s == NULL) { LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } for (size_t i = 0; s != NULL && i < count; i++) { if (strcmp(s, values[i]) == 0) return true; } LOG_ERR("%s: value must be one of:", conf_err_prefix(chain, node)); for (size_t i = 0; i < count; i++) LOG_ERR(" %s", values[i]); return false; } bool conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct attr_info info[]) { if (!yml_is_dict(node)) { LOG_ERR("%s: must be a dictionary", conf_err_prefix(chain, node)); return false; } /* Count the attributes */ size_t count = 0; for (; info[count].name != NULL; count++) ; bool exists[count]; memset(exists, 0, sizeof(exists)); for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) { const char *key = yml_value_as_string(it.key); if (key == NULL) { LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key)); return false; } const struct attr_info *attr = NULL; for (size_t i = 0; i < count; i++) { if (strcmp(info[i].name, key) == 0) { attr = &info[i]; exists[i] = true; break; } } if (attr == NULL) { LOG_ERR("%s: invalid key: %s", conf_err_prefix(chain, it.key), key); return false; } if (attr->verify == NULL) continue; if (!attr->verify(chain_push(chain, key), it.value)) return false; chain_pop(chain); } for (size_t i = 0; i < count; i++) { if (!info[i].required || exists[i]) continue; LOG_ERR("%s: missing required key: %s", conf_err_prefix(chain, node), info[i].name); return false; } return true; } static bool verify_on_click_path(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_string(chain, node)) return false; #if 1 /* We allow non-absolute paths in on-click handlers */ return true; #else const char *path = yml_value_as_string(node); const bool is_absolute = path[0] == '/'; const bool is_tilde = path[0] == '~' && path[1] == '/'; if (!is_absolute && !is_tilde) { LOG_ERR("%s: path must be either absolute, or begin with '~/", conf_err_prefix(chain, node)); return false; } return true; #endif } bool conf_verify_on_click(keychain_t *chain, const struct yml_node *node) { /* on-click: */ const char *s = yml_value_as_string(node); if (s != NULL) return verify_on_click_path(chain, node); static const struct attr_info info[] = { {"left", false, &verify_on_click_path}, {"middle", false, &verify_on_click_path}, {"right", false, &verify_on_click_path}, {"wheel-up", false, &verify_on_click_path}, {"wheel-down", false, &verify_on_click_path}, {"previous", false, &verify_on_click_path}, {"next", false, &verify_on_click_path}, {NULL, false, NULL}, }; return conf_verify_dict(chain, node, info); } bool conf_verify_color(keychain_t *chain, const struct yml_node *node) { const char *s = yml_value_as_string(node); if (s == NULL) { LOG_ERR("%s: value must be a string", conf_err_prefix(chain, node)); return false; } unsigned int r, g, b, a; int v = sscanf(s, "%02x%02x%02x%02x", &r, &g, &b, &a); if (strlen(s) != 8 || v != 4) { LOG_ERR("%s: value must be a color ('rrggbbaa', e.g ff00ffff)", conf_err_prefix(chain, node)); return false; } return true; } bool conf_verify_font(keychain_t *chain, const struct yml_node *node) { if (!yml_is_scalar(node)) { LOG_ERR("%s: font must be a fontconfig-formatted string", conf_err_prefix(chain, node)); return false; } return true; } bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node) { return conf_verify_enum(chain, node, (const char *[]){"full", /*"graphemes",*/ "none"}, 2); } bool conf_verify_decoration(keychain_t *chain, const struct yml_node *node) { assert(yml_is_dict(node)); if (yml_dict_length(node) != 1) { LOG_ERR("%s: decoration must be a dictionary with a single key; " "the name of the particle", conf_err_prefix(chain, node)); return false; } struct yml_dict_iter p = yml_dict_iter(node); const struct yml_node *deco = p.key; const struct yml_node *values = p.value; const char *deco_name = yml_value_as_string(deco); if (deco_name == NULL) { LOG_ERR("%s: decoration name must be a string", conf_err_prefix(chain, deco)); return false; } const struct deco_iface *iface = plugin_load_deco(deco_name); if (iface == NULL) { LOG_ERR("%s: invalid decoration name: %s", conf_err_prefix(chain, deco), deco_name); return false; } chain_push(chain, deco_name); if (!iface->verify_conf(chain, values)) return false; chain_pop(chain); return true; } bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node) { assert(yml_is_list(node)); for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) { if (!conf_verify_particle(chain, it.node)) return false; } return true; } static bool conf_verify_particle_dictionary(keychain_t *chain, const struct yml_node *node) { assert(yml_is_dict(node)); if (yml_dict_length(node) != 1) { LOG_ERR("%s: particle must be a dictionary with a single key; " "the name of the particle", conf_err_prefix(chain, node)); return false; } struct yml_dict_iter p = yml_dict_iter(node); const struct yml_node *particle = p.key; const struct yml_node *values = p.value; const char *particle_name = yml_value_as_string(particle); if (particle_name == NULL) { LOG_ERR("%s: particle name must be a string", conf_err_prefix(chain, particle)); return false; } const struct particle_iface *iface = plugin_load_particle(particle_name); if (iface == NULL) { LOG_ERR("%s: invalid particle name: %s", conf_err_prefix(chain, particle), particle_name); return false; } assert(iface->verify_conf != NULL); chain_push(chain, particle_name); if (!iface->verify_conf(chain, values)) return false; chain_pop(chain); return true; } bool conf_verify_particle(keychain_t *chain, const struct yml_node *node) { if (yml_is_dict(node)) return conf_verify_particle_dictionary(chain, node); else if (yml_is_list(node)) return conf_verify_particle_list_items(chain, node); else { LOG_ERR("%s: particle must be either a dictionary or a list", conf_err_prefix(chain, node)); return false; } } static bool verify_module(keychain_t *chain, const struct yml_node *node) { if (!yml_is_dict(node) || yml_dict_length(node) != 1) { LOG_ERR("%s: module must be a dictionary with a single key; " "the name of the module", conf_err_prefix(chain, node)); return false; } struct yml_dict_iter m = yml_dict_iter(node); const struct yml_node *module = m.key; const struct yml_node *values = m.value; const char *mod_name = yml_value_as_string(module); if (mod_name == NULL) { LOG_ERR("%s: module name must be a string", conf_err_prefix(chain, module)); return false; } const struct module_iface *iface = plugin_load_module(mod_name); if (iface == NULL) { LOG_ERR("%s: invalid module name: %s", conf_err_prefix(chain, node), mod_name); return false; } assert(iface->verify_conf != NULL); chain_push(chain, mod_name); if (!iface->verify_conf(chain, values)) return false; chain_pop(chain); return true; } static bool verify_module_list(keychain_t *chain, const struct yml_node *node) { if (!yml_is_list(node)) { LOG_ERR("%s: must be a list of modules", conf_err_prefix(chain, node)); return false; } for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) { if (!verify_module(chain, it.node)) return false; } return true; } static bool verify_bar_border(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"width", false, &conf_verify_unsigned}, {"left-width", false, &conf_verify_unsigned}, {"right-width", false, &conf_verify_unsigned}, {"top-width", false, &conf_verify_unsigned}, {"bottom-width", false, &conf_verify_unsigned}, {"color", false, &conf_verify_color}, {"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned}, {"right-margin", false, &conf_verify_unsigned}, {"top-margin", false, &conf_verify_unsigned}, {"bottom-margin", false, &conf_verify_unsigned}, {NULL, false, NULL}, }; return conf_verify_dict(chain, node, attrs); } static bool verify_bar_location(keychain_t *chain, const struct yml_node *node) { return conf_verify_enum(chain, node, (const char *[]){"top", "bottom"}, 2); } static bool verify_bar_layer(keychain_t *chain, const struct yml_node *node) { return conf_verify_enum(chain, node, (const char *[]){"overlay", "top", "bottom", "background"}, 4); } bool conf_verify_bar(const struct yml_node *bar) { if (!yml_is_dict(bar)) { LOG_ERR("bar is not a dictionary"); return false; } keychain_t chain = tll_init(); chain_push(&chain, "bar"); static const struct attr_info attrs[] = { {"height", true, &conf_verify_unsigned}, {"location", true, &verify_bar_location}, {"background", true, &conf_verify_color}, {"monitor", false, &conf_verify_string}, {"layer", false, &verify_bar_layer}, {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, {"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned}, {"right-margin", false, &conf_verify_unsigned}, {"border", false, &verify_bar_border}, {"font", false, &conf_verify_font}, {"font-shaping", false, &conf_verify_font_shaping}, {"foreground", false, &conf_verify_color}, {"left", false, &verify_module_list}, {"center", false, &verify_module_list}, {"right", false, &verify_module_list}, {"trackpad-sensitivity", false, &conf_verify_unsigned}, {NULL, false, NULL}, }; bool ret = conf_verify_dict(&chain, bar, attrs); tll_free(chain); return ret; } yambar-1.11.0/config-verify.h000066400000000000000000000033641460770427600160100ustar00rootroot00000000000000#pragma once #include #include "tllist.h" #include "yml.h" typedef tll(const char *) keychain_t; struct attr_info { const char *name; bool required; bool (*verify)(keychain_t *chain, const struct yml_node *node); }; static inline keychain_t * chain_push(keychain_t *chain, const char *key) { tll_push_back(*chain, key); return chain; } static inline void chain_pop(keychain_t *chain) { tll_pop_back(*chain); } const char *conf_err_prefix(const keychain_t *chain, const struct yml_node *node); bool conf_verify_string(keychain_t *chain, const struct yml_node *node); bool conf_verify_int(keychain_t *chain, const struct yml_node *node); bool conf_verify_unsigned(keychain_t *chain, const struct yml_node *node); bool conf_verify_bool(keychain_t *chain, const struct yml_node *node); bool conf_verify_enum(keychain_t *chain, const struct yml_node *node, const char *values[], size_t count); bool conf_verify_list(keychain_t *chain, const struct yml_node *node, bool (*verify)(keychain_t *chain, const struct yml_node *node)); bool conf_verify_dict(keychain_t *chain, const struct yml_node *node, const struct attr_info info[]); /* NULL-terminated list */ bool conf_verify_on_click(keychain_t *chain, const struct yml_node *node); bool conf_verify_color(keychain_t *chain, const struct yml_node *node); bool conf_verify_font(keychain_t *chain, const struct yml_node *node); bool conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node); bool conf_verify_particle(keychain_t *chain, const struct yml_node *node); bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node); bool conf_verify_decoration(keychain_t *chain, const struct yml_node *node); yambar-1.11.0/config.c000066400000000000000000000430451460770427600145010ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include "bar/bar.h" #include "color.h" #include "config-verify.h" #include "module.h" #include "plugin.h" #define LOG_MODULE "config" #define LOG_ENABLE_DBG 0 #include "log.h" static uint8_t hex_nibble(char hex) { assert((hex >= '0' && hex <= '9') || (hex >= 'a' && hex <= 'f') || (hex >= 'A' && hex <= 'F')); if (hex >= '0' && hex <= '9') return hex - '0'; else if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10; else return hex - 'A' + 10; } static uint8_t hex_byte(const char hex[2]) { uint8_t upper = hex_nibble(hex[0]); uint8_t lower = hex_nibble(hex[1]); return upper << 4 | lower; } pixman_color_t conf_to_color(const struct yml_node *node) { const char *hex = yml_value_as_string(node); assert(hex != NULL); assert(strlen(hex) == 8); uint16_t red = hex_byte(&hex[0]); uint16_t green = hex_byte(&hex[2]); uint16_t blue = hex_byte(&hex[4]); uint16_t alpha = hex_byte(&hex[6]); alpha |= alpha << 8; return (pixman_color_t){ .red = (uint32_t)(red << 8 | red) * alpha / 0xffff, .green = (uint32_t)(green << 8 | green) * alpha / 0xffff, .blue = (uint32_t)(blue << 8 | blue) * alpha / 0xffff, .alpha = alpha, }; } struct fcft_font * conf_to_font(const struct yml_node *node) { const char *font_spec = yml_value_as_string(node); size_t count = 0; size_t size = 0; const char **fonts = NULL; char *copy = strdup(font_spec); for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) { /* Trim spaces, strictly speaking not necessary, but looks nice :) */ while (isspace(font[0])) font++; if (font[0] == '\0') continue; if (count + 1 > size) { size += 4; fonts = realloc(fonts, size * sizeof(fonts[0])); } assert(count + 1 <= size); fonts[count++] = font; } struct fcft_font *ret = fcft_from_name(count, fonts, NULL); free(fonts); free(copy); return ret; } enum font_shaping conf_to_font_shaping(const struct yml_node *node) { const char *v = yml_value_as_string(node); if (strcmp(v, "none") == 0) return FONT_SHAPE_NONE; else if (strcmp(v, "graphemes") == 0) { static bool have_warned = false; if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_GRAPHEME_SHAPING)) { have_warned = true; LOG_WARN("cannot enable grapheme shaping; no support in fcft"); } return FONT_SHAPE_GRAPHEMES; } else if (strcmp(v, "full") == 0) { static bool have_warned = false; if (!have_warned && !(fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING)) { have_warned = true; LOG_WARN("cannot enable full text shaping; no support in fcft"); } return FONT_SHAPE_FULL; } else { assert(false); return FONT_SHAPE_NONE; } } struct deco * conf_to_deco(const struct yml_node *node) { struct yml_dict_iter it = yml_dict_iter(node); const struct yml_node *deco_type = it.key; const struct yml_node *deco_data = it.value; const char *type = yml_value_as_string(deco_type); const struct deco_iface *iface = plugin_load_deco(type); assert(iface != NULL); return iface->from_conf(deco_data); } static struct particle * particle_simple_list_from_config(const struct yml_node *node, struct conf_inherit inherited) { size_t count = yml_list_length(node); struct particle *parts[count]; size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) { parts[idx] = conf_to_particle(it.node, inherited); } /* Lazy-loaded function pointer to particle_list_new() */ static struct particle *(*particle_list_new)(struct particle *common, struct particle *particles[], size_t count, int left_spacing, int right_spacing) = NULL; if (particle_list_new == NULL) { const struct plugin *plug = plugin_load("list", PLUGIN_PARTICLE); particle_list_new = dlsym(plug->lib, "particle_list_new"); assert(particle_list_new != NULL); } struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping, inherited.foreground, NULL); return particle_list_new(common, parts, count, 0, 2); } struct particle * conf_to_particle(const struct yml_node *node, struct conf_inherit inherited) { if (yml_is_list(node)) return particle_simple_list_from_config(node, inherited); struct yml_dict_iter pair = yml_dict_iter(node); const char *type = yml_value_as_string(pair.key); const struct yml_node *margin = yml_get_value(pair.value, "margin"); const struct yml_node *left_margin = yml_get_value(pair.value, "left-margin"); const struct yml_node *right_margin = yml_get_value(pair.value, "right-margin"); const struct yml_node *on_click = yml_get_value(pair.value, "on-click"); const struct yml_node *font_node = yml_get_value(pair.value, "font"); const struct yml_node *font_shaping_node = yml_get_value(pair.value, "font-shaping"); const struct yml_node *foreground_node = yml_get_value(pair.value, "foreground"); const struct yml_node *deco_node = yml_get_value(pair.value, "deco"); int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0; int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0; char *on_click_templates[MOUSE_BTN_COUNT] = {NULL}; if (on_click != NULL) { const char *yml_legacy = yml_value_as_string(on_click); if (yml_legacy != NULL) { char *legacy = NULL; if (yml_legacy[0] == '~' && yml_legacy[1] == '/') { const char *home_dir = getenv("HOME"); if (home_dir != NULL) if (asprintf(&legacy, "%s/%s", home_dir, yml_legacy + 2) < 0) legacy = NULL; if (legacy == NULL) legacy = strdup(yml_legacy); } else legacy = strdup(yml_legacy); on_click_templates[MOUSE_BTN_LEFT] = legacy; } else if (yml_is_dict(on_click)) { for (struct yml_dict_iter it = yml_dict_iter(on_click); it.key != NULL; yml_dict_next(&it)) { const char *key = yml_value_as_string(it.key); const char *yml_template = yml_value_as_string(it.value); char *template = NULL; if (yml_template[0] == '~' && yml_template[1] == '/') { const char *home_dir = getenv("HOME"); if (home_dir != NULL) if (asprintf(&template, "%s/%s", home_dir, yml_template + 2) < 0) template = NULL; if (template == NULL) template = strdup(yml_template); } else template = strdup(yml_template); if (strcmp(key, "left") == 0) on_click_templates[MOUSE_BTN_LEFT] = template; else if (strcmp(key, "middle") == 0) on_click_templates[MOUSE_BTN_MIDDLE] = template; else if (strcmp(key, "right") == 0) on_click_templates[MOUSE_BTN_RIGHT] = template; else if (strcmp(key, "wheel-up") == 0) on_click_templates[MOUSE_BTN_WHEEL_UP] = template; else if (strcmp(key, "wheel-down") == 0) on_click_templates[MOUSE_BTN_WHEEL_DOWN] = template; else if (strcmp(key, "previous") == 0) on_click_templates[MOUSE_BTN_PREVIOUS] = template; else if (strcmp(key, "next") == 0) on_click_templates[MOUSE_BTN_NEXT] = template; else assert(false); } } } struct deco *deco = deco_node != NULL ? conf_to_deco(deco_node) : NULL; /* * Font and foreground are inheritable attributes. Each particle * may specify its own font/foreground values, which will then be * used by itself, and all its sub-particles. If *not* specified, * we inherit the values from our parent. Note that since * particles actually *use* the font/foreground values, we must * clone the font, since each particle takes ownership of its own * font. */ struct fcft_font *font = font_node != NULL ? conf_to_font(font_node) : fcft_clone(inherited.font); enum font_shaping font_shaping = font_shaping_node != NULL ? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping; pixman_color_t foreground = foreground_node != NULL ? conf_to_color(foreground_node) : inherited.foreground; /* Instantiate base/common particle */ struct particle *common = particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco); const struct particle_iface *iface = plugin_load_particle(type); assert(iface != NULL); return iface->from_conf(pair.value, common); } struct bar * conf_to_bar(const struct yml_node *bar, enum bar_backend backend) { if (!conf_verify_bar(bar)) return NULL; struct bar_config conf = { .backend = backend, .layer = BAR_LAYER_BOTTOM, .font_shaping = FONT_SHAPE_FULL, }; /* * Required attributes */ const struct yml_node *height = yml_get_value(bar, "height"); conf.height = yml_value_as_int(height); const struct yml_node *location = yml_get_value(bar, "location"); conf.location = strcmp(yml_value_as_string(location), "top") == 0 ? BAR_TOP : BAR_BOTTOM; const struct yml_node *background = yml_get_value(bar, "background"); conf.background = conf_to_color(background); /* * Optional attributes */ const struct yml_node *monitor = yml_get_value(bar, "monitor"); if (monitor != NULL) conf.monitor = yml_value_as_string(monitor); const struct yml_node *layer = yml_get_value(bar, "layer"); if (layer != NULL) { const char *tmp = yml_value_as_string(layer); if (strcmp(tmp, "overlay") == 0) conf.layer = BAR_LAYER_OVERLAY; else if (strcmp(tmp, "top") == 0) conf.layer = BAR_LAYER_TOP; else if (strcmp(tmp, "bottom") == 0) conf.layer = BAR_LAYER_BOTTOM; else if (strcmp(tmp, "background") == 0) conf.layer = BAR_LAYER_BACKGROUND; else assert(false); } const struct yml_node *spacing = yml_get_value(bar, "spacing"); if (spacing != NULL) conf.left_spacing = conf.right_spacing = yml_value_as_int(spacing); const struct yml_node *left_spacing = yml_get_value(bar, "left-spacing"); if (left_spacing != NULL) conf.left_spacing = yml_value_as_int(left_spacing); const struct yml_node *right_spacing = yml_get_value(bar, "right-spacing"); if (right_spacing != NULL) conf.right_spacing = yml_value_as_int(right_spacing); const struct yml_node *margin = yml_get_value(bar, "margin"); if (margin != NULL) conf.left_margin = conf.right_margin = yml_value_as_int(margin); const struct yml_node *left_margin = yml_get_value(bar, "left-margin"); if (left_margin != NULL) conf.left_margin = yml_value_as_int(left_margin); const struct yml_node *right_margin = yml_get_value(bar, "right-margin"); if (right_margin != NULL) conf.right_margin = yml_value_as_int(right_margin); const struct yml_node *trackpad_sensitivity = yml_get_value(bar, "trackpad-sensitivity"); conf.trackpad_sensitivity = trackpad_sensitivity != NULL ? yml_value_as_int(trackpad_sensitivity) : 30; const struct yml_node *border = yml_get_value(bar, "border"); if (border != NULL) { const struct yml_node *width = yml_get_value(border, "width"); const struct yml_node *left_width = yml_get_value(border, "left-width"); const struct yml_node *right_width = yml_get_value(border, "right-width"); const struct yml_node *top_width = yml_get_value(border, "top-width"); const struct yml_node *bottom_width = yml_get_value(border, "bottom-width"); const struct yml_node *color = yml_get_value(border, "color"); const struct yml_node *margin = yml_get_value(border, "margin"); const struct yml_node *left_margin = yml_get_value(border, "left-margin"); const struct yml_node *right_margin = yml_get_value(border, "right-margin"); const struct yml_node *top_margin = yml_get_value(border, "top-margin"); const struct yml_node *bottom_margin = yml_get_value(border, "bottom-margin"); if (width != NULL) conf.border.left_width = conf.border.right_width = conf.border.top_width = conf.border.bottom_width = yml_value_as_int(width); if (left_width != NULL) conf.border.left_width = yml_value_as_int(left_width); if (right_width != NULL) conf.border.right_width = yml_value_as_int(right_width); if (top_width != NULL) conf.border.top_width = yml_value_as_int(top_width); if (bottom_width != NULL) conf.border.bottom_width = yml_value_as_int(bottom_width); if (color != NULL) conf.border.color = conf_to_color(color); if (margin != NULL) conf.border.left_margin = conf.border.right_margin = conf.border.top_margin = conf.border.bottom_margin = yml_value_as_int(margin); if (left_margin != NULL) conf.border.left_margin = yml_value_as_int(left_margin); if (right_margin != NULL) conf.border.right_margin = yml_value_as_int(right_margin); if (top_margin != NULL) conf.border.top_margin = yml_value_as_int(top_margin); if (bottom_margin != NULL) conf.border.bottom_margin = yml_value_as_int(bottom_margin); } /* * Create a default font and foreground * * These aren't used by the bar itself, but passed down to modules * and particles. This allows us to specify a default font and * foreground color at top-level. */ struct fcft_font *font = fcft_from_name(1, &(const char *){"sans"}, NULL); enum font_shaping font_shaping = FONT_SHAPE_FULL; pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */ const struct yml_node *font_node = yml_get_value(bar, "font"); if (font_node != NULL) { fcft_destroy(font); font = conf_to_font(font_node); } const struct yml_node *font_shaping_node = yml_get_value(bar, "font-shaping"); if (font_shaping_node != NULL) font_shaping = conf_to_font_shaping(font_shaping_node); const struct yml_node *foreground_node = yml_get_value(bar, "foreground"); if (foreground_node != NULL) foreground = conf_to_color(foreground_node); struct conf_inherit inherited = { .font = font, .font_shaping = font_shaping, .foreground = foreground, }; const struct yml_node *left = yml_get_value(bar, "left"); const struct yml_node *center = yml_get_value(bar, "center"); const struct yml_node *right = yml_get_value(bar, "right"); for (size_t i = 0; i < 3; i++) { const struct yml_node *node = i == 0 ? left : i == 1 ? center : right; if (node != NULL) { const size_t count = yml_list_length(node); struct module **mods = calloc(count, sizeof(*mods)); size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) { struct yml_dict_iter m = yml_dict_iter(it.node); const char *mod_name = yml_value_as_string(m.key); /* * These aren't used by the modules, but passed down * to particles. This allows us to specify a default * font and foreground for each module, and having it * applied to all its particles. */ const struct yml_node *mod_font = yml_get_value(m.value, "font"); const struct yml_node *mod_font_shaping = yml_get_value(m.value, "font-shaping"); const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground"); struct conf_inherit mod_inherit = { .font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font, .font_shaping = mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping, .foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground, }; const struct module_iface *iface = plugin_load_module(mod_name); mods[idx] = iface->from_conf(m.value, mod_inherit); } if (i == 0) { conf.left.mods = mods; conf.left.count = count; } else if (i == 1) { conf.center.mods = mods; conf.center.count = count; } else { conf.right.mods = mods; conf.right.count = count; } } } struct bar *ret = bar_new(&conf); free(conf.left.mods); free(conf.center.mods); free(conf.right.mods); fcft_destroy(font); return ret; } yambar-1.11.0/config.h000066400000000000000000000014111460770427600144750ustar00rootroot00000000000000#pragma once #include "bar/bar.h" #include "font-shaping.h" #include "yml.h" #include struct bar; struct particle; bool conf_verify_bar(const struct yml_node *bar); struct bar *conf_to_bar(const struct yml_node *bar, enum bar_backend backend); /* * Utility functions, for e.g. modules */ pixman_color_t conf_to_color(const struct yml_node *node); struct fcft_font *conf_to_font(const struct yml_node *node); enum font_shaping conf_to_font_shaping(const struct yml_node *node); struct conf_inherit { const struct fcft_font *font; enum font_shaping font_shaping; pixman_color_t foreground; }; struct particle *conf_to_particle(const struct yml_node *node, struct conf_inherit inherited); struct deco *conf_to_deco(const struct yml_node *node); yambar-1.11.0/decoration.h000066400000000000000000000011101460770427600153530ustar00rootroot00000000000000#pragma once #include struct deco { void *private; void (*expose)(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height); void (*destroy)(struct deco *deco); }; #define DECORATION_COMMON_ATTRS \ { \ NULL, false, NULL \ } yambar-1.11.0/decorations/000077500000000000000000000000001460770427600153745ustar00rootroot00000000000000yambar-1.11.0/decorations/background.c000066400000000000000000000030411460770427600176550ustar00rootroot00000000000000#include #include "../config-verify.h" #include "../config.h" #include "../decoration.h" #include "../plugin.h" struct private { // struct rgba color; pixman_color_t color; }; static void destroy(struct deco *deco) { struct private *d = deco->private; free(d); free(deco); } static void expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height) { const struct private *d = deco->private; pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, height}); } static struct deco * background_new(pixman_color_t color) { struct private *priv = calloc(1, sizeof(*priv)); priv->color = color; struct deco *deco = calloc(1, sizeof(*deco)); deco->private = priv; deco->expose = &expose; deco->destroy = &destroy; return deco; } static struct deco * from_conf(const struct yml_node *node) { const struct yml_node *color = yml_get_value(node, "color"); return background_new(conf_to_color(color)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"color", true, &conf_verify_color}, DECORATION_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct deco_iface deco_background_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct deco_iface iface __attribute__((weak, alias("deco_background_iface"))); #endif yambar-1.11.0/decorations/border.c000066400000000000000000000046621460770427600170250ustar00rootroot00000000000000#include #include "../config-verify.h" #include "../config.h" #include "../decoration.h" #include "../plugin.h" #define LOG_MODULE "border" #define LOG_ENABLE_DBG 0 #include "../log.h" #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) struct private { pixman_color_t color; int size; }; static void destroy(struct deco *deco) { struct private *d = deco->private; free(d); free(deco); } static void expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height) { const struct private *d = deco->private; pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 4, (pixman_rectangle16_t[]){ /* Top */ {x, y, width, min(d->size, height)}, /* Bottom */ {x, max(y + height - d->size, y), width, min(d->size, height)}, /* Left */ {x, y, min(d->size, width), height}, /* Right */ {max(x + width - d->size, x), y, min(d->size, width), height}, }); } static struct deco * border_new(pixman_color_t color, int size) { struct private *priv = calloc(1, sizeof(*priv)); priv->color = color; priv->size = size; struct deco *deco = calloc(1, sizeof(*deco)); deco->private = priv; deco->expose = &expose; deco->destroy = &destroy; return deco; } static struct deco * from_conf(const struct yml_node *node) { const struct yml_node *color = yml_get_value(node, "color"); const struct yml_node *size = yml_get_value(node, "size"); return border_new(conf_to_color(color), size != NULL ? yml_value_as_int(size) : 1); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"color", true, &conf_verify_color}, {"size", false, &conf_verify_unsigned}, DECORATION_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct deco_iface deco_border_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct deco_iface iface __attribute__((weak, alias("deco_border_iface"))); #endif yambar-1.11.0/decorations/meson.build000066400000000000000000000011761460770427600175430ustar00rootroot00000000000000deco_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) decorations = [] foreach deco : ['background', 'border', 'stack', 'underline', 'overline'] if plugs_as_libs shared_module('@0@'.format(deco), '@0@.c'.format(deco), dependencies: deco_sdk, name_prefix: 'decoration_', install: true, install_dir: join_paths(get_option('libdir'), 'yambar')) else decorations += [declare_dependency( sources: '@0@.c'.format(deco), dependencies: deco_sdk, compile_args: '-DHAVE_PLUGIN_@0@'.format(deco.underscorify()))] endif endforeach yambar-1.11.0/decorations/overline.c000066400000000000000000000032651460770427600173710ustar00rootroot00000000000000#include #include "../config-verify.h" #include "../config.h" #include "../decoration.h" #include "../plugin.h" struct private { int size; pixman_color_t color; }; static void destroy(struct deco *deco) { struct private *d = deco->private; free(d); free(deco); } static void expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height) { const struct private *d = deco->private; pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y, width, d->size}); } static struct deco * overline_new(int size, pixman_color_t color) { struct private *priv = calloc(1, sizeof(*priv)); priv->size = size; priv->color = color; struct deco *deco = calloc(1, sizeof(*deco)); deco->private = priv; deco->expose = &expose; deco->destroy = &destroy; return deco; } static struct deco * from_conf(const struct yml_node *node) { const struct yml_node *size = yml_get_value(node, "size"); const struct yml_node *color = yml_get_value(node, "color"); return overline_new(yml_value_as_int(size), conf_to_color(color)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"size", true, &conf_verify_unsigned}, {"color", true, &conf_verify_color}, DECORATION_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct deco_iface deco_overline_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct deco_iface iface __attribute__((weak, alias("deco_overline_iface"))); #endif yambar-1.11.0/decorations/stack.c000066400000000000000000000041711460770427600166500ustar00rootroot00000000000000#include #define LOG_MODULE "stack" #include "../config-verify.h" #include "../config.h" #include "../decoration.h" #include "../log.h" #include "../plugin.h" struct private { struct deco **decos; size_t count; }; static void destroy(struct deco *deco) { struct private *d = deco->private; for (size_t i = 0; i < d->count; i++) d->decos[i]->destroy(d->decos[i]); free(d->decos); free(d); free(deco); } static void expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height) { const struct private *d = deco->private; for (size_t i = 0; i < d->count; i++) d->decos[i]->expose(d->decos[i], pix, x, y, width, height); } static struct deco * stack_new(struct deco *decos[], size_t count) { struct private *priv = calloc(1, sizeof(*priv)); priv->decos = malloc(count * sizeof(priv->decos[0])); priv->count = count; for (size_t i = 0; i < count; i++) priv->decos[i] = decos[i]; struct deco *deco = calloc(1, sizeof(*deco)); deco->private = priv; deco->expose = &expose; deco->destroy = &destroy; return deco; } static struct deco * from_conf(const struct yml_node *node) { size_t count = yml_list_length(node); struct deco *decos[count]; size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) { decos[idx] = conf_to_deco(it.node); } return stack_new(decos, count); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { if (!yml_is_list(node)) { LOG_ERR("%s: must be a list of decorations", conf_err_prefix(chain, node)); return false; } for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) { if (!conf_verify_decoration(chain, it.node)) return false; } return true; } const struct deco_iface deco_stack_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct deco_iface iface __attribute__((weak, alias("deco_stack_iface"))); #endif yambar-1.11.0/decorations/underline.c000066400000000000000000000033551460770427600175330ustar00rootroot00000000000000#include #include "../config-verify.h" #include "../config.h" #include "../decoration.h" #include "../plugin.h" struct private { int size; pixman_color_t color; }; static void destroy(struct deco *deco) { struct private *d = deco->private; free(d); free(deco); } static void expose(const struct deco *deco, pixman_image_t *pix, int x, int y, int width, int height) { const struct private *d = deco->private; pixman_image_fill_rectangles(PIXMAN_OP_OVER, pix, &d->color, 1, &(pixman_rectangle16_t){x, y + height - d->size, width, d->size}); } static struct deco * underline_new(int size, pixman_color_t color) { struct private *priv = calloc(1, sizeof(*priv)); priv->size = size; priv->color = color; struct deco *deco = calloc(1, sizeof(*deco)); deco->private = priv; deco->expose = &expose; deco->destroy = &destroy; return deco; } static struct deco * from_conf(const struct yml_node *node) { const struct yml_node *size = yml_get_value(node, "size"); const struct yml_node *color = yml_get_value(node, "color"); return underline_new(yml_value_as_int(size), conf_to_color(color)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"size", true, &conf_verify_unsigned}, {"color", true, &conf_verify_color}, DECORATION_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct deco_iface deco_underline_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct deco_iface iface __attribute__((weak, alias("deco_underline_iface"))); #endif yambar-1.11.0/doc/000077500000000000000000000000001460770427600136275ustar00rootroot00000000000000yambar-1.11.0/doc/meson.build000066400000000000000000000046451460770427600160020ustar00rootroot00000000000000sh = find_program('sh', native: true) scdoc = dependency('scdoc', native: true) scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) plugin_pages = [] if plugin_alsa_enabled plugin_pages += ['yambar-modules-alsa.5.scd'] endif if plugin_backlight_enabled plugin_pages += ['yambar-modules-backlight.5.scd'] endif if plugin_battery_enabled plugin_pages += ['yambar-modules-battery.5.scd'] endif if plugin_clock_enabled plugin_pages += ['yambar-modules-clock.5.scd'] endif if plugin_cpu_enabled plugin_pages += ['yambar-modules-cpu.5.scd'] endif if plugin_disk_io_enabled plugin_pages += ['yambar-modules-disk-io.5.scd'] endif if plugin_dwl_enabled plugin_pages += ['yambar-modules-dwl.5.scd'] endif if plugin_foreign_toplevel_enabled plugin_pages += ['yambar-modules-foreign-toplevel.5.scd'] endif if plugin_mem_enabled plugin_pages += ['yambar-modules-mem.5.scd'] endif if plugin_mpd_enabled plugin_pages += ['yambar-modules-mpd.5.scd'] endif if plugin_i3_enabled plugin_pages += ['yambar-modules-i3.5.scd'] plugin_pages += ['yambar-modules-sway.5.scd'] endif if plugin_label_enabled plugin_pages += ['yambar-modules-label.5.scd'] endif if plugin_network_enabled plugin_pages += ['yambar-modules-network.5.scd'] endif if plugin_pipewire_enabled plugin_pages += ['yambar-modules-pipewire.5.scd'] endif if plugin_pulse_enabled plugin_pages += ['yambar-modules-pulse.5.scd'] endif if plugin_removables_enabled plugin_pages += ['yambar-modules-removables.5.scd'] endif if plugin_river_enabled plugin_pages += ['yambar-modules-river.5.scd'] endif if plugin_script_enabled plugin_pages += ['yambar-modules-script.5.scd'] endif if plugin_sway_xkb_enabled plugin_pages += ['yambar-modules-sway-xkb.5.scd'] endif if plugin_xkb_enabled plugin_pages += ['yambar-modules-xkb.5.scd'] endif foreach man_src : ['yambar.1.scd', 'yambar.5.scd', 'yambar-decorations.5.scd', 'yambar-modules.5.scd', 'yambar-particles.5.scd', 'yambar-tags.5.scd'] + plugin_pages parts = man_src.split('.') name = parts[-3] section = parts[-2] out = '@0@.@1@'.format(name, section) custom_target( out, output: out, input: man_src, command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())], capture: true, install: true, install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section))) endforeach yambar-1.11.0/doc/yambar-decorations.5.scd000066400000000000000000000044521460770427600202550ustar00rootroot00000000000000yambar-decorations(5) # NAME yambar-decorations - configuration file # DESCRIPTION Decorations are optional additions to particles, enabling you to e.g. change the background color of the entire particle, draw an underline and so on. Decorations are applied to the particle's margins as well. All decorations define their own configuration attributes, and there are no common attributes. # BACKGROUND This decoration sets the particles background color. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | color : color : yes : The background color. See *yambar*(5) for format. ## EXAMPLES ``` content: string: deco: background: color: 00ff00ff ``` # UNDERLINE This decoration renders a line of configurable size and color at the bottom of the particle. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | size : int : yes : The size (height/thickness) of the line, in pixels | color : color : yes : The color of the line. See *yambar*(5) for format. ## EXAMPLES ``` content: string: deco: underline: size: 2 color: ff0000ff ``` # OVERLINE Similar to _underline_, this decoration renders a line of configurable size and color at the top of the particle. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | size : int : yes : The size (height/thickness) of the line, in pixels | color : color : yes : The color of the line. See *yambar*(5) for format. ## EXAMPLES ``` content: string: deco: overline: size: 2 color: ff0000ff ``` # BORDER This decoration renders a border of configurable size (i.e border width) around the particle. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | color : color : yes : The color of the line. See *yambar*(5) for format. | size : int : no : Border width, in pixels. Defaults to 1px. ## EXAMPLES ``` content: string: deco: border: size: 2 color: ff0000ff ``` # STACK This particles combines multiple decorations. ## CONFIGURATION No configuration attributes available. The _stack_ is just a *list* of the decorations that should be combined. ## EXAMPLES ``` content: string: deco: stack: - background: ... - underline: ... ``` # SEE ALSO *yambar-particles*(5) yambar-1.11.0/doc/yambar-modules-alsa.5.scd000066400000000000000000000024711460770427600203300ustar00rootroot00000000000000yambar-modules-alsa(5) # NAME alsa - Monitors an alsa soundcard for volume and mute/unmute changes # TAGS [[ *Name* :[ *Type* :< *Description* | online : bool : True when the ALSA device has successfully been opened | dB : range : Volume level (in dB), with min and max as start and end range values. | volume : range : Volume level (raw), with min and max as start and end range values | percent : range : Volume level, as a percentage. This value is based on the *dB* tag if available, otherwise the *volume* tag. | muted : bool : True if muted, otherwise false # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | card : string : yes : The soundcard name. *default* might work. | mixer : string : yes : Mixer channel to monitor. _Master_ might work. | volume : string : no : The name of the channel to use as source for the volume level (default: first available channel, usually "Front Left"). | muted : string : no : The name of the channel to use as source for the muted state (default: first available channel, usually "Front Left"). # EXAMPLES ``` bar: left: - alsa: card: hw:PCH mixer: Master content: {string: {text: "{volume}"}} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-backlight.5.scd000066400000000000000000000014571460770427600213430ustar00rootroot00000000000000yambar-modules-backlight(5) # NAME backlight - This module reads monitor backlight status # DESCRIPTION This module reads monitor backlight status from _/sys/class/backlight_, and uses *udev* to monitor for changes. # TAGS [[ *Name* :[ *Type* :< *Description* | brightness : range : The current brightness level, in absolute value | percent : range : The current brightness level, in percent # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :[ *Description* | name : string : yes : The backlight device's name (one of the names in */sys/class/backlight*) # EXAMPLES ``` bar: left: - backlight: name: intel_backlight content: string: {text: "backlight: {percent}%"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-battery.5.scd000066400000000000000000000040311460770427600210540ustar00rootroot00000000000000yambar-modules-battery(5) # NAME battery - This module reads battery status # DESCRIPTION This module reads battery status from _/sys/class/power_supply_ and uses *udev* to monitor for changes. Note that it is common (and "normal") for batteries to be in the state *unknown* under certain conditions. For example, some have been seen to enter the *unknown* state when charging and the capacity reaches ~90%. The battery then stays in *unknown*, rather than *charging*, until it has been fully charged and enters the state *full*. This does not happen with all batteries, and other batteries may enter the state *unknown* under other conditions. # TAGS [[ *Name* :[ *Type* :< *Description* | name : string : Battery device name | manufacturer : string : Name of the battery manufacturer | model : string : Battery model name | state : string : One of *full*, *not charging*, *charging*, *discharging* or *unknown* | capacity : range : capacity left, in percent | estimate : string : Estimated time left (to empty while discharging, or to full while charging), formatted as HH:MM. # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | name : string : yes : Battery device name (one of the names in */sys/class/power_supply*) | poll-interval : int : no : How often, in milliseconds, to poll for capacity changes (default=*60000*). Set to `0` to disable polling (*warning*: many batteries do not support asynchronous reporting). Cannot be less than 250ms. | battery-scale : int : no : How much to scale down the battery charge amount. Some batteries report too high resulting in bad discharge estimates. Default=1. | smoothing-secs : int : no : How many seconds to perform smoothing over for battery discharge estimates. Default=100s. # EXAMPLES ``` bar: left: - battery: name: BAT0 poll-interval: 30000 content: string: {text: "BAT: {capacity}% {estimate}"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-clock.5.scd000066400000000000000000000015211460770427600204760ustar00rootroot00000000000000yambar-modules-clock(5) # NAME clock - This module provides the current date and time # TAGS [[ *Name* :[ *Type* :< *Description* | time : string : Current time, formatted using the _time-format_ attribute | date : string : Current date, formatted using the _date-format_ attribute # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | time-format : string : no : *strftime* formatter for the _time_ tag (default=*%H:%M*) | date-format : string : no : *strftime* formatter for the _date_ date (default=*%x*) | utc : bool : no : Use GMT instead of local timezone (default=false) # EXAMPLES ``` bar: left: - clock: time-format: "%H:%M %Z" content: string: {text: "{date} {time}"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-cpu.5.scd000066400000000000000000000032021460770427600201700ustar00rootroot00000000000000yambar-modules-cpu(5) # NAME cpu - This module provides the CPU usage # DESCRIPTION This module reports CPU usage, in percent. The _content_ particle is a template that is instantiated once for each core, and once for the total CPU usage. # TAGS [[ *Name* :[ *Type* :< *Description* | id : int : Core ID. 0..n represents individual cores, and -1 represents the total usage | cpu : range : Current usage of CPU core {id}, in percent # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | poll-interval : int : no : Refresh interval of the CPU usage stats in milliseconds (default=500). Cannot be less then 250ms. # EXAMPLES ## Display total CPU usage as a number ``` bar: left: - cpu: poll-interval: 2500 content: map: conditions: id < 0: - string: {text: , font: Font Awesome 6 Free:style=solid} - string: {text: "{cpu}%"} ``` ## Display a vertical bar for each core ``` bar: left: - cpu: poll-interval: 2500 content: map: conditions: id >= 0: - ramp: tag: cpu items: - string: {text: ▁} - string: {text: ▂} - string: {text: ▃} - string: {text: ▄} - string: {text: ▅} - string: {text: ▆} - string: {text: ▇} - string: {text: █} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-disk-io.5.scd000066400000000000000000000047301460770427600207470ustar00rootroot00000000000000yambar-modules-disk-io(5) # NAME disk-io - This module keeps track of the amount of bytes being read/written from/to disk. It can distinguish between all partitions currently present in the machine. # TAGS [[ *Name* :[ *Type* :< *Description* | device : string : Name of the device being tracked (use the command *lsblk* to see these). There is a special device, "Total", that reports the total stats for the machine | is_disk : boolean : whether or not the device is a disk (e.g. sda, sdb) or a partition (e.g. sda1, sda2, ...). "Total" is advertised as a disk. | read_speed : int : bytes read, in bytes/s | write_speed : int : bytes written, in bytes/s | ios_in_progress : int : number of ios that are happening at the time of polling # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | poll-interval : int : no : Refresh interval of disk's stats in milliseconds (default=500). Cannot be less then 250ms. # EXAMPLES This reports the total amount of bytes being read and written every second, formatting in b/s, kb/s, mb/s, or gb/s, as appropriate. ``` bar: left: - disk-io: poll-interval: 1000 content: map: conditions: device == Total: list: items: - string: {text: "Total read: "} - map: default: {string: {text: "{read_speed} B/s"}} conditions: read_speed > 1073741824: string: {text: "{read_speed:gib} GB/s"} read_speed > 1048576: string: {text: "{read_speed:mib} MB/s"} read_speed > 1024: string: {text: "{read_speed:kib} KB/s"} - string: {text: " | "} - string: {text: "Total written: "} - map: default: {string: {text: "{write_speed} B/s"}} conditions: write_speed > 1073741824: string: {text: "{write_speed:gib} GB/s"} write_speed > 1048576: string: {text: "{write_speed:mib} MB/s"} write_speed > 1024: string: {text: "{write_speed:kib} KB/s"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-dwl.5.scd000066400000000000000000000050431460770427600201740ustar00rootroot00000000000000yambar-modules-dwl(5) # NAME dwl - This module provides information about dwl tags, and information. # DESCRIPTION This module provides a map of each tags present in dwl. Each tags has its _id_, its _name_, its status (_selected_, _empty_, _urgent_) and the global data like _title_, _appid_, _fullscreen_, _floating_, _selmon_, and _layout_). The tags start a 1. For needs where you only want information about the global data and not the _tags_, there is a tag with the id _0_ that contains only the global data. This module will track *only* the monitor where yambar was launched on. If you have a multi monitor setup, please launch yambar on each of your monitors. Please, be aware that only *one instance* of this module is supported. Running multiple instances at the same time may result in *undefined behavior*. # TAGS [[ *Name* :[ *Type* :< *Description* | id : int : dwl tag id. | name : string : The name of the tag (defaults to _id_ if not set). | selected : bool : True if the tag is currently selected. | empty : bool : True if there are no windows in the tag. | urgent : bool : True if the tag has the urgent flag set. | title : string : The currently focused window's title. | appid : string : The currently focused window's application id. | fullscreen : bool : True if there is a fullscreen window in the current tag. | floating : bool : True if there is a floating window in the current tag. | selmon : bool : True if the monitor is actually focused. | layout : string : The actual layout name of the tag. # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | number-of-tags : int : yes : The number of defined tags in the dwl `config.def.h`. | name-of-tags : list : false : The name of the tags (must have the same length that _number-of-tags_). | dwl-info-filename : string : yes : The filepath to the log emitted by dwl when running. # EXAMPLES ``` bar: left: - dwl: number-of-tags: 9 dwl-info-filename: "/home/ogromny/dwl_info" name-of-tags: [ , , , , , , , ,  ] content: list: items: - map: conditions: # default tag id == 0: {string: {text: "{layout} {title}"}} selected: {string: {text: "-> {name}"}} ~empty: {string: {text: "{name}"}} urgent: {string: {text: "=> {name} <="}} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-foreign-toplevel.5.scd000066400000000000000000000031171460770427600226670ustar00rootroot00000000000000yambar-modules-foreign-toplevel(5) # NAME foreign-toplevel - This module provides information about toplevel windows in Wayland # DESCRIPTION This module uses the _wlr foreign toplevel management_ Wayland protocol to provide information about currently open windows, such as their application ID, window title, and their current state (maximized/minimized/fullscreen/activated). The configuration for the foreign-toplevel module specifies a _template_ particle. This particle will be instantiated once for each window. Note: Wayland only. # TAGS [[ *Name* :[ *Type* :< *Description* | app-id : string : The application ID (typically the application name) | title : string : The window title | maximized : bool : True if the window is currently maximized | minimized : bool : True if the window is currently minimized | fullscreen : bool : True if the window is currently fullscreened | activated : bool : True if the window is currently activated (i.e. has focus) # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | content : particle : yes : Template particle that will be instantiated once for each window | all-monitors : bool : no : When set to true, only windows on the same monitor the bar will be used. The default is false. # EXAMPLES ``` bar: left: - foreign-toplevel: content: map: conditions: ~activated: {empty: {}} activated: - string: {text: "{app-id}: {title}"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-i3.5.scd000066400000000000000000000057631460770427600177320ustar00rootroot00000000000000yambar-modules-i3(5) # NAME i3 - This module monitors i3 and sway workspaces # DESCRIPTION Unlike other modules where the _content_ attribute is just a single *particle*, the i3 module's _content_ is an associative array mapping i3/sway workspace names to a particle. You can add an empty workspace name, *""*, as a catch-all workspace particle. The *i3* module will fallback to this entry if it cannot find the workspace name in the _content_ map. It also recognizes the special name *current*, which always represents the currently focused workspace. On Sway, this can be used together with the _application_ and _title_ tags to replace the X11-only *xwindow* module. # TAGS [[ *Name* :[ *Type* :< *Description* | name : string : The workspace name | visible : bool : True if the workspace is currently visible (on any output) | focused : bool : True if the workspace is currently focused | urgent : bool : True if the workspace has the urgent flag set | empty : bool : True if the workspace is empty (Sway only) | state : string : One of *urgent*, *focused*, *unfocused* or *invisible* (note: *unfocused* is when it is visible, but neither focused nor urgent). | application : string : Name of application currently focused on this workspace (Sway only - use the *xwindow* module in i3) | title : string : This workspace's focused window's title | mode : string : The name of the current mode # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | content : associative array : yes : Unlike other modules, _content_ is an associative array mapping workspace names to particles. Use *""* to specify a default fallback particle, or *current* for the currently active workspace. | sort : enum : no : How to sort the list of workspaces; one of _none_, _native_, _ascending_ or _descending_, defaults to _none_. Use _native_ to sort numbered workspaces only. | strip-workspace-numbers : bool : no : If true, *N:* prefixes will be stripped from workspace names. Useful together with *sort*, to have the workspace order fixed. | persistent : list of strings : no : Persistent workspaces. I.e. workspaces that are never removed, even if empty. | left-spacing : int : no : Space, in pixels, on the left-side of each rendered workspace particle | right-spacing : int : no : Space, in pixels, on the right-side of each rendered workspace particle | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ # EXAMPLES This renders all workspace names, with an *\** indicating the currently focused one. It also renders the currently focused application name and window title. ``` bar: left: - i3: content: "": map: default: {string: {text: "{name}"}} conditions: state == focused: {string: {text: "{name}*"}} current: { string: {text: "{application}: {title}"}} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-label.5.scd000066400000000000000000000010241460770427600204600ustar00rootroot00000000000000yambar-modules-label(5) # NAME label - This module renders the provided _content_ particle # DESCRIPTION This module renders the provided _content_ particle, but provides no additional data. # TAGS None # CONFIGURATION No additional attributes supported, only the generic ones (see *GENERIC CONFIGURATION* in *yambar-modules*(5)) # EXAMPLES ``` bar: left: - label: content: {string: {text: hello world}} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-mem.5.scd000066400000000000000000000014061460770427600201630ustar00rootroot00000000000000yambar-modules-mem(5) # NAME mem - This module provides the memory usage # TAGS [[ *Name* :[ *Type* :< *Description* | free : int : Free memory in bytes | used : int : Used memory in bytes | total : int : Total memory in bytes | percent_free : range : Free memory in percent | percent_used : range : Used memory in percent # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | poll-interval : string : no : Refresh interval of the memory usage stats in milliseconds (default=500). Cannot be less then 250ms. # EXAMPLES ``` bar: left: - mem: poll-interval: 2500 content: string: {text: "{used:mb}MB"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-mpd.5.scd000066400000000000000000000031411460770427600201630ustar00rootroot00000000000000yambar-modules-mpd(5) # NAME mpd - This module provides MPD status such as currently playing artist/album/song # TAGS [[ *Name* :[ *Type* :< *Description* | state : string : One of *offline*, *stopped*, *paused* or *playing* | repeat : bool : True if the *repeat* flag is set | random : bool : True if the *random* flag is set | consume : bool : True if the *consume* flag is set | volume : range : Volume of MPD in percentage | album : string : Currently playing album (also valid in *paused* state) | artist : string : Artist of currently playing song (also valid in *paused* state) | title : string : Title of currently playing song (also valid in *paused* state) | file : string : Filename or URL of currently playing song (also valid in *paused* state) | pos : string : *%M:%S*-formatted string describing the song's current position (also see _elapsed_) | end : string : *%M:%S*-formatted string describing the song's total length (also see _duration_) | elapsed : realtime : Position in currently playing song, in milliseconds. Can be used with a _progress-bar_ particle. | duration : int : Length of currently playing song, in milliseconds # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | host : string : yes : Hostname/IP/unix-socket to connect to | port : int : no : TCP port to connect to # EXAMPLES ``` bar: left: - mpd: host: /run/mpd/socket content: string: {text: "{artist} - {album} - {title} ({end})"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-network.5.scd000066400000000000000000000044061460770427600211010ustar00rootroot00000000000000yambar-modules-network(5) # NAME network - This module monitors network connection state # DESCRIPTION This module monitors network connection state; disconnected/connected state and MAC/IP addresses. It instantiates the provided _content_ particle for each network interface. Note: while the module internally tracks all assigned IPv4/IPv6 addresses, it currently exposes only a single IPv4 and a single IPv6 address per network interface. # TAGS [[ *Name* :[ *Type* :< *Description* | name : string : Network interface name | index : int : Network interface index | carrier : bool : True if the interface has CARRIER. That is, if it is physically connected. | state : string : One of *unknown*, *not present*, *down*, *lower layers down*, *testing*, *dormant* or *up*. You are probably interested in *down* and *up*. | mac : string : MAC address | ipv4 : string : IPv4 address assigned to the interface, or *""* if none | ipv6 : string : IPv6 address assigned to the interface, or *""* if none | ssid : string : SSID the adapter is connected to (Wi-Fi only) | signal : int : Signal strength, in dBm (Wi-Fi only) | quality : range : Quality of the signal, in percent (Wi-Fi only) | rx-bitrate : int : RX bitrate, in bits/s | tx-bitrate : int : TX bitrate in bits/s | dl-speed : int : Download speed in bits/s | ul-speed : int : Upload speed in bits/s # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | left-spacing : int : no : Space, in pixels, in the left side of each rendered volume | right-spacing : int : no : Space, in pixels, on the right side of each rendered volume | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ | poll-interval : int : no : Periodically (in milliseconds) update the signal, quality, rx+tx bitrate, and ul+dl speed tags (default=0). Setting it to 0 disables updates. Cannot be less than 250ms. # EXAMPLES ``` bar: left: - network: content: map: default: string: {text: "{name}: {state} ({ipv4})"} conditions: ipv4 == "": string: {text: "{name}: {state}"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-pipewire.5.scd000066400000000000000000000042101460770427600212250ustar00rootroot00000000000000yambar-modules-pipewire(5) # NAME pipewire - Monitors pipewire for volume, mute/unmute, device change # TAGS [[ *Name* :[ *Type* :< *Description* | type : string : Either "source" (capture) or "sink" (speaker) | name : string : Current device name | description : string : Current device description | form_factor : string : Current device form factor (headset, speaker, mic, etc) | bus : string : Current device bus (bluetooth, alsa, etc) | icon : string : Current device icon name | muted : bool : True if muted, otherwise false | linear_volume : range : Linear volume in percentage (with 0 as min and 100 as max) | cubic_volume : range : Cubic volume (used by pulseaudio) in percentage (with 0 as min and 100 as max) # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | content : particle : yes : Unlike other modules, _content_ is a template particle that will be expanded twice (i.e. into a list of two elements). The first element is the 'sink', and the second element the 'source'. # EXAMPLES ``` bar: left: - pipewire: anchors: volume: &volume conditions: muted: {string: {text: "{linear_volume}%", foreground: ff0000ff}} ~muted: {string: {text: "{linear_volume}%"}} content: list: items: - map: conditions: type == "sink": map: conditions: icon == "audio-headset-bluetooth": string: {text: "🎧 "} default: - ramp: tag: linear_volume items: - string: {text: "🔈 "} - string: {text: "🔉 "} - string: {text: "🔊 "} type == "source": - string: {text: "🎙 "} - map: <<: *volume ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-pulse.5.scd000066400000000000000000000022011460770427600205270ustar00rootroot00000000000000yambar-modules-pulse(5) # NAME pulse - Monitors a PulseAudio source and/or sink # TAGS [[ *Name* :[ *Type* :< *Description* | online : bool : True when connected to the PulseAudio server | sink_online : bool : True when the sink is present | source_online : bool : True when the source is present | sink_percent : range : Sink volume level, as a percentage | source_percent : range : Source volume level, as a percentage | sink_muted : bool : True if the sink is muted, otherwise false | source_muted : bool : True if the source is muted, otherwise false | sink_port : string : Description of the active sink port | source_port : string : Description of the active source port # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | sink : string : no : Name of sink to monitor (default: _@DEFAULT\_SINK@_). | source : string : no : Name of source to monitor (default: _@DEFAULT\_SOURCE@_). # EXAMPLES ``` bar: left: - pulse: content: string: {text: "{sink_percent}% ({sink_port})"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-removables.5.scd000066400000000000000000000037141460770427600215500ustar00rootroot00000000000000yambar-modules-removables(5) # NAME removables - This module detects removable drives # DESCRIPTION This module detects removable drives (USB sticks, CD-ROMs) and instantiates the provided _content_ particle for each detected drive. # TAGS [[ *Name* :[ *Type* :< *Description* | vendor : string : Name of the drive vendor | model : string : Drive model name | optical : bool : True if the drive is an optical drive (CD-ROM, DVD-ROM etc) | audio : bool : True if an optical drive has an audio CD inserted (i.e. this property is always false for non-optical drives). | device : string : Volume device name (typically */dev/sd?*) | size : range : The volume's size, in bytes. The tag's maximum value is set to the underlying block device's size | label : string : The volume's label, or its size if it has no label | mounted : bool : True if the volume is mounted | mount_point : string : Path where the volume is mounted, or *""* if it is not mounted # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | left-spacing : int : no : Space, in pixels, in the left side of each rendered volume | right-spacing : int : no : Space, in pixels, on the right side of each rendered volume | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ | ignore : list of strings : no : List of device paths that should be ignored (e.g. /dev/mmcblk0, or /dev/mmcblk0p1) # EXAMPLES ``` bar: right: - removables: content: map: conditions: ~mounted: string: on-click: udisksctl mount -b {device} text: "{label}" mounted: string: on-click: udisksctl unmount -b {device} text: "{label}" deco: {underline: {size: 2, color: ffffffff}} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-river.5.scd000066400000000000000000000051431460770427600205360ustar00rootroot00000000000000yambar-modules-river(5) # NAME river - This module provides information about the river tags # DESCRIPTION This module uses river's (https://github.com/ifreund/river, a dynamic tiling Wayland compositor) status protocol to provide information about the river tags. It has an interface similar to the i3/sway module. The configuration for the river module specifies one _title_ particle, which will be instantiated once for each seat, with tags representing the seats' name, the title of the seats' currently focused view, and its current river "mode". It also specifies a _content_ template particle, which is instantiated once for all 32 river tags. This means you probably want to use a *map* particle to hide unused river tags. # TAGS (for the "content" particle) [[ *Name* :[ *Type* :< *Description* | id : int : River tag number | urgent : bool : True if the river tag has at least one urgent view. | visible : bool : True if the river tag is focused by at least one output (i.e. visible on at least one monitor). | focused : bool : True if the river tag is _visible_ and has keyboard focus. | occupied : bool : True if the river tag has views (i.e. windows). | state : string : Set to *urgent* if _urgent_ is true, *focused* if _focused_ is true, *unfocused* if _visible_ is true, but _focused_ is false, or *invisible* if the river tag is not visible on any monitors. # TAGS (for the "title" particle) [[ *Name* :[ *Type* :< *Description* | seat : string : The name of the seat. | title : string : The seat's focused view's title. | mode : string : The seat's current mode (entered with e.g. *riverctl enter-mode foobar*). | layout : string : Current layout of the output currently focused by the seat. # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | title : particle : no : Particle that will be instantiated with the _seat_ and _title_ tags. | content : particle : yes : Template particle that will be instantiated once for all of the 32 river tags. | all-monitors : bool : no : When set to false (the default), tags reflect river tags and seats for the monitor yambar is on only. When set to true, tags reflect the union of all monitors. # EXAMPLES ``` bar: left: - river: title: {string: { text: "{seat} - {title} ({layout}/{mode})" }} content: map: conditions: ~occupied: {empty: {}} occupied: string: margin: 5 text: "{id}: {state}" ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-script.5.scd000066400000000000000000000070731460770427600207170ustar00rootroot00000000000000yambar-modules-script(5) # NAME script - This module executes a user-provided script (or binary!) # DESCRIPTION This module executes a user-provided script (or binary!) that writes tags on its stdout. Scripts can be run in two modes: yambar polled, or continuously. In the yambar polled mode, the script is expected to write one set of tags and then exit. Yambar will execute the script again after a configurable amount of time. In continuous mode, the script is executed once. It will typically run in a loop, sending an updated tag set whenever it needs, or wants to. The last tag set is used (displayed) by yambar until a new tag set is received. This mode is intended to be used by scripts that depends on non-polling methods to update their state. Tag sets, or _transactions_, are separated by an empty line (e.g. *echo ""*). The empty line is required to commit (update) the tag even for only one transaction. Each _tag_ is a single line on the format: ``` name|type|value ``` Where _name_ is what you also use to refer to the tag in the yambar configuration, _type_ is one of the tag types defined in *yambar-tags*(5), and _value_ is the tag’s value. Example: ``` var1|string|hello var2|int|13 var1|string|world var2|int|37 ``` The example above consists of two transactions. Each transaction has two tags: one string tag and one integer tag. The second transaction replaces the tags from the first transaction. Note that **both** transactions need to be terminated with an empty line. Supported _types_ are: - string - int - bool - float - range:n-m (e.g. *var|range:0-100|57*) # TAGS User defined. # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | path : string : yes : Path to script/binary to execute. Must either be an absolute path, or start with *~/*. | args : list of strings : no : Arguments to pass to the script/binary. | poll-interval : integer : no : Number of milliseconds between each script run. If unset, or set to 0, continuous mode is used. # EXAMPLES Here is an "hello world" example script: ``` #!/bin/sh while true; do echo "test|string|hello" echo "" sleep 3 echo "test|string|world" echo "" sleep 3 done ``` This script runs in continuous mode, and will emit a single string tag, _test_, and alternate its value between *hello* and *world* every three seconds. A corresponding yambar configuration could look like this: ``` bar: left: - script: path: /path/to/script.sh args: [] content: {string: {text: "{test}"}} ``` Another example use case of this module could be to display currently playing song or other media from players that support MPRIS (Media Player Remote Interfacing Specification): ``` bar: center: - script: path: /usr/bin/playerctl args: - "--follow" - "metadata" - "-f" - | status|string|{{status}} artist|string|{{artist}} title|string|{{title}} content: map: conditions: status == Paused: {empty: {}} status == Playing: content: {string: {text: "{artist} - {title}"}} ``` The above snippet runs a _playerctl_ utility in _--follow_ mode, reacting to media updates on DBUS and outputting status, artist and title of media being played in a format that is recognized by yambar. See _playerctl_ documentation for more available metadata fields and control over which players get used. # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-sway-xkb.5.scd000066400000000000000000000030221460770427600211460ustar00rootroot00000000000000yambar-modules-sway-xkb(5) # NAME sway-xkb - This module monitor input devices' active XKB layout # DESCRIPTION This module uses *Sway* extensions to the I3 IPC API to monitor input devices' active XKB layout. As such, it requires Sway to be running. *Note* that the _content_ configuration option is a *template*; *sway-xkb* will instantiate a particle list, where each item is instantiated from this template, and represents an input device. # TAGS [[ *Name* :[ *Type* :< *Description* | id : string : Input device identifier | layout : string : The input device's currently active XKB layout # CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | identifiers : list of strings : yes : Identifiers of input devices to monitor. Use _swaymsg -t get_inputs_ to see available devices. | content : particle : yes : A particle template; each existing input device will be instantiated with this template. | left-spacing : int : no : Space, in pixels, in the left side of each rendered input device | right-spacing : int : no : Space, in pixels, on the right side of each rendered input device | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ # EXAMPLES ``` bar: left: - sway-xkb: identifiers: - 1523:7:HID_05f3:0007 - 7247:2:USB_USB_Keykoard spacing: 5 content: {string: {text: "{id}: {layout}"}} ``` # SEE ALSO *yambar-modules-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-sway.5.scd000066400000000000000000000002701460770427600203660ustar00rootroot00000000000000yambar-modules-sway(5) # DESCRIPTION Please use the i3 (*yambar-modules-i3*(5)) module, as it is fully compatible with Sway # SEE ALSO *yambar-modules*(5), *yambar-modules-i3*(5) yambar-1.11.0/doc/yambar-modules-xkb.5.scd000066400000000000000000000017511460770427600201740ustar00rootroot00000000000000yambar-modules-xkb(5) # NAME xkb - This module monitors the currently active XKB keyboard layout # DESCRIPTION This module monitors the currently active XKB keyboard layout and lock-key states. Note: this module is X11 only. It does not work in Wayland. # TAGS [[ *Name* :[ *Type* :< *Description* | name : string : Name of currently selected layout, long version (e.g. "English (US)") | symbol : string : Name of currently selected layout, short version (e.g. "us") | caps_lock : bool : True if *CapsLock* is enabled | num_lock : bool : True if *NumLock* is enabled | scroll_lock : bool : True if *ScrollLock* is enabled # CONFIGURATION No additional attributes supported, only the generic ones (see *GENERIC CONFIGURATION* in *yambar-modules*(5)) # EXAMPLES ``` bar: left: - xkb: content: string: {text: "{symbol}"} ``` # SEE ALSO *yambar-modules-sway-xkb*(5), *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules-xwindow.5.scd000066400000000000000000000016231460770427600211050ustar00rootroot00000000000000yambar-modules-xwindow(5) # NAME xwindow - This module provides the application name and window title # DESCRIPTION This module provides the application name and window title of the currently focused window. Note: this module is X11 only. It does not work in Wayland. If you are running Sway, take a look at the *i3* module and its _application_ and _title_ tags. # TAGS [[ *Name* :[ *Type* :< *Description* | application : string : Name of the application that owns the currently focused window | title : string : The title of the currently focused window # CONFIGURATION No additional attributes supported, only the generic ones (see *GENERIC CONFIGURATION* in *yambar-modules*(5)) # EXAMPLES ``` bar: left: - xwindow: content: string: {text: "{application}: {title}"} ``` # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-modules.5.scd000066400000000000000000000104431460770427600174100ustar00rootroot00000000000000yambar-modules(5) # NAME yambar-modules - configuration file # DESCRIPTION Modules are what monitors your system and provides data for the status bar. All modules expose their data through *tags*. Each tag has a *name*, *type* and *value*. The name and type is fixed, while the value typically changes over time. See *yambar-tags*(5). The tags are rendered by _particles_. Each particle has its own way of representing tag values. The simplest one is the _string_ particle, which renders a text representation of the tag value. See *yambar-particles*(5). Note that all the examples showed below have been kept simple. Here are a couple of tips that will improve the looks: ## Use list particles to change font and/or color The _string_ particle, for example, cannot change font or colors in the middle of its string. To do this, you need to wrap multiple _string_ particles in a _list_ particle. This can be useful if you want to use an icon font for a glyph since the default fallback fonts provided by fontconfig may not favor your icon font. Also remember there is a short version for lists (see *yambar-particles*(5)) For example, to render _backlight_ as " 20%", you could use: ``` content: - string: font: Font Awesome 6 Free:style=solid:pixelsize=14 text:  - string: font: Adobe Helvetica:pixelsize=12 text: "{percent}%" ``` ## Use map particles to handle 'state' Several modules have a _state_ tag that can be used to render different particles depending on the module's state. For example, you might want different things to be shown for a _network_ interface that is *down* or *up*. You could further differentiate between an *up* interface that has or has not an IP address assigned to it. Below is an example, where a wired connection is not renderer at all when disconnected. When connected, it is rendered in the default text color if it is up and also has an IPv4 address. If it is up, but does not have an IPv4 address, it is rendered in a semi-transparent white color. Finally, if it is down, or in any other unknown state, it is rendered in red. ``` content: map: conditions: ~carrier: {empty: {}} carrier: map: default: {string: {text: , font: *awesome, foreground: ffffff66}} conditions: state == up: map: default: {string: {text: , font: *awesome}} conditions: ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}} ``` ## Use yaml anchors You often end up using the same definitions in a lot of places. This is particular true for fonts. But it can also be true when mapping state. In these cases, you can define an anchor point, either at top-level, or in a module's _anchors_ attribute: ``` awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14 ``` Then reference it in your particle definitions: ``` content: string: {text: , font: *awesome} ``` # GENERIC CONFIGURATION Each module defines its own configuration format. However, the following attributes are supported by all modules: [[ *Name* :[ *Type* :[ *Req* :< *Description* | content : particle : yes : A particle describing how the module's information is to be rendered. See *yambar-particles*(5) | anchors : associative array : no : Free-to-use associative array, where you can put yaml anchor definitions | font : font : no : Font to use in the content particle. This is an inherited attribute. | foreground : color : no : Foreground (text) color of the content particle. This is an inherited attribute. # BUILT-IN MODULES Available modules have their own pages: *yambar-modules-alsa*(5) *yambar-modules-backlight*(5) *yambar-modules-battery*(5) *yambar-modules-clock*(5) *yambar-modules-cpu*(5) *yambar-modules-disk-io*(5) *yambar-modules-dwl*(5) *yambar-modules-foreign-toplevel*(5) *yambar-modules-i3*(5) *yambar-modules-label*(5) *yambar-modules-mem*(5) *yambar-modules-mpd*(5) *yambar-modules-network*(5) *yambar-modules-pipewire*(5) *yambar-modules-pulse*(5) *yambar-modules-removables*(5) *yambar-modules-river*(5) *yambar-modules-script*(5) *yambar-modules-sway-xkb*(5) *yambar-modules-sway*(5) *yambar-modules-xkb*(5) *yambar-modules-xwindow*(5) # SEE ALSO *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-particles.5.scd000066400000000000000000000222551460770427600177320ustar00rootroot00000000000000yambar-particles(5) # NAME yambar-particles - configuration file # DESCRIPTION Particles are what renders the tags provided by modules. Each particle defines its own set of configuration attributes. However, the following attributes are supported by all particles: [[ *Name* :[ *Type* :[ *Req* :< *Description* | left-margin : int : no : Space, in pixels, on the left side of the particle | right-margin : int : no : Space, in pixels, on the right side of the particle | margin : int : no : Short-hand for setting both _left-margin_ and _right-margin_ | font : font : no : Font to use. Note that this is an inherited attribute; i.e. you can set it on e.g. a _list_ particle, and it will apply to all particles in the list. | font-shaping : enum : no : font-shaping; one of _full_ or _none_. When set to _full_ (the default), strings will be "shaped" using HarfBuzz. Requires support in fcft. | foreground : color : no : Foreground (text) color. Just like _font_, this is an inherited attribute. | on-click : associative array/string : no : When set to a string, executes the string as a command when the particle is left-clicked. Tags can be used. Note that the string is *not* executed in a shell. Environment variables are not expanded. *~/* is expanded, but only in the first argument. The same applies to all attributes associated with it, below. | on-click.left : string : no : Command to execute when the particle is left-clicked. | on-click.right : string : no : Command to execute when the particle is right-clicked. | on-click.middle : string : no : Command to execute when the particle is middle-clicked. | on-click.wheel-up : string : no : Command to execute every time a 'wheel-up' event is triggered. | on-click.wheel-down : string : no : Command to execute every time a 'wheel-down' event is triggered. | on-click.previous : string : no : Command to execute when the particle is clicked with the 'previous' button. | on-click.next : string : no : Command to execute when the particle is clicked with the 'next' button. | deco : decoration : no : Decoration to apply to the particle. See *yambar-decorations*(5) ## EXAMPLES: *on-click* as a string (handles left click): ``` content: : on-click: command args ``` *on-click* as an associative array (handles other buttons): ``` content: : on-click: left: command-1 wheel-up: command-3 wheel-down: command-4 ``` # STRING This is the most basic particle. It takes a format string, consisting of free text mixed with tag specifiers. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :[ *Description* | text : string : yes : Format string. Tags are specified with _{tag_name}_. Some tag types have suffixes that can be appended (e.g. _{tag_name:suffix}_). See *yambar-modules*(5)). | max : int : no : Sets the rendered string's maximum length. If the final string's length exceeds this, the rendered string will be truncated, and "…" will be appended. Note that the trailing "…" is *included* in the maximum length. I.e. if you set _max_ to '5', you will only get *4* characters from the string. ## EXAMPLES ``` content: string: text: "hello, this is footag's value: {footag}" ``` # EMPTY This particle is a place-holder. While it does not render any tags, margins and decorations are rendered. ## CONFIGURATION None ## EXAMPLES ``` content: empty: {} ``` # LIST This particle is a list (or sequence, if you like) of other particles. It can be used to render e.g. _string_ particles with different font and/or color formatting. Or ay other particle combinations. But note that this means you *cannot* set any attributes on the _list_ particle itself. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | items : list : yes : List of sub particles | left-spacing : int : no : Space, in pixels, *between* the sub particles. | right-spacing : int : no : Space, in pixels, *between* the sub particles. Note: default=2 | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ ## EXAMPLES ``` content: list: spacing: 5 items: - string: {text: hello} - string: {text: world} ``` Many times, the only attribute you need to set is _items_. In this case, there is a shorter form. Instead of: ``` content: list: items: - string: ... - string: ... ``` you can list the items directly: ``` content: - string: ... - string: ... ``` # MAP This particle maps the values of a specific tag to different particles based on conditions. A condition takes either the form of: ``` ``` Or, for boolean tags: ``` ``` Where is the tag you would like to map, is one of: [- == :- != :- >= :- > :- <= :- < and is the value you would like to compare it to. *If the value contains any non-alphanumerical characters, you must surround it with ' \" ' *: ``` "hello world" "@#$%" ``` Negation is done with a preceding '~': ``` ~ ~ ``` To match for empty strings, use ' "" ': ``` == "" ``` Furthermore, you may use the boolean operators: [- && :- || in order to create more complex conditions: ``` && ``` You may surround with parenthesis for clarity or specifying precedence: ``` () && ( || ) ``` In addition to explicit tag values, you can also specify a default/fallback particle. Note that conditions are evaluated in the order they appear. *If multiple conditions are true, the first one will be used*. This means that in a configuration such as: ``` tx-bitrate > 1000: tx-bitrate > 1000000: ``` the second condition would never run, since whenever the second condition is true, the first is also true. The correct way of doing this would be to invert the order of the conditions: ``` tx-bitrate > 1000000: tx-bitrate > 1000: ``` ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | conditions : associative array : yes : An associative array of conditions (see above) mapped to particles | default : particle : no : Default particle to use, none of the conditions are true ## EXAMPLES ``` content: map: default: string: text: this is the default particle; the tag's value is now {tag_name} conditions: tag == one_value: string: text: tag's value is now one_value tag == another_value: string: text: tag's value is now another_value ``` For a boolean tag: ``` content: map: conditions: tag: string: text: tag is true ~tag: string: text: tag is false ``` # RAMP This particle uses a range tag to index into an array of particles. This can be used for example to map volume to a volume-level icon, or a battery's capacity level to a battery indicator. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | tag : string : yes : The range tag (name of) to use as index | items : list : yes : List of particles. Note that the tag value is *not* used as-is; its minimum and maximum values are used to map the tag's range to the particle list's range. | min : int : no : If present this will be used as a lower bound instead of the tags minimum value. Tag values falling outside the defined range will get clamped to min/max. | max : int : no : If present this will be used as an upper bound instead of the tags maximum value. Tag values falling outside the defined range will get clamped to min/max. ## EXAMPLES ``` content: ramp: tag: capacity items: - string: {text: } - string: {text: } - string: {text: } - string: {text: } - string: {text: } ``` # PROGRESS-BAR This particle renders a range tag's value as a progress bar. You control the looks of it by defining the particles to use for the progress bar's start and end, it's size, which particles to use for the range that has been completed, the range that has yet to be completed, and the particle to use as the progress bar's current value indicator. This particle also supports _realtime_ tags, and will then auto-update itself when needed. ## CONFIGURATION [[ *Name* :[ *Type* :[ *Req* :< *Description* | tag : string : yes : The range or realtime tag (name of) which value will be used as the progress bar's value. | length : int : yes : The size/length of the progress bar, in characters. Note that the _start_, _end_ and _indicator_ particles are *not* included. | start : particle : yes : The progress bar's starting character | end : particle : yes : The progress bar's ending character | fill : particle : yes : Particle to use in the completed range | empty : particle : yes : Particle to use in the not-yet-completed range | indicator : particle : yes : Particle representing the progress bar's current value ## EXAMPLES ``` content: progres-bar: tag: tag_name length: 20 start: {string: {text: ├}} end: {string: {text: ┤}} fill: {string: {text: ─}} empty: {string: {text: ╌}} indicator: {string: {text: ┼}} ``` # SEE ALSO *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/doc/yambar-tags.5.scd000066400000000000000000000062401460770427600166760ustar00rootroot00000000000000yambar-tags(5) # NAME yambar-tags - configuration file # DESCRIPTION Tags are the data carriers; it is through tags that modules expose their information. Each module defines its own set of tags. The available tag *types* are: [[ *Type* :< *Description* | string : Value is a string. Rendered as-is by the _string_ particle. | int : Value is an integer. Rendered in base 10 by the _string_ particle. | bool : Value is true or false. Rendered as "true" or "false" by the _string_ particle | float : Value is a float. Rendered in base 10, with two decimal digits by the _string_ particle | range : Value is an integer, with a minimum and maximum value associated with it. By default, the _string_ particle renders the value. The *:min* or *:max* suffixes may be added to instead render the minimum or maximum value (_\"{tag_name:min}\"_). | realtime : Value is an integer that changes in a predictable manner (in "realtime"). This allows the particle to update itself periodically. Only supported by the *yambar-particle-progress-bar*(5). Other particles can still render the tag's value. And, the _string_ particle recognizes the *:unit* suffix, which will be translated to a "s" for a tag with "seconds" resolution, or "ms" for one with "milliseconds" resolution. # FORMATTING A tag may be followed by one or more formatters that alter the tags rendition. Formatters are added by appending a ':' separated list of formatter names: "{tag_name:max:hex}" In the table below, "kind" describes the type of action performed by the formatter: - *format*: changes the representation of the tag's value - *selector*: changes what to render In general, formatters of the same kind cannot be combined; if multiple formatters of the same kind are specified, the last one will be used. [[ *Formatter* :[ *Kind* :[ *Applies to* :< *Description* | [0][.] : format : Numeric tags (integer and floats) : The width reserved to the field. The leading '0' is optional and indicates zero padding, as opposed to space padding. The trailing '.' is also optional | . : format : Float tags : How many decimals to print | [0][.] : format : N: numeric tags, M: float tags : Combined version of the two previous formatters | hex : format : All tag types : Renders a tag's value in hex | oct : format : All tag types : Renders a tag's value in octal | % : format : Range tags : Renders a range tag's value as a percentage value | kb, mb, gb : format : All tag types : Renders a tag's value (in decimal) divided by 1000, 1000^2 or 1000^3. Note: no unit suffix is appended) | kib, mib, gib : format : All tag types : Same as *kb*, *mb* and *gb*, but divide by 1024^n instead of 1000^n. | min : selector : Range tags : Renders a range tag's minimum value | max : selector : Range tags : Renders a range tag's maximum value | unit : selector : Realtime tags : Renders a realtime tag's unit (e.g. "s", or "ms") # EXAMPLES - A numeric (float or int) tag with at least 3 digits, zero-padded if necessary: ``` {tag:03} ``` - A float tag with 2 decimals: ``` {tag:.2} ``` - A "byte count" tag in gigabytes: ``` {tag:gib}GB ``` yambar-1.11.0/doc/yambar.1.scd000066400000000000000000000041351460770427600157370ustar00rootroot00000000000000yambar(1) # NAME yambar - modular status panel for X11 and Wayland # SYNOPSIS *yambar* [_OPTIONS_]... # OPTIONS *-b*,*--backend*={*xcb*,*wayland*,*auto*} Backend to use. The default is *auto*. In this mode, yambar will look for the environment variable _WAYLAND\_DISPLAY_, and if available, use the *Wayland* backend. If not, the *XCB* backend is used. *-c*,*--config*=_FILE_ Use an alternative configuration file instead of the default one. *-C*,*--validate* Verify the configuration and then quit. If no errors are detected, nothing is printed and the exit code is 0. If there are errors, these are printed on stdout and the exit code is non-zero. *-p*,*--print-pid*=_FILE_|_FD_ Print PID to this file, or FD, when successfully started. The file (or FD) is closed immediately after writing the PID. When a _FILE_ as been specified, the file is unlinked exit. *-d*,*--log-level*={*info*,*warning*,*error*,*none*} Log level, used both for log output on stderr as well as syslog. Default: _warning_. *-l*,*--log-colorize*=[{*never*,*always*,*auto*}] Enables or disables colorization of log output on stderr. *-s*,*--log-no-syslog* Disables syslog logging. Logging is only done on stderr. *-v*,*--version* Show the version number and quit # DESCRIPTION *yambar* is a lightweight and configurable status panel (_bar_, for short) for *X11* and *Wayland*. It has a number of _modules_ that provide information in the form of _tags_. For example, the _clock_ module has a _date_ tag that contains the current date. The modules do not know how to present the information though. This is instead done by _particles_. And the user, you, decides which particles (and thus how to present the data) to use. Furthermore, each particle can have a _decoration_. These are things like a different background, or an graphical underline. There is *no* support for images or icons. use an icon font (e.g. *Font Awesome*, or *Material Icons*) if you want a graphical representation. There are a number of modules and particles builtin. More can be added as plugins. You can even write your own! # CONFIGURATION See *yambar*(5) yambar-1.11.0/doc/yambar.5.scd000066400000000000000000000103231460770427600157370ustar00rootroot00000000000000yambar(5) # NAME yambar - configuration file # DESCRIPTION A yambar configuration file is a yaml formatted document containing an associative array named _bar_. You may define other top-level entities and reference them using anchors. Besides the normal yaml types, there are a couple of yambar specific types that are frequently used: - *font*: this is a comma separated list of fonts in _fontconfig_ format. Example of valid values: - Font Awesome 6 Brands - Font Awesome 6 Free:style=solid - Dina:pixelsize=10:slant=italic - Dina:pixelsize=10:weight=bold - *color*: an rgba hexstring; _RRGGBBAA_. Examples: - ffffffff: white, no transparency - 000000ff: black, no transparency - 00ff00ff: green, no transparency - ff000099: red, semi-transparent # FORMAT [[ *Name* :[ *Type* :[ *Req* :< *Description* | height : int : yes : The height of the bar, in pixels (*not* including border) | location : enum : yes : one of _top_ or _bottom_ | background : color : yes : Background color | monitor : string : no : Monitor to place the bar on. If not specified, the primary monitor will be used | layer : string : no : Layer to put bar on. One of _overlay_, _top_, _bottom_ or _background_. Wayland only. Default: _bottom_. | left-spacing : int : no : Space, in pixels, added *before* each module | right-spacing : int : no : Space, in pixels, added *after* each module | spacing : int : no : Short-hand for setting both _left-spacing_ and _right-spacing_ | left-margin : int : no : Left-side margin, in pixels | right-margin : int : no : Right-side margin, in pixels | margin : int : no : Short-hand for setting both _left-margin_ and _right-margin_ | border : associative array : no : Configures the border around the status bar | border.left-width : int : no : Width of the border on the left side, in pixels | border.right-width : int : no : Width of the border on the right side, in pixels | border.top-width : int : no : Width of the border on the top side, in pixels | border.bottom-width : int : no : Width of the border on the bottom side, in pixels | border.width : int : no : Short-hand for setting _border.left/right/top/bottom-width_ | border.color : color : no : The color of the border | border.left-margin : int : no : Left-side margin, in pixels, from screen edge to bar | border.right-margin : int : no : Right-side margin, in pixels, from screen edge to bar | border.top-margin : int : no : Top margin, in pixels, from screen edge to bar | border.bottom-margin : int : no : Bottom margin, in pixels, from screen edge to bar | border.margin : int : no : Short-hand for setting _border.left/right/top/bottom-margin_ | font : font : no : Default font to use in modules and particles. May also be a comma separated list of several fonts, in which case the first font is the primary font, and the rest fallback fonts. These are yambar custom fallback fonts that will be searched before the fontconfig provided fallback list. | font-shaping : enum : no : Default setting for font-shaping, for use in particles. One of _full_ or _none_. When set to _full_ (the default), strings will be "shaped" using HarfBuzz. Requires support in fcft. | foreground : color : no : Default foreground (text) color to use | trackpad-sensitivity : int : no : How easy it is to trigger wheel-up and wheel-down on-click handlers. Higher values means you need to drag your finger a longer distance. The default is 30. | left : list : no : Left-aligned modules (see *yambar-modules*(5)) | center : list : no : Center-aligned modules (see *yambar-modules*(5)) | right : list : no : Right-aligned modules (see *yambar-modules*(5)) # EXAMPLES Top-oriented bar with a single, right-aligned, module: the *clock*, rendered as a simple string displaying only the time (not date). ``` bar: location: top height: 26 background: 00000066 right: - clock: content: - string: {text: "{time}"} ``` # FILES The configuration file is searched for (in this order): - _$XDG_CONFIG_HOME/yambar/config.yml_ - _$HOME/.config/yambar/config.yml_ # SEE ALSO *yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5) yambar-1.11.0/examples/000077500000000000000000000000001460770427600147005ustar00rootroot00000000000000yambar-1.11.0/examples/configurations/000077500000000000000000000000001460770427600177325ustar00rootroot00000000000000yambar-1.11.0/examples/configurations/laptop.conf000066400000000000000000000270701460770427600221060ustar00rootroot00000000000000# Typical laptop setup, with wifi, brightness, battery etc, for # i3/Sway. # For X11/i3, you'll want to replace calls to swaymsg with i3-msg, and # the sway-xkb module with the xkb module. # fonts we'll be re-using here and there awesome: &awesome Font Awesome 6 Free:style=solid:pixelsize=14 awesome_brands: &awesome_brands Font Awesome 6 Brands:pixelsize=16 std_underline: &std_underline {underline: { size: 2, color: ff0000ff}} # This is THE bar configuration bar: height: 26 location: top spacing: 5 margin: 7 # Default font font: Adobe Helvetica:pixelsize=12 foreground: ffffffff background: 111111cc border: width: 1 color: 999999cc margin: 5 top-margin: 0 left: - i3: anchors: # Not used (directly) by f00bar; used here to avoid duplication - string: &i3_common {margin: 5, on-click: "swaymsg --quiet workspace {name}"} - string: &default {<<: *i3_common, text: "? {name}"} - string: &main {<<: *i3_common, text: , font: *awesome} - string: &surfing {<<: *i3_common, text: , font: *awesome_brands} - string: &misc {<<: *i3_common, text: , font: *awesome} - string: &mail {<<: *i3_common, text: , font: *awesome} - string: &music {<<: *i3_common, text: , font: *awesome} - focused: &focused deco: {stack: [background: {color: ffa0a04c}, <<: *std_underline]} - invisible: &invisible {foreground: ffffff55} - urgent: &urgent foreground: 000000ff deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]} - map: &i3_mode default: - string: margin: 5 text: "{mode}" deco: {background: {color: cc421dff}} - empty: {right-margin: 7} conditions: mode == default: {empty: {}} content: "": map: conditions: state == focused: {string: {<<: [*default, *focused]}} state == unfocused: {string: {<<: *default}} state == invisible: {string: {<<: [*default, *invisible]}} state == urgent: {string: {<<: [*default, *urgent]}} main: map: conditions: state == focused: {string: {<<: [*main, *focused]}} state == unfocused: {string: {<<: *main}} state == invisible: {string: {<<: [*main, *invisible]}} state == urgent: {string: {<<: [*main, *urgent]}} surfing: map: conditions: state == focused: {string: {<<: [*surfing, *focused]}} state == unfocused: {string: {<<: *surfing}} state == invisible: {string: {<<: [*surfing, *invisible]}} state == urgent: {string: {<<: [*surfing, *urgent]}} misc: map: conditions: state == focused: {string: {<<: [*misc, *focused]}} state == unfocused: {string: {<<: *misc}} state == invisible: {string: {<<: [*misc, *invisible]}} state == urgent: {string: {<<: [*misc, *urgent]}} mail: map: conditions: state == focused: {string: {<<: [*mail, *focused]}} state == unfocused: {string: {<<: *mail}} state == invisible: {string: {<<: [*mail, *invisible]}} state == urgent: {string: {<<: [*mail, *urgent]}} music: map: conditions: state == focused: {string: {<<: [*music, *focused]}} state == unfocused: {string: {<<: *music}} state == invisible: {string: {<<: [*music, *invisible]}} state == urgent: {string: {<<: [*music, *urgent]}} - foreign-toplevel: content: map: conditions: ~activated: {empty: {}} activated: - string: {text: "{app-id}", foreground: ffa0a0ff} - string: {text: ": {title}"} center: - mpd: host: /run/mpd/socket anchors: list: &artist_album_title spacing: 0 items: - map: conditions: state == playing: {string: {text: "{artist}"}} state == paused: {string: {text: "{artist}", foreground: ffffff66}} - string: {text: " | ", foreground: ffffff66} - map: conditions: state == playing: {string: {text: "{album}"}} state == paused: {string: {text: "{album}", foreground: ffffff66}} - string: {text: " | ", foreground: ffffff66} - map: conditions: state == playing: {string: {text: "{title}", foreground: ffa0a0ff}} state == paused: {string: {text: "{title}", foreground: ffffff66}} content: map: margin: 10 conditions: state == offline: {string: {text: offline, foreground: ff0000ff}} state == stopped: {string: {text: stopped}} state == paused: {list: *artist_album_title} state == playing: {list: *artist_album_title} right: - removables: anchors: drive: &drive { text: , font: *awesome} optical: &optical {text: , font: *awesome} spacing: 5 content: map: conditions: ~mounted: map: on-click: udisksctl mount -b {device} conditions: ~optical: [{string: *drive}, {string: {text: "{label}"}}] optical: [{string: *optical}, {string: {text: "{label}"}}] mounted: map: on-click: udisksctl unmount -b {device} conditions: ~optical: - string: {<<: *drive, deco: *std_underline} - string: {text: "{label}"} optical: - string: {<<: *optical, deco: *std_underline} - string: {text: "{label}"} - sway-xkb: identifiers: [1:1:AT_Translated_Set_2_keyboard] content: - string: {text: , font: *awesome} - string: {text: "{layout}"} - network: content: map: default: {empty: {}} conditions: name == enp1s0: map: conditions: ~carrier: {empty: {}} carrier: map: default: {string: {text: , font: *awesome, foreground: ffffff66}} conditions: state == up && ipv4 != "": {string: {text: , font: *awesome}} - network: poll-interval: 1000 content: map: default: {empty: {}} conditions: name == wlp2s0: map: default: {string: {text: , font: *awesome, foreground: ffffff66}} conditions: state == down: {string: {text: , font: *awesome, foreground: ff0000ff}} state == up: map: default: - string: {text: , font: *awesome} - string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s"} conditions: ipv4 == "": - string: {text: , font: *awesome, foreground: ffffff66} - string: {text: "{ssid} {dl-speed:mb}/{ul-speed:mb} Mb/s", foreground: ffffff66} - alsa: card: hw:PCH mixer: Master content: map: conditions: ~online: {string: {text: , font: *awesome, foreground: ff0000ff}} online: map: on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle" conditions: muted: {string: {text: , font: *awesome, foreground: ffffff66}} ~muted: ramp: tag: percent items: - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - backlight: name: intel_backlight content: [ string: {text: , font: *awesome}, string: {text: "{percent}%"}] - battery: name: BAT0 poll-interval: 30000 anchors: discharging: &discharging list: items: - ramp: tag: capacity items: - string: {text: , foreground: ff0000ff, font: *awesome} - string: {text: , foreground: ffa600ff, font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% {estimate}"} content: map: conditions: state == unknown: <<: *discharging state == discharging: <<: *discharging state == charging: - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% {estimate}"} state == full: - string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}% full"} state == "not charging": - ramp: tag: capacity items: - string: {text:  , foreground: ff0000ff, font: *awesome} - string: {text:  , foreground: ffa600ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text:  , foreground: 00ff00ff, font: *awesome} - string: {text: "{capacity}%"} - clock: time-format: "%H:%M %Z" content: - string: {text: , font: *awesome} - string: {text: "{date}", right-margin: 5} - string: {text: , font: *awesome} - string: {text: "{time}"} - label: content: string: on-click: systemctl poweroff text:  font: *awesome yambar-1.11.0/examples/configurations/river-tags.conf000066400000000000000000000040251460770427600226650ustar00rootroot00000000000000hack: &hack Hack Nerd Font:pixelsize=13 bg_default: &bg_default {stack: [{background: {color: 81A1C1ff}}, {underline: {size: 4, color: D8DEE9ff}}]} bar: height: 40 location: top font: JuliaMono:pixelsize=10 spacing: 2 margin: 0 layer: bottom foreground: eeeeeeff background: 2E3440dd left: - river: anchors: - base: &river_base left-margin: 10 right-margin: 13 default: {string: {text: , font: *hack}} conditions: id == 1: {string: {text: ﳐ, font: *hack}} id == 2: {string: {text: , font: *hack}} id == 3: {string: {text: , font: *hack}} id == 4: {string: {text: , font: *hack}} id == 5: {string: {text: , font: *hack}} id == 10: {string: {text: "scratchpad", font: *hack}} id == 11: {string: {text: "work", font: *hack}} content: map: on-click: left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))" right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))" middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))" conditions: state == urgent: map: <<: *river_base deco: {background: {color: D08770ff}} state == focused: map: <<: *river_base deco: *bg_default state == visible && ~occupied: map: <<: *river_base state == visible && occupied: map: <<: *river_base deco: *bg_default state == unfocused: map: <<: *river_base state == invisible && ~occupied: {empty: {}} state == invisible && occupied: map: <<: *river_base deco: {underline: {size: 3, color: ea6962ff}} yambar-1.11.0/examples/scripts/000077500000000000000000000000001460770427600163675ustar00rootroot00000000000000yambar-1.11.0/examples/scripts/cpu.sh000077500000000000000000000060261460770427600175210ustar00rootroot00000000000000#!/bin/bash # cpu.sh - measures CPU usage at a configurable sample interval # # Usage: cpu.sh INTERVAL_IN_SECONDS # # This script will emit the following tags on stdout (N is the number # of logical CPUs): # # Name Type # -------------------- # cpu range 0-100 # cpu0 range 0-100 # cpu1 range 0-100 # ... # cpuN-1 range 0-100 # # I.e. ‘cpu’ is the average (or aggregated) CPU usage, while cpuX is a # specific CPU’s usage. # # Example configuration (update every second): # # - script: # path: /path/to/cpu.sh # args: [1] # content: {string: {text: "{cpu}%"}} # interval=${1} case ${interval} in ''|*[!0-9]*) echo "interval must be an integer" exit 1 ;; *) ;; esac # Get number of CPUs, by reading /proc/stat # The output looks like: # # cpu A B C D ... # cpu0 A B C D ... # cpu1 A B C D ... # cpuN A B C D ... # # The first line is a summary line, accounting *all* CPUs IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat) cpu_count=$((${#all_cpu_stats[@]} - 1)) # Arrays of ‘previous’ idle and total stats, needed to calculate the # difference between each sample. prev_idle=() prev_total=() for i in $(seq ${cpu_count}); do prev_idle+=(0) prev_total+=(0) done prev_average_idle=0 prev_average_total=0 while true; do IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat) usage=() # CPU usage in percent, 0 <= x <= 100 average_idle=0 # All CPUs idle time since boot average_total=0 # All CPUs total time since boot for i in $(seq 0 $((cpu_count - 1))); do # Split this CPUs stats into an array stats=($(echo "${all_cpu_stats[$((i + 1))]}")) # man procfs(5) user=${stats[1]} nice=${stats[2]} system=${stats[3]} idle=${stats[4]} iowait=${stats[5]} irq=${stats[6]} softirq=${stats[7]} steal=${stats[8]} guest=${stats[9]} guestnice=${stats[10]} # Guest time already accounted for in user user=$((user - guest)) nice=$((nice - guestnice)) idle=$((idle + iowait)) total=$((user + nice + system + irq + softirq + idle + steal + guest + guestnice)) average_idle=$((average_idle + idle)) average_total=$((average_total + total)) # Diff since last sample diff_idle=$((idle - prev_idle[i])) diff_total=$((total - prev_total[i])) usage[i]=$((100 * (diff_total - diff_idle) / diff_total)) prev_idle[i]=${idle} prev_total[i]=${total} done diff_average_idle=$((average_idle - prev_average_idle)) diff_average_total=$((average_total - prev_average_total)) average_usage=$((100 * (diff_average_total - diff_average_idle) / diff_average_total)) prev_average_idle=${average_idle} prev_average_total=${average_total} echo "cpu|range:0-100|${average_usage}" for i in $(seq 0 $((cpu_count - 1))); do echo "cpu${i}|range:0-100|${usage[i]}" done echo "" sleep "${interval}" done yambar-1.11.0/examples/scripts/dwl-tags.sh000077500000000000000000000100131460770427600204430ustar00rootroot00000000000000#!/usr/bin/env bash # # dwl-tags.sh - display dwl tags # # USAGE: dwl-tags.sh 1 # # REQUIREMENTS: # - inotifywait ( 'inotify-tools' on arch ) # - Launch dwl with `dwl > ~.cache/dwltags` or change $fname # # TAGS: # Name Type Return # ---------------------------------------------------- # {tag_N} string dwl tags name # {tag_N_occupied} bool dwl tags state occupied # {tag_N_focused} bool dwl tags state focused # {layout} string dwl layout # {title} string client title # # Now the fun part # # Exemple configuration: # # - script: # path: /absolute/path/to/dwl-tags.sh # args: [1] # anchors: # - occupied: &occupied {foreground: 57bbf4ff} # - focused: &focused {foreground: fc65b0ff} # - default: &default {foreground: d2ccd6ff} # content: # - map: # margin: 4 # conditions: # tag_0_occupied: # map: # conditions: # tag_0_focused: {string: {text: "{tag_0}", <<: *focused}} # ~tag_0_focused: {string: {text: "{tag_0}", <<: *occupied}} # ~tag_0_occupied: # map: # conditions: # tag_0_focused: {string: {text: "{tag_0}", <<: *focused}} # ~tag_0_focused: {string: {text: "{tag_0}", <<: *default}} # ... # ... # ... # - map: # margin: 4 # conditions: # tag_8_occupied: # map: # conditions: # tag_8_focused: {string: {text: "{tag_8}", <<: *focused}} # ~tag_8_focused: {string: {text: "{tag_8}", <<: *occupied}} # ~tag_8_occupied: # map: # values: # tag_8_focused: {string: {text: "{tag_8}", <<: *focused}} # ~tag_8_focused: {string: {text: "{tag_8}", <<: *default}} # - list: # spacing: 3 # items: # - string: {text: "{layout}"} # - string: {text: "{title}"} # Variables declare output title layout activetags selectedtags declare -a tags name readonly fname="$HOME"/.cache/dwltags _cycle() { tags=( "1" "2" "3" "4" "5" "6" "7" "8" "9" ) # Name of tag (optional) # If there is no name, number are used # # Example: # name=( "" "" "" "Media" ) # -> return "" "" "" "Media" 5 6 7 8 9) name=() for tag in "${!tags[@]}"; do mask=$((1</dev/null; then printf -- '%s\n' "${tag_name}_${tag}_focused|bool|true" printf -- '%s\n' "title|string|${title}" else printf '%s\n' "${tag_name}_${tag}_focused|bool|false" fi if (( "${activetags}" & mask )) 2>/dev/null; then printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|true" else printf -- '%s\n' "${tag_name}_${tag}_occupied|bool|false" fi done printf -- '%s\n' "layout|string|${layout}" printf -- '%s\n' "" } # Call the function here so the tags are displayed at dwl launch _cycle while true; do [[ ! -f "${fname}" ]] && printf -- '%s\n' \ "You need to redirect dwl stdout to ~/.cache/dwltags" >&2 inotifywait -qq --event modify "${fname}" # Get info from the file output="$(tail -n6 "${fname}")" title="$(echo "${output}" | grep title | cut -d ' ' -f 3- )" #selmon="$(echo "${output}" | grep 'selmon')" layout="$(echo "${output}" | grep layout | cut -d ' ' -f 3- )" # Get the tag bit mask as a decimal activetags="$(echo "${output}" | grep tags | awk '{print $3}')" selectedtags="$(echo "${output}" | grep tags | awk '{print $4}')" _cycle done unset -v output title layout activetags selectedtags unset -v tags name yambar-1.11.0/examples/scripts/pacman.sh000077500000000000000000000035001460770427600201630ustar00rootroot00000000000000#!/usr/bin/env bash # # pacman.sh - display number of packages update available # by default check every hour # # USAGE: pacman.sh # # TAGS: # Name Type Return # ------------------------------------------- # {pacman} int number of pacman packages # {aur} int number of aur packages # {pkg} int sum of both # # Exemples configuration: # - script: # path: /absolute/path/to/pacman.sh # args: [] # content: { string: { text: "{pacman} + {aur} = {pkg}" } } # # To display a message when there is no update: # - script: # path: /absolute/path/to/pacman.sh # args: [] # content: # map: # default: { string: { text: "{pacman} + {aur} = {pkg}" } } # conditions: # pkg == 0: {string: {text: no updates}} declare interval aur_helper pacman_num aur_num pkg_num # Error message in STDERR _err() { printf -- '%s\n' "[$(date +'%Y-%m-%d %H:%M:%S')]: $*" >&2 } # Display tags before yambar fetch the updates number printf -- '%s\n' "pacman|int|0" printf -- '%s\n' "aur|int|0" printf -- '%s\n' "pkg|int|0" printf -- '%s\n' "" while true; do # Change interval # NUMBER[SUFFIXE] # Possible suffix: # "s" seconds / "m" minutes / "h" hours / "d" days interval="1h" # Change your aur manager aur_helper="paru" # Get number of packages to update pacman_num=$(checkupdates | wc -l) if ! hash "${aur_helper}" >/dev/null 2>&1; then _err "aur helper not found, change it in the script" exit 1 else aur_num=$("${aur_helper}" -Qmu | wc -l) fi pkg_num=$(( pacman_num + aur_num )) printf -- '%s\n' "pacman|int|${pacman_num}" printf -- '%s\n' "aur|int|${aur_num}" printf -- '%s\n' "pkg|int|${pkg_num}" printf -- '%s\n' "" sleep "${interval}" done unset -v interval aur_helper pacman_num aur_num pkg_num unset -f _err yambar-1.11.0/external/000077500000000000000000000000001460770427600147045ustar00rootroot00000000000000yambar-1.11.0/external/river-status-unstable-v1.xml000066400000000000000000000140031460770427600222330ustar00rootroot00000000000000 Copyright 2020 The River Developers Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. A global factory for objects that receive status information specific to river. It could be used to implement, for example, a status bar. This request indicates that the client will not use the river_status_manager object any more. Objects that have been created through this instance are not affected. This creates a new river_output_status object for the given wl_output. This creates a new river_seat_status object for the given wl_seat. This interface allows clients to receive information about the current windowing state of an output. This request indicates that the client will not use the river_output_status object any more. Sent once binding the interface and again whenever the tag focus of the output changes. Sent once on binding the interface and again whenever the tag state of the output changes. Sent once on binding the interface and again whenever the set of tags with at least one urgent view changes. Sent once on binding the interface should a layout name exist and again whenever the name changes. Sent when the current layout name has been removed without a new one being set, for example when the active layout generator disconnects. This interface allows clients to receive information about the current focus of a seat. Note that (un)focused_output events will only be sent if the client has bound the relevant wl_output globals. This request indicates that the client will not use the river_seat_status object any more. Sent on binding the interface and again whenever an output gains focus. Sent whenever an output loses focus. Sent once on binding the interface and again whenever the focused view or a property thereof changes. The title may be an empty string if no view is focused or the focused view did not set a title. Sent once on binding the interface and again whenever a new mode is entered (e.g. with riverctl enter-mode foobar). yambar-1.11.0/external/wlr-foreign-toplevel-management-unstable-v1.xml000066400000000000000000000264221460770427600257700ustar00rootroot00000000000000 Copyright © 2018 Ilia Bozhinov Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and letting them request certain actions on them, like maximizing, etc. After a client binds the zwlr_foreign_toplevel_manager_v1, each opened toplevel window will be sent via the toplevel event This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial details of the toplevel(title, app_id, states, etc.) will be sent immediately after this event via the corresponding events in zwlr_foreign_toplevel_handle_v1. Indicates the client no longer wishes to receive events for new toplevels. However the compositor may emit further toplevel_created events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. Each toplevel has a list of outputs it is visible on, conveyed to the client with the output_enter and output_leave events. This event is emitted whenever the title of the toplevel changes. This event is emitted whenever the app-id of the toplevel changes. This event is emitted whenever the toplevel becomes visible on the given output. A toplevel may be visible on multiple outputs. This event is emitted whenever the toplevel stops being visible on the given output. It is guaranteed that an entered-output event with the same output has been emitted before this event. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Request that this toplevel be activated on the given seat. There is no guarantee the toplevel will be actually activated. The different states that a toplevel can have. These have the same meaning as the states with the same names defined in xdg-toplevel This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is sent after all changes in the toplevel state have been sent. This allows changes to the zwlr_foreign_toplevel_handle_v1 properties to be seen as atomic, even if they happen via multiple events. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the zwlr_foreign_toplevel_handle_v1.closed event will be emitted. The rectangle of the surface specified in this request corresponds to the place where the app using this protocol represents the given toplevel. It can be used by the compositor as a hint for some operations, e.g minimizing. The client is however not required to set this, in which case the compositor is free to decide some default value. If the client specifies more than one rectangle, only the last one is considered. The dimensions are given in surface-local coordinates. Setting width=height=0 removes the already-set rectangle. This event means the toplevel has been destroyed. It is guaranteed there won't be any more events for this zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes inert so any requests will be ignored except the destroy request. Destroys the zwlr_foreign_toplevel_handle_v1 object. This request should be called either when the client does not want to use the toplevel anymore or after the closed event to finalize the destruction of the object. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. This event is emitted whenever the parent of the toplevel changes. No event is emitted when the parent handle is destroyed by the client. yambar-1.11.0/external/wlr-layer-shell-unstable-v1.xml000066400000000000000000000440361460770427600226170ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. yambar-1.11.0/external/wlr-protocols/000077500000000000000000000000001460770427600175325ustar00rootroot00000000000000yambar-1.11.0/font-shaping.h000066400000000000000000000001511460770427600156250ustar00rootroot00000000000000#pragma once enum font_shaping { FONT_SHAPE_NONE, FONT_SHAPE_GRAPHEMES, FONT_SHAPE_FULL, }; yambar-1.11.0/generate-version.sh000077500000000000000000000020061460770427600166740ustar00rootroot00000000000000#!/bin/sh set -e default_version=${1} src_dir=${2} out_file=${3} # echo "default version: ${default_version}" # echo "source directory: ${src_dir}" # echo "output file: ${out_file}" if [ -d "${src_dir}/.git" ] && command -v git > /dev/null; then workdir=$(pwd) cd "${src_dir}" if git describe --tags > /dev/null 2>&1; then git_version=$(git describe --always --tags) else # No tags available, happens in e.g. CI builds git_version="${default_version}" fi git_branch=$(git rev-parse --abbrev-ref HEAD) cd "${workdir}" new_version="${git_version} ($(date "+%b %d %Y"), branch '${git_branch}')" else new_version="${default_version}" fi new_version="#define YAMBAR_VERSION \"${new_version}\"" if [ -f "${out_file}" ]; then old_version=$(cat "${out_file}") else old_version="" fi # echo "old version: ${old_version}" # echo "new version: ${new_version}" if [ "${old_version}" != "${new_version}" ]; then echo "${new_version}" > "${out_file}" fi yambar-1.11.0/log.c000066400000000000000000000127501460770427600140140ustar00rootroot00000000000000#include "log.h" #include #include #include #include #include #include #include #include #include #include #define ALEN(v) (sizeof(v) / sizeof((v)[0])) #define UNUSED __attribute__((unused)) static bool colorize = false; static bool do_syslog = false; static enum log_class log_level = LOG_CLASS_NONE; static const struct { const char name[8]; const char log_prefix[7]; uint8_t color; int syslog_equivalent; } log_level_map[] = { [LOG_CLASS_NONE] = {"none", "none", 5, -1}, [LOG_CLASS_ERROR] = {"error", " err", 31, LOG_ERR}, [LOG_CLASS_WARNING] = {"warning", "warn", 33, LOG_WARNING}, [LOG_CLASS_INFO] = {"info", "info", 97, LOG_INFO}, [LOG_CLASS_DEBUG] = {"debug", " dbg", 36, LOG_DEBUG}, }; void log_init(enum log_colorize _colorize, bool _do_syslog, enum log_facility syslog_facility, enum log_class _log_level) { static const int facility_map[] = { [LOG_FACILITY_USER] = LOG_USER, [LOG_FACILITY_DAEMON] = LOG_DAEMON, }; colorize = _colorize == LOG_COLORIZE_NEVER ? false : _colorize == LOG_COLORIZE_ALWAYS ? true : isatty(STDERR_FILENO); do_syslog = _do_syslog; log_level = _log_level; int slvl = log_level_map[_log_level].syslog_equivalent; if (do_syslog && slvl != -1) { openlog(NULL, /*LOG_PID*/ 0, facility_map[syslog_facility]); setlogmask(LOG_UPTO(slvl)); } } void log_deinit(void) { if (do_syslog) closelog(); } static void _log(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, int sys_errno, va_list va) { assert(log_class > LOG_CLASS_NONE); assert(log_class < ALEN(log_level_map)); if (log_class > log_level) return; const char *prefix = log_level_map[log_class].log_prefix; unsigned int class_clr = log_level_map[log_class].color; char clr[16]; snprintf(clr, sizeof(clr), "\033[%um", class_clr); fprintf(stderr, "%s%s%s: ", colorize ? clr : "", prefix, colorize ? "\033[0m" : ""); if (colorize) fputs("\033[2m", stderr); fprintf(stderr, "%s:%d: ", file, lineno); if (colorize) fputs("\033[0m", stderr); vfprintf(stderr, fmt, va); if (sys_errno != 0) fprintf(stderr, ": %s (%d)", strerror(sys_errno), sys_errno); fputc('\n', stderr); } static void _sys_log(enum log_class log_class, const char *module, const char UNUSED *file, int UNUSED lineno, const char *fmt, int sys_errno, va_list va) { assert(log_class > LOG_CLASS_NONE); assert(log_class < ALEN(log_level_map)); if (!do_syslog) return; if (log_class > log_level) return; /* Map our log level to syslog's level */ int level = log_level_map[log_class].syslog_equivalent; char msg[4096]; int n = vsnprintf(msg, sizeof(msg), fmt, va); assert(n >= 0); if (sys_errno != 0 && (size_t)n < sizeof(msg)) snprintf(msg + n, sizeof(msg) - n, ": %s", strerror(sys_errno)); syslog(level, "%s: %s", module, msg); } void log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va) { va_list va2; va_copy(va2, va); _log(log_class, module, file, lineno, fmt, 0, va); _sys_log(log_class, module, file, lineno, fmt, 0, va2); va_end(va2); } void log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) { va_list va; va_start(va, fmt); log_msg_va(log_class, module, file, lineno, fmt, va); va_end(va); } void log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va) { log_errno_provided_va(log_class, module, file, lineno, errno, fmt, va); } void log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) { va_list va; va_start(va, fmt); log_errno_va(log_class, module, file, lineno, fmt, va); va_end(va); } void log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy, const char *fmt, va_list va) { va_list va2; va_copy(va2, va); _log(log_class, module, file, lineno, fmt, errno_copy, va); _sys_log(log_class, module, file, lineno, fmt, errno_copy, va2); va_end(va2); } void log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int errno_copy, const char *fmt, ...) { va_list va; va_start(va, fmt); log_errno_provided_va(log_class, module, file, lineno, errno_copy, fmt, va); va_end(va); } static size_t map_len(void) { size_t len = ALEN(log_level_map); #ifndef _DEBUG /* Exclude "debug" entry for non-debug builds */ len--; #endif return len; } int log_level_from_string(const char *str) { if (str[0] == '\0') return -1; for (int i = 0, n = map_len(); i < n; i++) if (strcmp(str, log_level_map[i].name) == 0) return i; return -1; } const char * log_level_string_hint(void) { static char buf[64]; if (buf[0] != '\0') return buf; for (size_t i = 0, pos = 0, n = map_len(); i < n; i++) { const char *entry = log_level_map[i].name; const char *delim = (i + 1 < n) ? ", " : ""; pos += snprintf(buf + pos, sizeof(buf) - pos, "'%s'%s", entry, delim); } return buf; } yambar-1.11.0/log.h000066400000000000000000000044651460770427600140250ustar00rootroot00000000000000#pragma once #include #include enum log_colorize { LOG_COLORIZE_NEVER, LOG_COLORIZE_ALWAYS, LOG_COLORIZE_AUTO }; enum log_facility { LOG_FACILITY_USER, LOG_FACILITY_DAEMON }; enum log_class { LOG_CLASS_NONE, LOG_CLASS_ERROR, LOG_CLASS_WARNING, LOG_CLASS_INFO, LOG_CLASS_DEBUG }; void log_init(enum log_colorize colorize, bool do_syslog, enum log_facility syslog_facility, enum log_class log_level); void log_deinit(void); void log_msg(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) __attribute__((format(printf, 5, 6))); void log_errno(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, ...) __attribute__((format(printf, 5, 6))); void log_errno_provided(enum log_class log_class, const char *module, const char *file, int lineno, int _errno, const char *fmt, ...) __attribute__((format(printf, 6, 7))); void log_msg_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va) __attribute__((format(printf, 5, 0))); void log_errno_va(enum log_class log_class, const char *module, const char *file, int lineno, const char *fmt, va_list va) __attribute__((format(printf, 5, 0))); void log_errno_provided_va(enum log_class log_class, const char *module, const char *file, int lineno, int _errno, const char *fmt, va_list va) __attribute__((format(printf, 6, 0))); int log_level_from_string(const char *str); const char *log_level_string_hint(void); #define LOG_ERR(...) log_msg(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #define LOG_ERRNO(...) log_errno(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #define LOG_ERRNO_P(_errno, ...) \ log_errno_provided(LOG_CLASS_ERROR, LOG_MODULE, __FILE__, __LINE__, _errno, __VA_ARGS__) #define LOG_WARN(...) log_msg(LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #define LOG_INFO(...) log_msg(LOG_CLASS_INFO, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG #define LOG_DBG(...) log_msg(LOG_CLASS_DEBUG, LOG_MODULE, __FILE__, __LINE__, __VA_ARGS__) #else #define LOG_DBG(...) #endif yambar-1.11.0/main.c000066400000000000000000000253471460770427600141650ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bar/bar.h" #include "config.h" #include "yml.h" #define LOG_MODULE "main" #include "log.h" #include "version.h" static volatile sig_atomic_t aborted = 0; static void signal_handler(int signo) { aborted = signo; } static char * get_config_path_user_config(void) { struct passwd *passwd = getpwuid(getuid()); if (passwd == NULL) { LOG_ERRNO("failed to lookup user"); return NULL; } const char *home_dir = passwd->pw_dir; LOG_DBG("user's home directory: %s", home_dir); int len = snprintf(NULL, 0, "%s/.config/yambar/config.yml", home_dir); char *path = malloc(len + 1); snprintf(path, len + 1, "%s/.config/yambar/config.yml", home_dir); return path; } static char * get_config_path_xdg(void) { const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home == NULL) return NULL; int len = snprintf(NULL, 0, "%s/yambar/config.yml", xdg_config_home); char *path = malloc(len + 1); snprintf(path, len + 1, "%s/yambar/config.yml", xdg_config_home); return path; } static char * get_config_path(void) { struct stat st; char *config = get_config_path_xdg(); if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode)) return config; free(config); /* 'Default' XDG_CONFIG_HOME */ config = get_config_path_user_config(); if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode)) return config; free(config); return NULL; } static struct bar * load_bar(const char *config_path, enum bar_backend backend) { FILE *conf_file = fopen(config_path, "r"); if (conf_file == NULL) { LOG_ERRNO("%s: failed to open", config_path); return NULL; } struct bar *bar = NULL; char *yml_error = NULL; struct yml_node *conf = yml_load(conf_file, &yml_error); if (conf == NULL) { LOG_ERR("%s:%s", config_path, yml_error); goto out; } const struct yml_node *bar_conf = yml_get_value(conf, "bar"); if (bar_conf == NULL) { LOG_ERR("%s: missing required top level key 'bar'", config_path); goto out; } bar = conf_to_bar(bar_conf, backend); if (bar == NULL) { LOG_ERR("%s: failed to load configuration", config_path); goto out; } out: free(yml_error); yml_destroy(conf); fclose(conf_file); return bar; } static void print_usage(const char *prog_name) { printf("Usage: %s [OPTION]...\n", prog_name); printf("\n"); printf("Options:\n"); printf(" -b,--backend={xcb,wayland,auto} backend to use (default: auto)\n" " -c,--config=FILE alternative configuration file\n" " -C,--validate verify configuration then quit\n" " -p,--print-pid=FILE|FD print PID to file or FD\n" " -d,--log-level={info|warning|error|none} log level (warning)\n" " -l,--log-colorize=[never|always|auto] enable/disable colorization of log output on stderr\n" " -s,--log-no-syslog disable syslog logging\n" " -v,--version show the version number and quit\n"); } static bool print_pid(const char *pid_file, bool *unlink_at_exit) { LOG_DBG("printing PID to %s", pid_file); errno = 0; char *end; int pid_fd = strtoul(pid_file, &end, 10); if (errno != 0 || *end != '\0') { if ((pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) { LOG_ERRNO("%s: failed to open", pid_file); return false; } else *unlink_at_exit = true; } if (pid_fd >= 0) { char pid[32]; snprintf(pid, sizeof(pid), "%u\n", getpid()); ssize_t bytes = write(pid_fd, pid, strlen(pid)); close(pid_fd); if (bytes < 0) { LOG_ERRNO("failed to write PID to FD=%u", pid_fd); return false; } LOG_DBG("wrote %zd bytes to FD=%d", bytes, pid_fd); return true; } else return false; } int main(int argc, char *const *argv) { static const struct option longopts[] = { {"backend", required_argument, 0, 'b'}, {"config", required_argument, 0, 'c'}, {"validate", no_argument, 0, 'C'}, {"print-pid", required_argument, 0, 'p'}, {"log-level", required_argument, 0, 'd'}, {"log-colorize", optional_argument, 0, 'l'}, {"log-no-syslog", no_argument, 0, 's'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {NULL, no_argument, 0, 0}, }; bool unlink_pid_file = false; const char *pid_file = NULL; bool verify_config = false; char *config_path = NULL; enum bar_backend backend = BAR_BACKEND_AUTO; enum log_class log_level = LOG_CLASS_WARNING; enum log_colorize log_colorize = LOG_COLORIZE_AUTO; bool log_syslog = true; while (true) { int c = getopt_long(argc, argv, ":b:c:Cp:d:l::svh", longopts, NULL); if (c == -1) break; switch (c) { case 'b': if (strcmp(optarg, "xcb") == 0) backend = BAR_BACKEND_XCB; else if (strcmp(optarg, "wayland") == 0) backend = BAR_BACKEND_WAYLAND; else { fprintf(stderr, "%s: invalid backend\n", optarg); return EXIT_FAILURE; } break; case 'c': { struct stat st; if (stat(optarg, &st) == -1) { fprintf(stderr, "%s: invalid configuration file: %s\n", optarg, strerror(errno)); return EXIT_FAILURE; } else if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) { fprintf(stderr, "%s: invalid configuration file: neither a regular file nor a pipe or FIFO\n", optarg); return EXIT_FAILURE; } config_path = strdup(optarg); break; } case 'C': verify_config = true; break; case 'p': pid_file = optarg; break; case 'd': { int lvl = log_level_from_string(optarg); if (lvl < 0) { fprintf(stderr, "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint()); return EXIT_FAILURE; } log_level = lvl; break; } case 'l': if (optarg == NULL || strcmp(optarg, "auto") == 0) log_colorize = LOG_COLORIZE_AUTO; else if (strcmp(optarg, "never") == 0) log_colorize = LOG_COLORIZE_NEVER; else if (strcmp(optarg, "always") == 0) log_colorize = LOG_COLORIZE_ALWAYS; else { fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg); return EXIT_FAILURE; } break; case 's': log_syslog = false; break; case 'v': printf("yambar version %s\n", YAMBAR_VERSION); return EXIT_SUCCESS; case 'h': print_usage(argv[0]); return EXIT_SUCCESS; case ':': fprintf(stderr, "error: -%c: missing required argument\n", optopt); return EXIT_FAILURE; case '?': fprintf(stderr, "error: -%c: invalid option\n", optopt); return EXIT_FAILURE; } } log_init(log_colorize, log_syslog, LOG_FACILITY_DAEMON, log_level); _Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR, "fcft log level enum offset"); _Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS, "fcft colorize enum mismatch"); fcft_init((enum fcft_log_colorize)log_colorize, log_syslog, (enum fcft_log_class)log_level); atexit(&fcft_fini); const struct sigaction sa = {.sa_handler = &signal_handler}; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); /* Block SIGINT (this is under the assumption that threads inherit * the signal mask */ sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, SIGINT); sigaddset(&signal_mask, SIGTERM); pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); int abort_fd = eventfd(0, EFD_CLOEXEC); if (abort_fd == -1) { LOG_ERRNO("failed to create eventfd (for abort signalling)"); log_deinit(); return 1; } if (config_path == NULL) { config_path = get_config_path(); if (config_path == NULL) { LOG_ERR("could not find a configuration (see man 5 yambar)"); log_deinit(); return 1; } } struct bar *bar = load_bar(config_path, backend); free(config_path); if (bar == NULL) { close(abort_fd); log_deinit(); return 1; } if (verify_config) { bar->destroy(bar); close(abort_fd); log_deinit(); return 0; } setlocale(LC_ALL, ""); bar->abort_fd = abort_fd; thrd_t bar_thread; thrd_create(&bar_thread, (int (*)(void *))bar->run, bar); /* Now unblock. We should be only thread receiving SIGINT/SIGTERM */ pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); if (pid_file != NULL) { if (!print_pid(pid_file, &unlink_pid_file)) goto done; } while (!aborted) { struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (fds[0].revents & (POLLIN | POLLHUP)) { /* * Either the bar aborted (triggering the abort_fd), or user * killed us (triggering the signal handler which sets * 'aborted') */ assert(aborted || r == 1); break; } } if (aborted) LOG_INFO("aborted: %s (%ld)", strsignal(aborted), (long)aborted); done: /* Signal abort to other threads */ if (write(abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) LOG_ERRNO("failed to signal abort to threads"); int res; int r = thrd_join(bar_thread, &res); if (r != 0) LOG_ERRNO_P(r, "failed to join bar thread"); bar->destroy(bar); close(abort_fd); if (unlink_pid_file) unlink(pid_file); log_deinit(); return res; } yambar-1.11.0/meson.build000066400000000000000000000141631460770427600152310ustar00rootroot00000000000000project('yambar', 'c', version: '1.11.0', license: 'MIT', meson_version: '>=0.59.0', default_options: ['c_std=c18', 'warning_level=1', 'werror=true', 'b_ndebug=if-release']) is_debug_build = get_option('buildtype').startswith('debug') plugs_as_libs = get_option('core-plugins-as-shared-libraries') cc = meson.get_compiler('c') if cc.has_function('memfd_create') add_project_arguments('-DMEMFD_CREATE', language: 'c') endif # Compute the relative path used by compiler invocations. source_root = meson.current_source_dir().split('/') build_root = meson.global_build_root().split('/') relative_dir_parts = [] i = 0 in_prefix = true foreach p : build_root if i >= source_root.length() or not in_prefix or p != source_root[i] in_prefix = false relative_dir_parts += '..' endif i += 1 endforeach i = 0 in_prefix = true foreach p : source_root if i >= build_root.length() or not in_prefix or build_root[i] != p in_prefix = false relative_dir_parts += p endif i += 1 endforeach relative_dir = join_paths(relative_dir_parts) + '/' if cc.has_argument('-fmacro-prefix-map=/foo=') add_project_arguments('-fmacro-prefix-map=@0@='.format(relative_dir), language: 'c') endif # Common dependencies dl = cc.find_library('dl') m = cc.find_library('m') threads = [dependency('threads'), cc.find_library('stdthreads', required: false)] libepoll = dependency('epoll-shim', required: false) libinotify = dependency('libinotify', required: false) pixman = dependency('pixman-1') yaml = dependency('yaml-0.1') # X11/XCB dependencies xcb_aux = dependency('xcb-aux', required: get_option('backend-x11')) xcb_cursor = dependency('xcb-cursor', required: get_option('backend-x11')) xcb_event = dependency('xcb-event', required: get_option('backend-x11')) xcb_ewmh = dependency('xcb-ewmh', required: get_option('backend-x11')) xcb_randr = dependency('xcb-randr', required: get_option('backend-x11')) xcb_render = dependency('xcb-render', required: get_option('backend-x11')) xcb_errors = dependency('xcb-errors', required: false) backend_x11 = xcb_aux.found() and xcb_cursor.found() and xcb_event.found() and \ xcb_ewmh.found() and xcb_randr.found() and xcb_render.found() # Wayland dependencies wayland_client = dependency('wayland-client', required: get_option('backend-wayland')) wayland_cursor = dependency('wayland-cursor', required: get_option('backend-wayland')) backend_wayland = wayland_client.found() and wayland_cursor.found() # "My" dependencies, fallback to subproject tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft') add_project_arguments( ['-D_GNU_SOURCE'] + (is_debug_build ? ['-D_DEBUG'] : []) + (backend_x11 ? ['-DENABLE_X11'] : []) + (backend_wayland ? ['-DENABLE_WAYLAND'] : []) + (plugs_as_libs ? ['-DCORE_PLUGINS_AS_SHARED_LIBRARIES'] : []), language: 'c', ) if backend_x11 xcb_stuff_lib = static_library( 'xcb-stuff', 'xcb.c', 'xcb.h', dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr, xcb_render, xcb_errors], c_args: xcb_errors.found() ? '-DHAVE_XCB_ERRORS' : [], pic: plugs_as_libs) xcb_stuff = declare_dependency( link_with: xcb_stuff_lib, dependencies: [xcb_aux, xcb_cursor, xcb_event, xcb_ewmh, xcb_randr, xcb_render, xcb_errors], ) install_headers('xcb.h', subdir: 'yambar') endif subdir('completions') subdir('bar') subdir('decorations') subdir('particles') subdir('modules') subdir('doc') env = find_program('env', native: true) generate_version_sh = files('generate-version.sh') version = custom_target( 'generate_version', build_always_stale: true, output: 'version.h', command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@']) yambar = executable( 'yambar', 'char32.c', 'char32.h', 'color.h', 'config-verify.c', 'config-verify.h', 'config.c', 'config.h', 'decoration.h', 'font-shaping.h', 'log.c', 'log.h', 'main.c', 'module.c', 'module.h', 'particle.c', 'particle.h', 'plugin.c', 'plugin.h', 'tag.c', 'tag.h', 'yml.c', 'yml.h', version, dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] + decorations + particles + modules, build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', export_dynamic: true, install: true, install_rpath: '$ORIGIN/../' + get_option('libdir') + '/yambar') install_data( 'LICENSE', 'README.md', install_dir: join_paths(get_option('datadir'), 'doc', 'yambar')) install_data('yambar.desktop', install_dir: join_paths(get_option('datadir'), 'applications')) subdir('test') install_headers( 'color.h', 'config.h', 'config-verify.h', 'decoration.h', 'log.h', 'module.h', 'particle.h', 'stride.h', 'tag.h', 'yml.h', subdir: 'yambar') summary( { 'Build type': get_option('buildtype'), 'XCB backend': backend_x11, 'Wayland backend': backend_wayland, 'Core modules as plugins': plugs_as_libs, }, bool_yn: true ) summary( { 'ALSA': plugin_alsa_enabled, 'Backlight': plugin_backlight_enabled, 'Battery': plugin_battery_enabled, 'Clock': plugin_clock_enabled, 'CPU monitoring': plugin_cpu_enabled, 'Disk I/O monitoring': plugin_disk_io_enabled, 'dwl (dwm for Wayland)': plugin_dwl_enabled, 'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled, 'Memory monitoring': plugin_mem_enabled, 'Music Player Daemon (MPD)': plugin_mpd_enabled, 'i3+Sway': plugin_i3_enabled, 'Label': plugin_label_enabled, 'Network monitoring': plugin_network_enabled, 'Pipewire': plugin_pipewire_enabled, 'PulseAudio': plugin_pulse_enabled, 'Removables monitoring': plugin_removables_enabled, 'River': plugin_river_enabled, 'Script': plugin_script_enabled, 'Sway XKB keyboard': plugin_sway_xkb_enabled, 'XKB keyboard (for X11)': plugin_xkb_enabled, 'XWindow (window tracking for X11)': plugin_xwindow_enabled, }, section: 'Optional modules', bool_yn: true ) yambar-1.11.0/meson_options.txt000066400000000000000000000047711460770427600165300ustar00rootroot00000000000000option( 'backend-x11', type: 'feature', value: 'enabled', description: 'XCB (X11) backend') option( 'backend-wayland', type: 'feature', value: 'enabled', description: 'Wayland backend') option( 'core-plugins-as-shared-libraries', type: 'boolean', value: false, description: 'Compiles modules, particles and decorations as shared libraries, which are loaded on-demand') option('plugin-alsa', type: 'feature', value: 'auto', description: 'ALSA support') option('plugin-backlight', type: 'feature', value: 'auto', description: 'Backlight support') option('plugin-battery', type: 'feature', value: 'auto', description: 'Battery support') option('plugin-clock', type: 'feature', value: 'auto', description: 'Clock support') option('plugin-cpu', type: 'feature', value: 'auto', description: 'CPU monitoring support') option('plugin-disk-io', type: 'feature', value: 'auto', description: 'Disk I/O support') option('plugin-dwl', type: 'feature', value: 'auto', description: 'dwl (dwm for wayland) support') option('plugin-foreign-toplevel', type: 'feature', value: 'auto', description: 'Foreign toplevel (window tracking for Wayland) support') option('plugin-mem', type: 'feature', value: 'auto', description: 'Memory monitoring support') option('plugin-mpd', type: 'feature', value: 'auto', description: 'Music Player Daemon (MPD) support') option('plugin-i3', type: 'feature', value: 'auto', description: 'i3+Sway support') option('plugin-label', type: 'feature', value: 'auto', description: 'Label support') option('plugin-network', type: 'feature', value: 'auto', description: 'Network monitoring support') option('plugin-pipewire', type: 'feature', value: 'auto', description: 'Pipewire support') option('plugin-pulse', type: 'feature', value: 'auto', description: 'PulseAudio support') option('plugin-removables', type: 'feature', value: 'auto', description: 'Removables (USB sticks, CD-ROM etc) monitoring support') option('plugin-river', type: 'feature', value: 'auto', description: 'River support') option('plugin-script', type: 'feature', value: 'auto', description: 'Script support') option('plugin-sway-xkb', type: 'feature', value: 'auto', description: 'keyboard support for Sway') option('plugin-xkb', type: 'feature', value: 'auto', description: 'keyboard support for X11') option('plugin-xwindow', type: 'feature', value: 'auto', description: 'XWindow (window tracking for X11) support') yambar-1.11.0/module.c000066400000000000000000000010041460770427600145060ustar00rootroot00000000000000#include "module.h" #include #include #include struct module * module_common_new(void) { struct module *mod = calloc(1, sizeof(*mod)); mtx_init(&mod->lock, mtx_plain); mod->destroy = &module_default_destroy; return mod; } void module_default_destroy(struct module *mod) { mtx_destroy(&mod->lock); free(mod); } struct exposable * module_begin_expose(struct module *mod) { struct exposable *e = mod->content(mod); e->begin_expose(e); return e; } yambar-1.11.0/module.h000066400000000000000000000027021460770427600145210ustar00rootroot00000000000000#pragma once #include #include "particle.h" struct bar; struct module { const struct bar *bar; int abort_fd; mtx_t lock; void *private; int (*run)(struct module *mod); void (*destroy)(struct module *module); /* * Called by module_begin_expose(). Should return an * exposable (an instantiated particle). */ struct exposable *(*content)(struct module *mod); /* refresh_in() should schedule a module content refresh after the * specified number of milliseconds */ bool (*refresh_in)(struct module *mod, long milli_seconds); const char *(*description)(const struct module *mod); }; struct module *module_common_new(void); void module_default_destroy(struct module *mod); struct exposable *module_begin_expose(struct module *mod); /* List of attributes *all* modules implement */ #define MODULE_COMMON_ATTRS \ {"content", true, &conf_verify_particle}, {"anchors", false, NULL}, {"font", false, &conf_verify_font}, \ {"foreground", false, &conf_verify_color}, \ { \ NULL, false, NULL \ } yambar-1.11.0/modules/000077500000000000000000000000001460770427600145325ustar00rootroot00000000000000yambar-1.11.0/modules/alsa.c000066400000000000000000000512331460770427600156220ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "alsa" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" enum channel_type { CHANNEL_PLAYBACK, CHANNEL_CAPTURE }; struct channel { snd_mixer_selem_channel_id_t id; enum channel_type type; char *name; bool use_db; long vol_cur; long db_cur; bool muted; }; struct private { char *card; char *mixer; char *volume_name; char *muted_name; struct particle *label; tll(struct channel) channels; bool online; bool has_playback_volume; long playback_vol_min; long playback_vol_max; bool has_playback_db; long playback_db_min; long playback_db_max; bool has_capture_volume; long capture_vol_min; long capture_vol_max; long has_capture_db; long capture_db_min; long capture_db_max; const struct channel *volume_chan; const struct channel *muted_chan; }; static void channel_free(struct channel *chan) { free(chan->name); } static void destroy(struct module *mod) { struct private *m = mod->private; tll_foreach(m->channels, it) { channel_free(&it->item); tll_remove(m->channels, it); } m->label->destroy(m->label); free(m->card); free(m->mixer); free(m->volume_name); free(m->muted_name); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { static char desc[32]; const struct private *m = mod->private; snprintf(desc, sizeof(desc), "alsa(%s)", m->card); return desc; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); const struct channel *volume_chan = m->volume_chan; const struct channel *muted_chan = m->muted_chan; bool muted = muted_chan != NULL ? muted_chan->muted : false; long vol_min = 0, vol_max = 0, vol_cur = 0; long db_min = 0, db_max = 0, db_cur = 0; bool use_db = false; if (volume_chan != NULL) { if (volume_chan->type == CHANNEL_PLAYBACK) { db_min = m->playback_db_min; db_max = m->playback_db_max; vol_min = m->playback_vol_min; vol_max = m->playback_vol_max; } else { db_min = m->capture_db_min; db_max = m->capture_db_max; vol_min = m->capture_vol_min; vol_max = m->capture_vol_max; } vol_cur = volume_chan->vol_cur; db_cur = volume_chan->db_cur; use_db = volume_chan->use_db; } int percent; if (use_db) { bool use_linear = db_max - db_min <= 24 * 100; if (use_linear) { percent = db_min - db_max > 0 ? round(100. * (db_cur - db_min) / (db_max - db_min)) : 0; } else { double normalized = pow(10, (double)(db_cur - db_max) / 6000.); if (db_min != SND_CTL_TLV_DB_GAIN_MUTE) { double min_norm = pow(10, (double)(db_min - db_max) / 6000.); normalized = (normalized - min_norm) / (1. - min_norm); } percent = round(100. * normalized); } } else { percent = vol_max - vol_min > 0 ? round(100. * (vol_cur - vol_min) / (vol_max - vol_min)) : 0; } struct tag_set tags = { .tags = (struct tag *[]){ tag_new_bool(mod, "online", m->online), tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max), tag_new_int_range(mod, "dB", db_cur, db_min, db_max), tag_new_int_range(mod, "percent", percent, 0, 100), tag_new_bool(mod, "muted", muted), }, .count = 5, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static void update_state(struct module *mod, snd_mixer_elem_t *elem) { struct private *m = mod->private; mtx_lock(&mod->lock); /* If volume level can be changed (i.e. this isn't just a switch; * e.g. a digital channel), get current channel levels */ tll_foreach(m->channels, it) { struct channel *chan = &it->item; const bool has_volume = chan->type == CHANNEL_PLAYBACK ? m->has_playback_volume : m->has_capture_volume; const bool has_db = chan->type == CHANNEL_PLAYBACK ? m->has_playback_db : m->has_capture_db; if (!has_volume && !has_db) continue; if (has_db) { chan->use_db = true; const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_db_min : m->capture_db_min; const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_db_max : m->capture_db_max; assert(min <= max); int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_dB(elem, chan->id, &chan->db_cur) : snd_mixer_selem_get_capture_dB(elem, chan->id, &chan->db_cur); if (r < 0) { LOG_ERR("%s,%s: %s: failed to get current dB", m->card, m->mixer, chan->name); } if (chan->db_cur < min) { LOG_WARN("%s,%s: %s: current dB is less than the indicated minimum: " "%ld < %ld", m->card, m->mixer, chan->name, chan->db_cur, min); chan->db_cur = min; } if (chan->db_cur > max) { LOG_WARN("%s,%s: %s: current dB is greater than the indicated maximum: " "%ld > %ld", m->card, m->mixer, chan->name, chan->db_cur, max); chan->db_cur = max; } assert(chan->db_cur >= min); assert(chan->db_cur <= max); LOG_DBG("%s,%s: %s: dB: %ld", m->card, m->mixer, chan->name, chan->db_cur); } else chan->use_db = false; const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_min : m->capture_vol_min; const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_max : m->capture_vol_max; assert(min <= max); int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_volume(elem, chan->id, &chan->vol_cur) : snd_mixer_selem_get_capture_volume(elem, chan->id, &chan->vol_cur); if (r < 0) { LOG_ERR("%s,%s: %s: failed to get current volume", m->card, m->mixer, chan->name); } if (chan->vol_cur < min) { LOG_WARN("%s,%s: %s: current volume is less than the indicated minimum: " "%ld < %ld", m->card, m->mixer, chan->name, chan->vol_cur, min); chan->vol_cur = min; } if (chan->vol_cur > max) { LOG_WARN("%s,%s: %s: current volume is greater than the indicated maximum: " "%ld > %ld", m->card, m->mixer, chan->name, chan->vol_cur, max); chan->vol_cur = max; } assert(chan->vol_cur >= min); assert(chan->vol_cur <= max); LOG_DBG("%s,%s: %s: volume: %ld", m->card, m->mixer, chan->name, chan->vol_cur); } /* Get channels’ muted state */ tll_foreach(m->channels, it) { struct channel *chan = &it->item; int unmuted; int r = chan->type == CHANNEL_PLAYBACK ? snd_mixer_selem_get_playback_switch(elem, chan->id, &unmuted) : snd_mixer_selem_get_capture_switch(elem, chan->id, &unmuted); if (r < 0) { LOG_WARN("%s,%s: %s: failed to get muted state", m->card, m->mixer, chan->name); unmuted = 1; } chan->muted = !unmuted; LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted); } m->online = true; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } enum run_state { RUN_ERROR, RUN_FAILED_CONNECT, RUN_DISCONNECTED, RUN_DONE, }; static enum run_state run_while_online(struct module *mod) { struct private *m = mod->private; enum run_state ret = RUN_ERROR; /* Make sure we aren’t still tracking channels from previous connects */ tll_free(m->channels); snd_mixer_t *handle; if (snd_mixer_open(&handle, 0) != 0) { LOG_ERR("failed to open handle"); return ret; } if (snd_mixer_attach(handle, m->card) != 0 || snd_mixer_selem_register(handle, NULL, NULL) != 0 || snd_mixer_load(handle) != 0) { LOG_ERR("failed to attach to card"); ret = RUN_FAILED_CONNECT; goto err; } snd_mixer_selem_id_t *sid; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_name(sid, m->mixer); snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); if (elem == NULL) { LOG_ERR("failed to find mixer"); goto err; } /* Get playback volume range */ m->has_playback_volume = snd_mixer_selem_has_playback_volume(elem) > 0; if (m->has_playback_volume) { if (snd_mixer_selem_get_playback_volume_range(elem, &m->playback_vol_min, &m->playback_vol_max) < 0) { LOG_ERR("%s,%s: failed to get playback volume range", m->card, m->mixer); assert(m->playback_vol_min == 0); assert(m->playback_vol_max == 0); } if (m->playback_vol_min > m->playback_vol_max) { LOG_WARN("%s,%s: indicated minimum playback volume is greater than the " "maximum: %ld > %ld", m->card, m->mixer, m->playback_vol_min, m->playback_vol_max); m->playback_vol_min = m->playback_vol_max; } } if (snd_mixer_selem_get_playback_dB_range(elem, &m->playback_db_min, &m->playback_db_max) < 0) { LOG_WARN("%s,%s: failed to get playback dB range, " "will use raw volume values instead", m->card, m->mixer); m->has_playback_db = false; } else m->has_playback_db = true; /* Get capture volume range */ m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0; if (m->has_capture_volume) { if (snd_mixer_selem_get_capture_volume_range(elem, &m->capture_vol_min, &m->capture_vol_max) < 0) { LOG_ERR("%s,%s: failed to get capture volume range", m->card, m->mixer); assert(m->capture_vol_min == 0); assert(m->capture_vol_max == 0); } if (m->capture_vol_min > m->capture_vol_max) { LOG_WARN("%s,%s: indicated minimum capture volume is greater than the " "maximum: %ld > %ld", m->card, m->mixer, m->capture_vol_min, m->capture_vol_max); m->capture_vol_min = m->capture_vol_max; } } if (snd_mixer_selem_get_capture_dB_range(elem, &m->capture_db_min, &m->capture_db_max) < 0) { LOG_WARN("%s,%s: failed to get capture dB range, " "will use raw volume values instead", m->card, m->mixer); m->has_capture_db = false; } else m->has_capture_db = true; /* Get available channels */ for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) { bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1; bool is_capture = snd_mixer_selem_has_capture_channel(elem, i) == 1; if (is_playback || is_capture) { struct channel chan = { .id = i, .type = is_playback ? CHANNEL_PLAYBACK : CHANNEL_CAPTURE, .name = strdup(snd_mixer_selem_channel_name(i)), }; tll_push_back(m->channels, chan); } } if (tll_length(m->channels) == 0) { LOG_ERR("%s,%s: no channels", m->card, m->mixer); goto err; } char channels_str[1024]; int channels_idx = 0; tll_foreach(m->channels, it) { const struct channel *chan = &it->item; channels_idx += snprintf(&channels_str[channels_idx], sizeof(channels_str) - channels_idx, channels_idx == 0 ? "%s (%s)" : ", %s (%s)", chan->name, chan->type == CHANNEL_PLAYBACK ? "🔊" : "🎤"); assert(channels_idx <= sizeof(channels_str)); } LOG_INFO("%s,%s: channels: %s", m->card, m->mixer, channels_str); /* Verify volume/muted channel names are valid and exists */ bool volume_channel_is_valid = m->volume_name == NULL; bool muted_channel_is_valid = m->muted_name == NULL; tll_foreach(m->channels, it) { const struct channel *chan = &it->item; if (m->volume_name != NULL && strcmp(chan->name, m->volume_name) == 0) { m->volume_chan = chan; volume_channel_is_valid = true; } if (m->muted_name != NULL && strcmp(chan->name, m->muted_name) == 0) { m->muted_chan = chan; muted_channel_is_valid = true; } } if (m->volume_name == NULL) m->volume_chan = &tll_front(m->channels); if (m->muted_name == NULL) m->muted_chan = &tll_front(m->channels); if (!volume_channel_is_valid) { assert(m->volume_name != NULL); LOG_ERR("volume: invalid channel name: %s", m->volume_name); goto err; } if (!muted_channel_is_valid) { assert(m->muted_name != NULL); LOG_ERR("muted: invalid channel name: %s", m->muted_name); goto err; } /* Initial state */ update_state(mod, elem); LOG_INFO( "%s,%s: %s range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", m->card, m->mixer, m->volume_chan->use_db ? "dB" : "volume", (m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_min : m->playback_vol_min) : (m->volume_chan->use_db ? m->capture_db_min : m->capture_vol_min)), (m->volume_chan->type == CHANNEL_PLAYBACK ? (m->volume_chan->use_db ? m->playback_db_max : m->playback_vol_max) : (m->volume_chan->use_db ? m->capture_db_max : m->capture_vol_max)), m->volume_chan->use_db ? m->volume_chan->db_cur : m->volume_chan->vol_cur, m->muted_chan->muted ? " (muted)" : "", m->volume_chan->name, m->muted_chan->name); mod->bar->refresh(mod->bar); while (true) { int fd_count = snd_mixer_poll_descriptors_count(handle); assert(fd_count >= 1); struct pollfd fds[1 + fd_count]; fds[0] = (struct pollfd){.fd = mod->abort_fd, .events = POLLIN}; snd_mixer_poll_descriptors(handle, &fds[1], fd_count); int r = poll(fds, fd_count + 1, -1); if (r < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = RUN_DONE; break; } for (size_t i = 0; i < fd_count; i++) { if (fds[1 + i].revents & (POLLHUP | POLLERR | POLLNVAL)) { LOG_ERR("disconnected from alsa"); mtx_lock(&mod->lock); m->online = false; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); ret = RUN_DISCONNECTED; goto err; } } snd_mixer_handle_events(handle); update_state(mod, elem); } err: snd_mixer_close(handle); snd_config_update_free_global(); return ret; } static int run(struct module *mod) { int ret = 1; int ifd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (ifd < 0) { LOG_ERRNO("failed to inotify"); return 1; } int wd = inotify_add_watch(ifd, "/dev/snd", IN_CREATE); if (wd < 0) { LOG_ERRNO("failed to create inotify watcher for /dev/snd"); close(ifd); return 1; } while (true) { enum run_state state = run_while_online(mod); switch (state) { case RUN_DONE: ret = 0; goto out; case RUN_ERROR: ret = 1; goto out; case RUN_FAILED_CONNECT: break; case RUN_DISCONNECTED: /* * We’ve been connected - drain the watcher * * We don’t want old, un-releated events (for other * soundcards, for example) to trigger a storm of * re-connect attempts. */ while (true) { uint8_t buf[1024]; ssize_t amount = read(ifd, buf, sizeof(buf)); if (amount < 0) { if (errno == EAGAIN) break; LOG_ERRNO("failed to drain inotify watcher"); ret = 1; goto out; } if (amount == 0) break; } break; } bool have_create_event = false; while (!have_create_event) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = ifd, .events = POLLIN}}; int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (r < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); ret = 1; goto out; } if (fds[0].revents & (POLLIN | POLLHUP)) { ret = 0; goto out; } if (fds[1].revents & POLLHUP) { LOG_ERR("inotify socket closed"); ret = 1; goto out; } assert(fds[1].revents & POLLIN); while (true) { char buf[1024]; ssize_t len = read(ifd, buf, sizeof(buf)); if (len < 0) { if (errno == EAGAIN) break; LOG_ERRNO("failed to read inotify events"); ret = 1; goto out; } if (len == 0) break; /* Consume inotify data */ for (const char *ptr = buf; ptr < buf + len;) { const struct inotify_event *e = (const struct inotify_event *)ptr; if (e->mask & IN_CREATE) { LOG_DBG("inotify: CREATED: /dev/snd/%.*s", e->len, e->name); have_create_event = true; } ptr += sizeof(*e) + e->len; } } } } out: if (wd >= 0) inotify_rm_watch(ifd, wd); if (ifd >= 0) close(ifd); return ret; } static struct module * alsa_new(const char *card, const char *mixer, const char *volume_channel_name, const char *muted_channel_name, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->card = strdup(card); priv->mixer = strdup(mixer); priv->volume_name = volume_channel_name != NULL ? strdup(volume_channel_name) : NULL; priv->muted_name = muted_channel_name != NULL ? strdup(muted_channel_name) : NULL; struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *card = yml_get_value(node, "card"); const struct yml_node *mixer = yml_get_value(node, "mixer"); const struct yml_node *volume = yml_get_value(node, "volume"); const struct yml_node *muted = yml_get_value(node, "muted"); const struct yml_node *content = yml_get_value(node, "content"); return alsa_new(yml_value_as_string(card), yml_value_as_string(mixer), volume != NULL ? yml_value_as_string(volume) : NULL, muted != NULL ? yml_value_as_string(muted) : NULL, conf_to_particle(content, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"card", true, &conf_verify_string}, {"mixer", true, &conf_verify_string}, {"volume", false, &conf_verify_string}, {"muted", false, &conf_verify_string}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_alsa_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_alsa_iface"))); #endif yambar-1.11.0/modules/backlight.c000066400000000000000000000137541460770427600166400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "backlight" #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" struct private { struct particle *label; char *device; long max_brightness; long current_brightness; }; static void destroy(struct module *mod) { struct private *m = mod->private; free(m->device); m->label->destroy(m->label); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "backlight"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); const long current = m->current_brightness; const long max = m->max_brightness; const long percent = max > 0 ? round(100. * current / max) : 0; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_int_range(mod, "brightness", current, 0, max), tag_new_int_range(mod, "percent", percent, 0, 100), }, .count = 2, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static const char * readline_from_fd(int fd) { static char buf[4096]; ssize_t sz = read(fd, buf, sizeof(buf) - 1); lseek(fd, 0, SEEK_SET); if (sz < 0) { LOG_WARN("failed to read from FD=%d", fd); return NULL; } buf[sz] = '\0'; for (ssize_t i = sz - 1; i >= 0 && buf[i] == '\n'; sz--) buf[i] = '\0'; return buf; } static long readint_from_fd(int fd) { const char *s = readline_from_fd(fd); if (s == NULL) return 0; long ret; int r = sscanf(s, "%ld", &ret); if (r != 1) { LOG_WARN("failed to convert \"%s\" to an integer", s); return 0; } return ret; } static int initialize(struct private *m) { int backlight_fd = open("/sys/class/backlight", O_RDONLY); if (backlight_fd == -1) { LOG_ERRNO("/sys/class/backlight"); return -1; } int base_dir_fd = openat(backlight_fd, m->device, O_RDONLY); close(backlight_fd); if (base_dir_fd == -1) { LOG_ERRNO("/sys/class/backlight/%s", m->device); return -1; } int max_fd = openat(base_dir_fd, "max_brightness", O_RDONLY); if (max_fd == -1) { LOG_ERRNO("/sys/class/backlight/%s/max_brightness", m->device); close(base_dir_fd); return -1; } m->max_brightness = readint_from_fd(max_fd); close(max_fd); int current_fd = openat(base_dir_fd, "brightness", O_RDONLY); close(base_dir_fd); if (current_fd == -1) { LOG_ERRNO("/sys/class/backlight/%s/brightness", m->device); return -1; } m->current_brightness = readint_from_fd(current_fd); LOG_INFO("%s: brightness: %ld (max: %ld)", m->device, m->current_brightness, m->max_brightness); return current_fd; } static int run(struct module *mod) { const struct bar *bar = mod->bar; struct private *m = mod->private; int current_fd = initialize(m); if (current_fd == -1) return 1; struct udev *udev = udev_new(); struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev"); if (udev == NULL || mon == NULL) { LOG_ERR("failed to initialize udev monitor"); if (udev == NULL) udev_unref(udev); close(current_fd); return 1; } udev_monitor_filter_add_match_subsystem_devtype(mon, "backlight", NULL); udev_monitor_enable_receiving(mon); bar->refresh(bar); int ret = 1; while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = udev_monitor_get_fd(mon), .events = POLLIN}, }; if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = 0; break; } struct udev_device *dev = udev_monitor_receive_device(mon); if (dev == NULL) continue; const char *sysname = udev_device_get_sysname(dev); bool is_us = sysname != NULL && strcmp(sysname, m->device) == 0; udev_device_unref(dev); if (!is_us) continue; mtx_lock(&mod->lock); m->current_brightness = readint_from_fd(current_fd); mtx_unlock(&mod->lock); bar->refresh(bar); } udev_monitor_unref(mon); udev_unref(udev); close(current_fd); return ret; } static struct module * backlight_new(const char *device, struct particle *label) { struct private *m = calloc(1, sizeof(*m)); m->label = label; m->device = strdup(device); struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *name = yml_get_value(node, "name"); const struct yml_node *c = yml_get_value(node, "content"); return backlight_new(yml_value_as_string(name), conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"name", true, &conf_verify_string}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_backlight_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_backlight_iface"))); #endif yambar-1.11.0/modules/battery.c000066400000000000000000000465721460770427600163660ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "battery" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" #define max(x, y) ((x) > (y) ? (x) : (y)) static const long min_poll_interval = 250; static const long default_poll_interval = 60 * 1000; static const long one_sec_in_ns = 1000000000; enum state { STATE_FULL, STATE_NOTCHARGING, STATE_CHARGING, STATE_DISCHARGING, STATE_UNKNOWN }; struct current_state { long ema; long current; struct timespec time; }; struct private { struct particle *label; long poll_interval; int battery_scale; long smoothing_scale; char *battery; char *manufacturer; char *model; long energy_full_design; long energy_full; long charge_full_design; long charge_full; enum state state; long capacity; long energy; long power; long charge; struct current_state ema_current; long time_to_empty; long time_to_full; }; static int64_t difftimespec_ns(const struct timespec after, const struct timespec before) { return ((int64_t)after.tv_sec - (int64_t)before.tv_sec) * (int64_t)one_sec_in_ns + ((int64_t)after.tv_nsec - (int64_t)before.tv_nsec); } // Linear Exponential Moving Average (unevenly spaced time series) // http://www.eckner.com/papers/Algorithms%20for%20Unevenly%20Spaced%20Time%20Series.pdf // Adapted from: https://github.com/andreas50/utsAlgorithms/blob/master/ema.c static void ema_linear(struct current_state *state, struct current_state curr, long tau) { double w, w2, tmp; if (state->current == -1) { *state = curr; return; } long time = difftimespec_ns(curr.time, state->time); tmp = time / (double)tau; w = exp(-tmp); if (tmp > 1e-6) { w2 = (1 - w) / tmp; } else { // Use taylor expansion for numerical stability w2 = 1 - tmp / 2 + tmp * tmp / 6 - tmp * tmp * tmp / 24; } double ema = state->ema * w + curr.current * (1 - w2) + state->current * (w2 - w); state->ema = ema; state->current = curr.current; state->time = curr.time; LOG_DBG("ema current: %ld", (long)ema); } static void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res) { res->tv_sec = a->tv_sec - b->tv_sec; res->tv_nsec = a->tv_nsec - b->tv_nsec; /* tv_nsec may be negative */ if (res->tv_nsec < 0) { res->tv_sec--; res->tv_nsec += one_sec_in_ns; } } static void destroy(struct module *mod) { struct private *m = mod->private; free(m->battery); free(m->manufacturer); free(m->model); m->label->destroy(m->label); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { static char desc[32]; const struct private *m = mod->private; snprintf(desc, sizeof(desc), "bat(%s)", m->battery); return desc; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); assert(m->state == STATE_FULL || m->state == STATE_NOTCHARGING || m->state == STATE_CHARGING || m->state == STATE_DISCHARGING || m->state == STATE_UNKNOWN); unsigned long hours; unsigned long minutes; if (m->time_to_empty > 0) { minutes = m->time_to_empty / 60; hours = minutes / 60; minutes = minutes % 60; } else if (m->time_to_full > 0) { minutes = m->time_to_full / 60; hours = minutes / 60; minutes = minutes % 60; } else if (m->energy_full >= 0 && m->charge && m->power >= 0) { unsigned long energy = m->state == STATE_CHARGING ? m->energy_full - m->energy : m->energy; double hours_as_float; if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) hours_as_float = 0.0; else if (m->power > 0) hours_as_float = (double)energy / m->power; else hours_as_float = 99.0; hours = hours_as_float; minutes = (hours_as_float - (double)hours) * 60; } else if (m->charge_full >= 0 && m->charge >= 0 && m->ema_current.current >= 0) { unsigned long charge = m->state == STATE_CHARGING ? m->charge_full - m->charge : m->charge; double hours_as_float; if (m->state == STATE_FULL || m->state == STATE_NOTCHARGING) hours_as_float = 0.0; else if (m->ema_current.current > 0) hours_as_float = (double)charge / m->ema_current.current; else hours_as_float = 99.0; hours = hours_as_float; minutes = (hours_as_float - (double)hours) * 60; } else { hours = 99; minutes = 0; } char estimate[64]; snprintf(estimate, sizeof(estimate), "%02lu:%02lu", hours, minutes); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", m->battery), tag_new_string(mod, "manufacturer", m->manufacturer), tag_new_string(mod, "model", m->model), tag_new_string(mod, "state", m->state == STATE_FULL ? "full" : m->state == STATE_NOTCHARGING ? "not charging" : m->state == STATE_CHARGING ? "charging" : m->state == STATE_DISCHARGING ? "discharging" : "unknown"), tag_new_int_range(mod, "capacity", m->capacity, 0, 100), tag_new_string(mod, "estimate", estimate), }, .count = 6, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static const char * readline_from_fd(int fd, size_t sz, char buf[static sz]) { ssize_t bytes = read(fd, buf, sz - 1); lseek(fd, 0, SEEK_SET); if (bytes < 0) { LOG_WARN("failed to read from FD=%d", fd); return NULL; } buf[bytes] = '\0'; for (ssize_t i = bytes - 1; i >= 0 && buf[i] == '\n'; bytes--) buf[i] = '\0'; return buf; } static long readint_from_fd(int fd) { char buf[512]; const char *s = readline_from_fd(fd, sizeof(buf), buf); if (s == NULL) return 0; long ret; int r = sscanf(s, "%ld", &ret); if (r != 1) { LOG_WARN("failed to convert \"%s\" to an integer", s); return 0; } return ret; } static bool initialize(struct private *m) { char line_buf[512]; int pw_fd = open("/sys/class/power_supply", O_RDONLY); if (pw_fd < 0) { LOG_ERRNO("/sys/class/power_supply"); return false; } int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY); close(pw_fd); if (base_dir_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s", m->battery); return false; } { int fd = openat(base_dir_fd, "manufacturer", O_RDONLY); if (fd == -1) { LOG_WARN("/sys/class/power_supply/%s/manufacturer: %s", m->battery, strerror(errno)); m->manufacturer = NULL; } else { m->manufacturer = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf)); close(fd); } } { int fd = openat(base_dir_fd, "model_name", O_RDONLY); if (fd == -1) { LOG_WARN("/sys/class/power_supply/%s/model_name: %s", m->battery, strerror(errno)); m->model = NULL; } else { m->model = strdup(readline_from_fd(fd, sizeof(line_buf), line_buf)); close(fd); } } if (faccessat(base_dir_fd, "energy_full_design", O_RDONLY, 0) == 0 && faccessat(base_dir_fd, "energy_full", O_RDONLY, 0) == 0) { { int fd = openat(base_dir_fd, "energy_full_design", O_RDONLY); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/energy_full_design", m->battery); goto err; } m->energy_full_design = readint_from_fd(fd); close(fd); } { int fd = openat(base_dir_fd, "energy_full", O_RDONLY); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/energy_full", m->battery); goto err; } m->energy_full = readint_from_fd(fd); close(fd); } } else { m->energy_full = m->energy_full_design = -1; } if (faccessat(base_dir_fd, "charge_full_design", O_RDONLY, 0) == 0 && faccessat(base_dir_fd, "charge_full", O_RDONLY, 0) == 0) { { int fd = openat(base_dir_fd, "charge_full_design", O_RDONLY); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/charge_full_design", m->battery); goto err; } m->charge_full_design = readint_from_fd(fd) / m->battery_scale; close(fd); } { int fd = openat(base_dir_fd, "charge_full", O_RDONLY); if (fd == -1) { LOG_ERRNO("/sys/class/power_supply/%s/charge_full", m->battery); goto err; } m->charge_full = readint_from_fd(fd) / m->battery_scale; close(fd); } } else { m->charge_full = m->charge_full_design = -1; } close(base_dir_fd); return true; err: close(base_dir_fd); return false; } static bool update_status(struct module *mod) { struct private *m = mod->private; int pw_fd = open("/sys/class/power_supply", O_RDONLY); if (pw_fd < 0) { LOG_ERRNO("/sys/class/power_supply"); return false; } int base_dir_fd = openat(pw_fd, m->battery, O_RDONLY); close(pw_fd); if (base_dir_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s", m->battery); return false; } int status_fd = openat(base_dir_fd, "status", O_RDONLY); if (status_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s/status", m->battery); close(base_dir_fd); return false; } int capacity_fd = openat(base_dir_fd, "capacity", O_RDONLY); if (capacity_fd < 0) { LOG_ERRNO("/sys/class/power_supply/%s/capacity", m->battery); close(status_fd); close(base_dir_fd); return false; } int energy_fd = openat(base_dir_fd, "energy_now", O_RDONLY); int power_fd = openat(base_dir_fd, "power_now", O_RDONLY); int charge_fd = openat(base_dir_fd, "charge_now", O_RDONLY); int current_fd = openat(base_dir_fd, "current_now", O_RDONLY); int time_to_empty_fd = openat(base_dir_fd, "time_to_empty_now", O_RDONLY); int time_to_full_fd = openat(base_dir_fd, "time_to_full_now", O_RDONLY); long capacity = readint_from_fd(capacity_fd); long energy = energy_fd >= 0 ? readint_from_fd(energy_fd) : -1; long power = power_fd >= 0 ? readint_from_fd(power_fd) : -1; long charge = charge_fd >= 0 ? readint_from_fd(charge_fd) : -1; long current = current_fd >= 0 ? readint_from_fd(current_fd) : -1; long time_to_empty = time_to_empty_fd >= 0 ? readint_from_fd(time_to_empty_fd) : -1; long time_to_full = time_to_full_fd >= 0 ? readint_from_fd(time_to_full_fd) : -1; if (charge >= -1) { charge /= m->battery_scale; } char buf[512]; const char *status = readline_from_fd(status_fd, sizeof(buf), buf); if (status_fd >= 0) close(status_fd); if (capacity_fd >= 0) close(capacity_fd); if (energy_fd >= 0) close(energy_fd); if (power_fd >= 0) close(power_fd); if (charge_fd >= 0) close(charge_fd); if (current_fd >= 0) close(current_fd); if (time_to_empty_fd >= 0) close(time_to_empty_fd); if (time_to_full_fd >= 0) close(time_to_full_fd); if (base_dir_fd >= 0) close(base_dir_fd); enum state state; if (status == NULL) { LOG_WARN("failed to read battery state"); state = STATE_UNKNOWN; } else if (strcmp(status, "Full") == 0) state = STATE_FULL; else if (strcmp(status, "Not charging") == 0) state = STATE_NOTCHARGING; else if (strcmp(status, "Charging") == 0) state = STATE_CHARGING; else if (strcmp(status, "Discharging") == 0) state = STATE_DISCHARGING; else if (strcmp(status, "Unknown") == 0) state = STATE_UNKNOWN; else { LOG_ERR("unrecognized battery state: %s", status); state = STATE_UNKNOWN; } LOG_DBG("capacity: %ld, energy: %ld, power: %ld, charge=%ld, current=%ld, " "time-to-empty: %ld, time-to-full: %ld", capacity, energy, power, charge, current, time_to_empty, time_to_full); mtx_lock(&mod->lock); if (m->state != state) { m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}}; } m->state = state; m->capacity = capacity; m->energy = energy; m->power = power; m->charge = charge; if (current != -1) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); ema_linear(&m->ema_current, (struct current_state){current, current, t}, m->smoothing_scale); } m->time_to_empty = time_to_empty; m->time_to_full = time_to_full; mtx_unlock(&mod->lock); return true; } static int run(struct module *mod) { const struct bar *bar = mod->bar; struct private *m = mod->private; if (!initialize(m)) return -1; LOG_INFO("%s: %s %s (at %.1f%% of original capacity)", m->battery, m->manufacturer, m->model, (m->energy_full > 0 ? 100.0 * m->energy_full / m->energy_full_design : m->charge_full > 0 ? 100.0 * m->charge_full / m->charge_full_design : 0.0)); int ret = 1; struct udev *udev = udev_new(); struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev"); if (udev == NULL || mon == NULL) goto out; udev_monitor_filter_add_match_subsystem_devtype(mon, "power_supply", NULL); udev_monitor_enable_receiving(mon); if (!update_status(mod)) goto out; bar->refresh(bar); int timeout_left_ms = m->poll_interval; while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = udev_monitor_get_fd(mon), .events = POLLIN}, }; int timeout = m->poll_interval > 0 ? timeout_left_ms : -1; struct timespec time_before_poll; if (clock_gettime(CLOCK_BOOTTIME, &time_before_poll) < 0) { LOG_ERRNO("failed to get current time"); break; } const int poll_ret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout); if (poll_ret < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = 0; break; } bool udev_for_us = false; if (fds[1].revents & POLLIN) { struct udev_device *dev = udev_monitor_receive_device(mon); if (dev != NULL) { const char *sysname = udev_device_get_sysname(dev); udev_for_us = sysname != NULL && strcmp(sysname, m->battery) == 0; if (!udev_for_us) { LOG_DBG("udev notification not for us (%s != %s)", m->battery, sysname != sysname ? sysname : "NULL"); } else LOG_DBG("triggering update due to udev notification"); udev_device_unref(dev); } } if (udev_for_us || poll_ret == 0) { if (update_status(mod)) bar->refresh(bar); } if (poll_ret == 0 || udev_for_us) { LOG_DBG("resetting timeout-left to %ldms", m->poll_interval); timeout_left_ms = m->poll_interval; } else { struct timespec time_after_poll; if (clock_gettime(CLOCK_BOOTTIME, &time_after_poll) < 0) { LOG_ERRNO("failed to get current time"); break; } struct timespec timeout_consumed; timespec_sub(&time_after_poll, &time_before_poll, &timeout_consumed); const int timeout_consumed_ms = timeout_consumed.tv_sec * 1000 + timeout_consumed.tv_nsec / 1000000; LOG_DBG("timeout-left before: %dms, consumed: %dms, updated: %dms", timeout_left_ms, timeout_consumed_ms, max(timeout_left_ms - timeout_consumed_ms, 0)); timeout_left_ms -= timeout_consumed_ms; if (timeout_left_ms < 0) timeout_left_ms = 0; } } out: if (mon != NULL) udev_monitor_unref(mon); if (udev != NULL) udev_unref(udev); return ret; } static struct module * battery_new(const char *battery, struct particle *label, long poll_interval_msecs, int battery_scale, long smoothing_secs) { struct private *m = calloc(1, sizeof(*m)); m->label = label; m->poll_interval = poll_interval_msecs; m->battery_scale = battery_scale; m->smoothing_scale = smoothing_secs * one_sec_in_ns; m->battery = strdup(battery); m->state = STATE_UNKNOWN; m->ema_current = (struct current_state){-1, 0, (struct timespec){0, 0}}; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *name = yml_get_value(node, "name"); const struct yml_node *poll_interval = yml_get_value(node, "poll-interval"); const struct yml_node *battery_scale = yml_get_value(node, "battery-scale"); const struct yml_node *smoothing_secs = yml_get_value(node, "smoothing-secs"); return battery_new(yml_value_as_string(name), conf_to_particle(c, inherited), (poll_interval != NULL ? yml_value_as_int(poll_interval) : default_poll_interval), (battery_scale != NULL ? yml_value_as_int(battery_scale) : 1), (smoothing_secs != NULL ? yml_value_as_int(smoothing_secs) : 100)); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; const long value = yml_value_as_int(node); if (value != 0 && value < min_poll_interval) { LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"name", true, &conf_verify_string}, {"poll-interval", false, &conf_verify_poll_interval}, {"battery-scale", false, &conf_verify_unsigned}, {"smoothing-secs", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_battery_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_battery_iface"))); #endif yambar-1.11.0/modules/clock.c000066400000000000000000000127301460770427600157740ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "clock" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" struct private { struct particle *label; enum { UPDATE_GRANULARITY_SECONDS, UPDATE_GRANULARITY_MINUTES, } update_granularity; char *date_format; char *time_format; bool utc; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free(m->time_format); free(m->date_format); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "clock"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; time_t t = time(NULL); struct tm *tm = m->utc ? gmtime(&t) : localtime(&t); char date_str[1024]; strftime(date_str, sizeof(date_str), m->date_format, tm); char time_str[1024]; strftime(time_str, sizeof(time_str), m->time_format, tm); struct tag_set tags = { .tags = (struct tag *[]){tag_new_string(mod, "time", time_str), tag_new_string(mod, "date", date_str)}, .count = 2, }; struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static int run(struct module *mod) { const struct private *m = mod->private; const struct bar *bar = mod->bar; bar->refresh(bar); int ret = 1; while (true) { struct timespec _now; clock_gettime(CLOCK_REALTIME, &_now); const struct timeval now = { .tv_sec = _now.tv_sec, .tv_usec = _now.tv_nsec / 1000, }; int timeout_ms = 1000; switch (m->update_granularity) { case UPDATE_GRANULARITY_SECONDS: { const struct timeval next_second = {.tv_sec = now.tv_sec + 1, .tv_usec = 0}; struct timeval _timeout; timersub(&next_second, &now, &_timeout); assert(_timeout.tv_sec == 0 || (_timeout.tv_sec == 1 && _timeout.tv_usec == 0)); timeout_ms = _timeout.tv_usec / 1000; break; } case UPDATE_GRANULARITY_MINUTES: { const struct timeval next_minute = { .tv_sec = now.tv_sec / 60 * 60 + 60, .tv_usec = 0, }; struct timeval _timeout; timersub(&next_minute, &now, &_timeout); timeout_ms = _timeout.tv_sec * 1000 + _timeout.tv_usec / 1000; } } /* Add 1ms to account for rounding errors */ timeout_ms++; LOG_DBG("now: %lds %ldµs -> timeout: %dms", now.tv_sec, now.tv_usec, timeout_ms); struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; if (poll(fds, 1, timeout_ms) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = 0; break; } bar->refresh(bar); } return ret; } static struct module * clock_new(struct particle *label, const char *date_format, const char *time_format, bool utc) { struct private *m = calloc(1, sizeof(*m)); m->label = label; m->date_format = strdup(date_format); m->time_format = strdup(time_format); m->utc = utc; static const char *const seconds_formatters[] = { "%c", "%s", "%S", "%T", "%r", "%X", }; m->update_granularity = UPDATE_GRANULARITY_MINUTES; for (size_t i = 0; i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]); i++) { if (strstr(time_format, seconds_formatters[i]) != NULL) { m->update_granularity = UPDATE_GRANULARITY_SECONDS; break; } } LOG_DBG("using %s update granularity", (m->update_granularity == UPDATE_GRANULARITY_MINUTES ? "minutes" : "seconds")); struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *date_format = yml_get_value(node, "date-format"); const struct yml_node *time_format = yml_get_value(node, "time-format"); const struct yml_node *utc = yml_get_value(node, "utc"); return clock_new(conf_to_particle(c, inherited), date_format != NULL ? yml_value_as_string(date_format) : "%x", time_format != NULL ? yml_value_as_string(time_format) : "%H:%M", utc != NULL ? yml_value_as_bool(utc) : false); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"date-format", false, &conf_verify_string}, {"time-format", false, &conf_verify_string}, {"utc", false, &conf_verify_bool}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_clock_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_clock_iface"))); #endif yambar-1.11.0/modules/cpu.c000066400000000000000000000201021460770427600154600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "cpu" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../particles/dynlist.h" #include "../plugin.h" static const long min_poll_interval = 250; struct cpu_stats { uint32_t *prev_cores_idle; uint32_t *prev_cores_nidle; uint32_t *cur_cores_idle; uint32_t *cur_cores_nidle; }; struct private { struct particle *template; uint16_t interval; size_t core_count; struct cpu_stats cpu_stats; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->template->destroy(m->template); free(m->cpu_stats.prev_cores_idle); free(m->cpu_stats.prev_cores_nidle); free(m->cpu_stats.cur_cores_idle); free(m->cpu_stats.cur_cores_nidle); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "cpu"; } static uint32_t get_cpu_nb_cores() { int nb_cores = sysconf(_SC_NPROCESSORS_ONLN); LOG_DBG("CPU count: %d", nb_cores); return nb_cores; } static bool parse_proc_stat_line(const char *line, uint32_t *user, uint32_t *nice, uint32_t *system, uint32_t *idle, uint32_t *iowait, uint32_t *irq, uint32_t *softirq, uint32_t *steal, uint32_t *guest, uint32_t *guestnice) { int32_t core_id; if (line[sizeof("cpu") - 1] == ' ') { int read = sscanf(line, "cpu %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice); return read == 10; } else { int read = sscanf(line, "cpu%" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, &core_id, user, nice, system, idle, iowait, irq, softirq, steal, guest, guestnice); return read == 11; } } static uint8_t get_cpu_usage_percent(const struct cpu_stats *cpu_stats, int8_t core_idx) { uint32_t prev_total = cpu_stats->prev_cores_idle[core_idx + 1] + cpu_stats->prev_cores_nidle[core_idx + 1]; uint32_t cur_total = cpu_stats->cur_cores_idle[core_idx + 1] + cpu_stats->cur_cores_nidle[core_idx + 1]; double totald = cur_total - prev_total; double nidled = cpu_stats->cur_cores_nidle[core_idx + 1] - cpu_stats->prev_cores_nidle[core_idx + 1]; double percent = (nidled * 100) / (totald + 1); return round(percent); } static void refresh_cpu_stats(struct cpu_stats *cpu_stats, size_t core_count) { int32_t core = 0; uint32_t user = 0; uint32_t nice = 0; uint32_t system = 0; uint32_t idle = 0; uint32_t iowait = 0; uint32_t irq = 0; uint32_t softirq = 0; uint32_t steal = 0; uint32_t guest = 0; uint32_t guestnice = 0; FILE *fp = NULL; char *line = NULL; size_t len = 0; ssize_t read; fp = fopen("/proc/stat", "r"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/stat"); return; } while ((read = getline(&line, &len, fp)) != -1 && core <= core_count) { if (strncmp(line, "cpu", sizeof("cpu") - 1) == 0) { if (!parse_proc_stat_line(line, &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guestnice)) { LOG_ERR("unable to parse /proc/stat line"); goto exit; } cpu_stats->prev_cores_idle[core] = cpu_stats->cur_cores_idle[core]; cpu_stats->prev_cores_nidle[core] = cpu_stats->cur_cores_nidle[core]; cpu_stats->cur_cores_idle[core] = idle + iowait; cpu_stats->cur_cores_nidle[core] = user + nice + system + irq + softirq + steal; core++; } } exit: fclose(fp); free(line); } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); const size_t list_count = m->core_count + 1; struct exposable *parts[list_count]; { uint8_t total_usage = get_cpu_usage_percent(&m->cpu_stats, -1); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_int(mod, "id", -1), tag_new_int_range(mod, "cpu", total_usage, 0, 100), }, .count = 2, }; parts[0] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); } for (size_t i = 0; i < m->core_count; i++) { uint8_t core_usage = get_cpu_usage_percent(&m->cpu_stats, i); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_int(mod, "id", i), tag_new_int_range(mod, "cpu", core_usage, 0, 100), }, .count = 2, }; parts[i + 1] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); } mtx_unlock(&mod->lock); return dynlist_exposable_new(parts, list_count, 0, 0); } static int run(struct module *mod) { const struct bar *bar = mod->bar; bar->refresh(bar); struct private *p = mod->private; while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval); if (res < 0) { if (EINTR == errno) continue; LOG_ERRNO("unable to poll abort fd"); return -1; } if (fds[0].revents & POLLIN) break; mtx_lock(&mod->lock); refresh_cpu_stats(&p->cpu_stats, p->core_count); mtx_unlock(&mod->lock); bar->refresh(bar); } return 0; } static struct module * cpu_new(uint16_t interval, struct particle *template) { uint32_t nb_cores = get_cpu_nb_cores(); struct private *p = calloc(1, sizeof(*p)); p->template = template; p->interval = interval; p->core_count = nb_cores; p->cpu_stats.prev_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_nidle)); p->cpu_stats.prev_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.prev_cores_idle)); p->cpu_stats.cur_cores_nidle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_nidle)); p->cpu_stats.cur_cores_idle = calloc(nb_cores + 1, sizeof(*p->cpu_stats.cur_cores_idle)); struct module *mod = module_common_new(); mod->private = p; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *interval = yml_get_value(node, "poll-interval"); const struct yml_node *c = yml_get_value(node, "content"); return cpu_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited)); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; if (yml_value_as_int(node) < min_poll_interval) { LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"poll-interval", false, &conf_verify_poll_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_cpu_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_cpu_iface"))); #endif yambar-1.11.0/modules/disk-io.c000066400000000000000000000241451460770427600162430ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "disk-io" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../particles/dynlist.h" #include "../plugin.h" static const long min_poll_interval = 250; struct device_stats { char *name; bool is_disk; uint64_t prev_sectors_read; uint64_t cur_sectors_read; uint64_t prev_sectors_written; uint64_t cur_sectors_written; uint32_t ios_in_progress; bool exists; }; struct private { struct particle *label; uint16_t interval; tll(struct device_stats *) devices; }; static bool is_disk(char const *name) { DIR *dir = opendir("/sys/block"); if (dir == NULL) { LOG_ERRNO("failed to read /sys/block directory"); return false; } struct dirent *entry; bool found = false; while ((entry = readdir(dir)) != NULL) { if (strcmp(name, entry->d_name) == 0) { found = true; break; } } closedir(dir); return found; } static struct device_stats * new_device_stats(char const *name) { struct device_stats *dev = malloc(sizeof(*dev)); dev->name = strdup(name); dev->is_disk = is_disk(name); return dev; } static void free_device_stats(struct device_stats *dev) { free(dev->name); free(dev); } static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); tll_foreach(m->devices, it) { free_device_stats(it->item); } tll_free(m->devices); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "disk-io"; } static void refresh_device_stats(struct private *m) { FILE *fp = NULL; char *line = NULL; size_t len = 0; ssize_t read; fp = fopen("/proc/diskstats", "r"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/diskstats"); return; } /* * Devices may be added or removed during the bar's lifetime, as external * block devices are connected or disconnected from the machine. /proc/diskstats * reports data only for the devices that are currently connected. * * This means that if we have a device that ISN'T in /proc/diskstats, it was * disconnected, and we need to remove it from the list. * * On the other hand, if a device IS in /proc/diskstats, but not in our list, we * must create a new device_stats struct and add it to the list. * * The 'exists' variable is what keep tracks of whether or not /proc/diskstats * is still reporting the device (i.e., it is still connected). */ tll_foreach(m->devices, it) { it->item->exists = false; } while ((read = getline(&line, &len, fp)) != -1) { /* * For an explanation of the fields bellow, see * https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats */ uint8_t major_number = 0; uint8_t minor_number = 0; char *device_name = NULL; uint32_t completed_reads = 0; uint32_t merged_reads = 0; uint64_t sectors_read = 0; uint32_t reading_time = 0; uint32_t completed_writes = 0; uint32_t merged_writes = 0; uint64_t sectors_written = 0; uint32_t writting_time = 0; uint32_t ios_in_progress = 0; uint32_t io_time = 0; uint32_t io_weighted_time = 0; uint32_t completed_discards = 0; uint32_t merged_discards = 0; uint32_t sectors_discarded = 0; uint32_t discarding_time = 0; uint32_t completed_flushes = 0; uint32_t flushing_time = 0; if (!sscanf(line, " %" SCNu8 " %" SCNu8 " %ms %" SCNu32 " %" SCNu32 " %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu64 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32 " %" SCNu32, &major_number, &minor_number, &device_name, &completed_reads, &merged_reads, §ors_read, &reading_time, &completed_writes, &merged_writes, §ors_written, &writting_time, &ios_in_progress, &io_time, &io_weighted_time, &completed_discards, &merged_discards, §ors_discarded, &discarding_time, &completed_flushes, &flushing_time)) { LOG_ERR("unable to parse /proc/diskstats line"); free(device_name); goto exit; } bool found = false; tll_foreach(m->devices, it) { struct device_stats *dev = it->item; if (strcmp(dev->name, device_name) == 0) { dev->prev_sectors_read = dev->cur_sectors_read; dev->prev_sectors_written = dev->cur_sectors_written; dev->ios_in_progress = ios_in_progress; dev->cur_sectors_read = sectors_read; dev->cur_sectors_written = sectors_written; dev->exists = true; found = true; break; } } if (!found) { struct device_stats *new_dev = new_device_stats(device_name); new_dev->ios_in_progress = ios_in_progress; new_dev->prev_sectors_read = sectors_read; new_dev->cur_sectors_read = sectors_read; new_dev->prev_sectors_written = sectors_written; new_dev->cur_sectors_written = sectors_written; new_dev->exists = true; tll_push_back(m->devices, new_dev); } free(device_name); } tll_foreach(m->devices, it) { if (!it->item->exists) { free_device_stats(it->item); tll_remove(m->devices, it); } } exit: fclose(fp); free(line); } static struct exposable * content(struct module *mod) { const struct private *p = mod->private; uint64_t total_bytes_read = 0; uint64_t total_bytes_written = 0; uint32_t total_ios_in_progress = 0; mtx_lock(&mod->lock); struct exposable *tag_parts[p->devices.length + 1]; int i = 0; tll_foreach(p->devices, it) { struct device_stats *dev = it->item; uint64_t bytes_read = (dev->cur_sectors_read - dev->prev_sectors_read) * 512; uint64_t bytes_written = (dev->cur_sectors_written - dev->prev_sectors_written) * 512; if (dev->is_disk) { total_bytes_read += bytes_read; total_bytes_written += bytes_written; total_ios_in_progress += dev->ios_in_progress; } struct tag_set tags = { .tags = (struct tag *[]) { tag_new_string(mod, "device", dev->name), tag_new_bool(mod, "is_disk", dev->is_disk), tag_new_int(mod, "read_speed", (bytes_read * 1000) / p->interval), tag_new_int(mod, "write_speed", (bytes_written * 1000) / p->interval), tag_new_int(mod, "ios_in_progress", dev->ios_in_progress), }, .count = 5, }; tag_parts[i++] = p->label->instantiate(p->label, &tags); tag_set_destroy(&tags); } struct tag_set tags = { .tags = (struct tag *[]) { tag_new_string(mod, "device", "Total"), tag_new_bool(mod, "is_disk", true), tag_new_int(mod, "read_speed", (total_bytes_read * 1000) / p->interval), tag_new_int(mod, "write_speed", (total_bytes_written * 1000) / p->interval), tag_new_int(mod, "ios_in_progress", total_ios_in_progress), }, .count = 5, }; tag_parts[i] = p->label->instantiate(p->label, &tags); tag_set_destroy(&tags); mtx_unlock(&mod->lock); return dynlist_exposable_new(tag_parts, p->devices.length + 1, 0, 0); } static int run(struct module *mod) { const struct bar *bar = mod->bar; bar->refresh(bar); struct private *p = mod->private; while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, sizeof(fds) / sizeof(*fds), p->interval); if (res < 0) { if (EINTR == errno) continue; LOG_ERRNO("unable to poll abort fd"); return -1; } if (fds[0].revents & POLLIN) break; mtx_lock(&mod->lock); refresh_device_stats(p); mtx_unlock(&mod->lock); bar->refresh(bar); } return 0; } static struct module * disk_io_new(uint16_t interval, struct particle *label) { struct private *p = calloc(1, sizeof(*p)); p->label = label; p->interval = interval; struct module *mod = module_common_new(); mod->private = p; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *interval = yml_get_value(node, "poll-interval"); const struct yml_node *c = yml_get_value(node, "content"); return disk_io_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited)); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; if (yml_value_as_int(node) < min_poll_interval) { LOG_ERR("%s: poll-interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"poll-interval", false, &conf_verify_poll_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_disk_io_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_disk_io_iface"))); #endif yambar-1.11.0/modules/dwl.c000066400000000000000000000401561460770427600154720ustar00rootroot00000000000000#include #include #include #include #include #define ARR_LEN(x) (sizeof((x)) / sizeof((x)[0])) #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../module.h" #include "../particles/dynlist.h" #include "../plugin.h" #define LOG_MODULE "dwl" #define LOG_ENABLE_DBG 0 struct dwl_tag { int id; char *name; bool selected; bool empty; bool urgent; }; struct private { struct particle *label; char const *monitor; unsigned int number_of_tags; char *dwl_info_filename; /* dwl data */ char *title; char *appid; bool fullscreen; bool floating; bool selmon; tll(struct dwl_tag *) tags; char *layout; }; enum LINE_MODE { LINE_MODE_0, LINE_MODE_TITLE, LINE_MODE_APPID, LINE_MODE_FULLSCREEN, LINE_MODE_FLOATING, LINE_MODE_SELMON, LINE_MODE_TAGS, LINE_MODE_LAYOUT, }; static void free_dwl_tag(struct dwl_tag *tag) { free(tag->name); free(tag); } static void destroy(struct module *module) { struct private *private = module->private; private->label->destroy(private->label); tll_free_and_free(private->tags, free_dwl_tag); free(private->dwl_info_filename); free(private->title); free(private->layout); free(private); module_default_destroy(module); } static char const * description(const struct module *module) { return "dwl"; } static struct exposable * content(struct module *module) { struct private const *private = module->private; mtx_lock(&module->lock); size_t i = 0; /* + 1 for `default` tag */ struct exposable *exposable[tll_length(private->tags) + 1]; tll_foreach(private->tags, it) { struct tag_set tags = { .tags = (struct tag*[]){ tag_new_string(module, "title", private->title), tag_new_string(module, "appid", private->appid), tag_new_bool(module, "fullscreen", private->fullscreen), tag_new_bool(module, "floating", private->floating), tag_new_bool(module, "selmon", private->selmon), tag_new_string(module, "layout", private->layout), tag_new_int(module, "id", it->item->id), tag_new_string(module, "name", it->item->name), tag_new_bool(module, "selected", it->item->selected), tag_new_bool(module, "empty", it->item->empty), tag_new_bool(module, "urgent", it->item->urgent), }, .count = 11, }; exposable[i++] = private->label->instantiate(private->label, &tags); tag_set_destroy(&tags); } /* default tag (used for title, layout, etc) */ struct tag_set tags = { .tags = (struct tag*[]){ tag_new_string(module, "title", private->title), tag_new_string(module, "appid", private->appid), tag_new_bool(module, "fullscreen", private->fullscreen), tag_new_bool(module, "floating", private->floating), tag_new_bool(module, "selmon", private->selmon), tag_new_string(module, "layout", private->layout), tag_new_int(module, "id", 0), tag_new_string(module, "name", "0"), tag_new_bool(module, "selected", false), tag_new_bool(module, "empty", true), tag_new_bool(module, "urgent", false), }, .count = 11, }; exposable[i++] = private->label->instantiate(private->label, &tags); tag_set_destroy(&tags); mtx_unlock(&module->lock); return dynlist_exposable_new(exposable, i, 0, 0); } static struct dwl_tag * dwl_tag_from_id(struct private *private, uint32_t id) { tll_foreach(private->tags, it) { if (it->item->id == id) return it->item; } assert(false); /* unreachable */ return NULL; } static void process_line(char *line, struct module *module) { struct private *private = module->private; enum LINE_MODE line_mode = LINE_MODE_0; /* Remove \n */ line[strcspn(line, "\n")] = '\0'; /* Split line by space */ size_t index = 1; char *save_pointer = NULL; char *string = strtok_r(line, " ", &save_pointer); while (string != NULL) { /* dwl logs are formatted like this * $1 -> monitor * $2 -> action * $3 -> arg1 * $4 -> arg2 * ... */ /* monitor */ if (index == 1) { /* Not our monitor */ if (strcmp(string, private->monitor) != 0) break; } /* action */ else if (index == 2) { if (strcmp(string, "title") == 0) { line_mode = LINE_MODE_TITLE; /* Update the title here, to avoid allocate and free memory on * every iteration (the line is separated by spaces, then we * join it again) a bit suboptimal, isn't it?) */ free(private->title); private->title = strdup(save_pointer); break; } else if (strcmp(string, "appid") == 0) { line_mode = LINE_MODE_APPID; /* Update the appid here, same as the title. */ free(private->appid); private->appid = strdup(save_pointer); break; } else if (strcmp(string, "fullscreen") == 0) line_mode = LINE_MODE_FULLSCREEN; else if (strcmp(string, "floating") == 0) line_mode = LINE_MODE_FLOATING; else if (strcmp(string, "selmon") == 0) line_mode = LINE_MODE_SELMON; else if (strcmp(string, "tags") == 0) line_mode = LINE_MODE_TAGS; else if (strcmp(string, "layout") == 0) line_mode = LINE_MODE_LAYOUT; else { LOG_WARN("UNKNOWN action, please open an issue on https://codeberg.org/dnkl/yambar"); return; } } /* args */ else { if (line_mode == LINE_MODE_TAGS) { static uint32_t occupied, selected, client_tags, urgent; static uint32_t *target = NULL; /* dwl tags action log are formatted like this * $3 -> occupied * $4 -> tags * $5 -> clientTags (not needed) * $6 -> urgent */ if (index == 3) target = &occupied; else if (index == 4) target = &selected; else if (index == 5) target = &client_tags; else if (index == 6) target = &urgent; /* No need to check error IMHO */ *target = strtoul(string, NULL, 10); /* Populate informations */ if (index == 6) { for (size_t id = 1; id <= private->number_of_tags; ++id) { uint32_t mask = 1 << (id - 1); struct dwl_tag *dwl_tag = dwl_tag_from_id(private, id); dwl_tag->selected = mask & selected; dwl_tag->empty = !(mask & occupied); dwl_tag->urgent = mask & urgent; } } } else switch (line_mode) { case LINE_MODE_TITLE: case LINE_MODE_APPID: assert(false); /* unreachable */ break; case LINE_MODE_FULLSCREEN: private ->fullscreen = (strcmp(string, "0") != 0); break; case LINE_MODE_FLOATING: private ->floating = (strcmp(string, "0") != 0); break; case LINE_MODE_SELMON: private ->selmon = (strcmp(string, "0") != 0); break; case LINE_MODE_LAYOUT: free(private->layout); private->layout = strdup(string); break; default:; assert(false); /* unreachable */ } } string = strtok_r(NULL, " ", &save_pointer); ++index; } } static int file_read_content(FILE *file, struct module *module) { static char buffer[1024]; errno = 0; while (fgets(buffer, ARR_LEN(buffer), file) != NULL) process_line(buffer, module); fseek(file, 0, SEEK_END); /* Check whether error has been */ if (ferror(file) != 0) { LOG_ERRNO("unable to read file's content."); return 1; } return 0; } static void file_seek_to_last_n_lines(FILE *file, int number_of_lines) { if (number_of_lines == 0 || file == NULL) return; fseek(file, 0, SEEK_END); long position = ftell(file); while (position > 0) { /* Cannot go less than position 0 */ if (fseek(file, --position, SEEK_SET) == EINVAL) break; if (fgetc(file) == '\n') if (number_of_lines-- == 0) break; } } static int run_init(int *inotify_fd, int *inotify_wd, FILE **file, char *dwl_info_filename) { *inotify_fd = inotify_init(); if (*inotify_fd == -1) { LOG_ERRNO("unable to create inotify fd."); return -1; } *inotify_wd = inotify_add_watch(*inotify_fd, dwl_info_filename, IN_MODIFY); if (*inotify_wd == -1) { close(*inotify_fd); LOG_ERRNO("unable to add watch to inotify fd."); return 1; } *file = fopen(dwl_info_filename, "r"); if (*file == NULL) { inotify_rm_watch(*inotify_fd, *inotify_wd); close(*inotify_fd); LOG_ERRNO("unable to open file."); return 1; } return 0; } static int run_clean(int inotify_fd, int inotify_wd, FILE *file) { if (inotify_fd != -1) { if (inotify_wd != -1) inotify_rm_watch(inotify_fd, inotify_wd); close(inotify_fd); } if (file != NULL) { if (fclose(file) == EOF) { LOG_ERRNO("unable to close file."); return 1; } } return 0; }; static int run(struct module *module) { struct private *private = module->private; /* Ugly, but I didn't find better way for waiting * the monitor's name to be set */ do { private->monitor = module->bar->output_name(module->bar); usleep(50); } while (private->monitor == NULL); int inotify_fd = -1, inotify_wd = -1; FILE *file = NULL; if (run_init(&inotify_fd, &inotify_wd, &file, private->dwl_info_filename) != 0) return 1; /* Dwl output is 6 lines per monitor, so let's assume that nobody has * more than 5 monitors (6 * 5 = 30) */ mtx_lock(&module->lock); file_seek_to_last_n_lines(file, 30); if (file_read_content(file, module) != 0) { mtx_unlock(&module->lock); return run_clean(inotify_fd, inotify_wd, file); } mtx_unlock(&module->lock); module->bar->refresh(module->bar); while (true) { struct pollfd fds[] = { (struct pollfd){.fd = module->abort_fd, .events = POLLIN}, (struct pollfd){.fd = inotify_fd, .events = POLLIN}, }; if (poll(fds, ARR_LEN(fds), -1) == -1) { if (errno == EINTR) continue; LOG_ERRNO("unable to poll."); break; } if (fds[0].revents & POLLIN) break; /* fds[1] (inotify_fd) must be POLLIN otherwise issue happen'd */ if (!(fds[1].revents & POLLIN)) { LOG_ERR("expected POLLIN revent"); break; } /* Block until event */ static char buffer[1024]; ssize_t length = read(inotify_fd, buffer, ARR_LEN(buffer)); if (length == 0) break; if (length == -1) { if (errno == EAGAIN) continue; LOG_ERRNO("unable to read %s", private->dwl_info_filename); break; } mtx_lock(&module->lock); if (file_read_content(file, module) != 0) { mtx_unlock(&module->lock); break; } mtx_unlock(&module->lock); module->bar->refresh(module->bar); } return run_clean(inotify_fd, inotify_wd, file); } static struct module * dwl_new(struct particle *label, int number_of_tags, struct yml_node const *name_of_tags, char const *dwl_info_filename) { struct private *private = calloc(1, sizeof(struct private)); private->label = label; private->number_of_tags = number_of_tags; private->dwl_info_filename = strdup(dwl_info_filename); struct yml_list_iter list = {0}; if (name_of_tags) list = yml_list_iter(name_of_tags); for (int i = 1; i <= number_of_tags; i++) { struct dwl_tag *dwl_tag = calloc(1, sizeof(struct dwl_tag)); dwl_tag->id = i; if (list.node) { dwl_tag->name = strdup(yml_value_as_string(list.node)); yml_list_next(&list); } else if (asprintf(&dwl_tag->name, "%d", i) < 0) { LOG_ERRNO("asprintf"); } tll_push_back(private->tags, dwl_tag); } struct module *module = module_common_new(); module->private = private; module->run = &run; module->destroy = &destroy; module->content = &content; module->description = &description; return module; } static struct module * from_conf(struct yml_node const *node, struct conf_inherit inherited) { struct yml_node const *content = yml_get_value(node, "content"); struct yml_node const *number_of_tags = yml_get_value(node, "number-of-tags"); struct yml_node const *name_of_tags = yml_get_value(node, "name-of-tags"); struct yml_node const *dwl_info_filename = yml_get_value(node, "dwl-info-filename"); return dwl_new(conf_to_particle(content, inherited), yml_value_as_int(number_of_tags), name_of_tags, yml_value_as_string(dwl_info_filename)); } static bool verify_names(keychain_t *keychain, const struct yml_node *node) { if (!yml_is_list(node)) { LOG_ERR("%s: %s is not a list", conf_err_prefix(keychain, node), yml_value_as_string(node)); return false; } return conf_verify_list(keychain, node, &conf_verify_string); } static bool verify_conf(keychain_t *keychain, struct yml_node const *node) { static struct attr_info const attrs[] = { {"number-of-tags", true, &conf_verify_unsigned}, {"name-of-tags", false, &verify_names}, {"dwl-info-filename", true, &conf_verify_string}, MODULE_COMMON_ATTRS, }; if (!conf_verify_dict(keychain, node, attrs)) return false; /* No need to check whether is `number_of_tags` is a int * because `conf_verify_unsigned` already did it */ struct yml_node const *ntags_key = yml_get_key(node, "number-of-tags"); struct yml_node const *value = yml_get_value(node, "number-of-tags"); int number_of_tags = yml_value_as_int(value); if (number_of_tags == 0) { LOG_ERR("%s: %s must not be 0", conf_err_prefix(keychain, ntags_key), yml_value_as_string(ntags_key)); return false; } struct yml_node const *key = yml_get_key(node, "name-of-tags"); value = yml_get_value(node, "name-of-tags"); if (value && yml_list_length(value) != number_of_tags) { LOG_ERR("%s: %s must have the same number of elements that %s", conf_err_prefix(keychain, key), yml_value_as_string(key), yml_value_as_string(ntags_key)); return false; } /* No need to check whether is `dwl_info_filename` is a string * because `conf_verify_string` already did it */ key = yml_get_key(node, "dwl-info-filename"); value = yml_get_value(node, "dwl-info-filename"); if (strlen(yml_value_as_string(value)) == 0) { LOG_ERR("%s: %s must not be empty", conf_err_prefix(keychain, key), yml_value_as_string(key)); return false; } return true; } struct module_iface const module_dwl_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern struct module_iface const iface __attribute__((weak, alias("module_dwl_iface"))); #endif yambar-1.11.0/modules/foreign-toplevel.c000066400000000000000000000411531460770427600201630ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "foreign-toplevel" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "wlr-foreign-toplevel-management-unstable-v1.h" #include "xdg-output-unstable-v1.h" #define min(x, y) ((x) < (y) ? (x) : (y)) static const int required_manager_interface_version = 2; struct output { struct module *mod; uint32_t wl_name; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; char *name; }; struct toplevel { struct module *mod; struct zwlr_foreign_toplevel_handle_v1 *handle; char *app_id; char *title; bool maximized; bool minimized; bool activated; bool fullscreen; tll(const struct output *) outputs; }; struct private { struct particle *template; uint32_t manager_wl_name; struct zwlr_foreign_toplevel_manager_v1 *manager; struct zxdg_output_manager_v1 *xdg_output_manager; bool all_monitors; tll(struct toplevel) toplevels; tll(struct output) outputs; }; static void output_free(struct output *output) { free(output->name); if (output->xdg_output != NULL) zxdg_output_v1_destroy(output->xdg_output); if (output->wl_output != NULL) wl_output_release(output->wl_output); } static void toplevel_free(struct toplevel *top) { if (top->handle != NULL) zwlr_foreign_toplevel_handle_v1_destroy(top->handle); free(top->app_id); free(top->title); tll_free(top->outputs); } static void destroy(struct module *mod) { struct private *m = mod->private; m->template->destroy(m->template); assert(tll_length(m->toplevels) == 0); assert(tll_length(m->outputs) == 0); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "toplevel"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); const size_t toplevel_count = tll_length(m->toplevels); size_t show_count = 0; struct exposable *toplevels[toplevel_count]; const char *current_output = mod->bar->output_name(mod->bar); tll_foreach(m->toplevels, it) { const struct toplevel *top = &it->item; bool show = false; if (m->all_monitors) show = true; else if (current_output != NULL) { tll_foreach(top->outputs, it2) { const struct output *output = it2->item; if (output->name != NULL && strcmp(output->name, current_output) == 0) { show = true; break; } } } if (!show) continue; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "app-id", it->item.app_id), tag_new_string(mod, "title", it->item.title), tag_new_bool(mod, "maximized", it->item.maximized), tag_new_bool(mod, "minimized", it->item.minimized), tag_new_bool(mod, "activated", it->item.activated), tag_new_bool(mod, "fullscreen", it->item.fullscreen), }, .count = 6, }; toplevels[show_count++] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); } mtx_unlock(&mod->lock); return dynlist_exposable_new(toplevels, show_count, 0, 0); } static bool verify_iface_version(const char *iface, uint32_t version, uint32_t wanted) { if (version >= wanted) return true; LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version); return false; } static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { } static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { } static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct output *output = data; struct module *mod = output->mod; mtx_lock(&mod->lock); { free(output->name); output->name = name != NULL ? strdup(name) : NULL; } mtx_unlock(&mod->lock); } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { } static struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = xdg_output_handle_done, .name = xdg_output_handle_name, .description = xdg_output_handle_description, }; static void title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title) { struct toplevel *top = data; mtx_lock(&top->mod->lock); { free(top->title); top->title = title != NULL ? strdup(title) : NULL; } mtx_unlock(&top->mod->lock); } static void app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id) { struct toplevel *top = data; mtx_lock(&top->mod->lock); { free(top->app_id); top->app_id = app_id != NULL ? strdup(app_id) : NULL; } mtx_unlock(&top->mod->lock); } static void output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output) { struct toplevel *top = data; struct module *mod = top->mod; struct private *m = mod->private; mtx_lock(&mod->lock); const struct output *output = NULL; tll_foreach(m->outputs, it) { if (it->item.wl_output == wl_output) { output = &it->item; break; } } if (output == NULL) { LOG_ERR("output-enter event on untracked output"); goto out; } tll_foreach(top->outputs, it) { if (it->item == output) { LOG_ERR("output-enter event on output we're already on"); goto out; } } LOG_DBG("mapped: %s:%s on %s", top->app_id, top->title, output->name); tll_push_back(top->outputs, output); out: mtx_unlock(&mod->lock); } static void output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *wl_output) { struct toplevel *top = data; struct module *mod = top->mod; struct private *m = mod->private; mtx_lock(&mod->lock); const struct output *output = NULL; tll_foreach(m->outputs, it) { if (it->item.wl_output == wl_output) { output = &it->item; break; } } if (output == NULL) { LOG_ERR("output-leave event on untracked output"); goto out; } bool output_removed = false; tll_foreach(top->outputs, it) { if (it->item == output) { LOG_DBG("unmapped: %s:%s from %s", top->app_id, top->title, output->name); tll_remove(top->outputs, it); output_removed = true; break; } } if (!output_removed) { LOG_ERR("output-leave event on an output we're not on"); goto out; } out: mtx_unlock(&mod->lock); } static void state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *states) { struct toplevel *top = data; bool maximized = false; bool minimized = false; bool activated = false; bool fullscreen = false; enum zwlr_foreign_toplevel_handle_v1_state *state; wl_array_for_each(state, states) { switch (*state) { case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: maximized = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: minimized = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: activated = true; break; case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: fullscreen = true; break; } } mtx_lock(&top->mod->lock); { top->maximized = maximized; top->minimized = minimized; top->activated = activated; top->fullscreen = fullscreen; } mtx_unlock(&top->mod->lock); } static void done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { struct toplevel *top = data; const struct bar *bar = top->mod->bar; bar->refresh(bar); } static void closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { struct toplevel *top = data; struct module *mod = top->mod; struct private *m = mod->private; mtx_lock(&mod->lock); tll_foreach(m->toplevels, it) { if (it->item.handle == handle) { toplevel_free(top); tll_remove(m->toplevels, it); break; } } mtx_unlock(&mod->lock); const struct bar *bar = mod->bar; bar->refresh(bar); } static void parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) { } static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_listener = { .title = &title, .app_id = &app_id, .output_enter = &output_enter, .output_leave = &output_leave, .state = &state, .done = &done, .closed = &closed, .parent = &parent, }; static void toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *handle) { struct module *mod = data; struct private *m = mod->private; struct toplevel toplevel = { .mod = mod, .handle = handle, }; mtx_lock(&mod->lock); { tll_push_back(m->toplevels, toplevel); zwlr_foreign_toplevel_handle_v1_add_listener(handle, &toplevel_listener, &tll_back(m->toplevels)); } mtx_unlock(&mod->lock); } static void finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) { struct module *mod = data; struct private *m = mod->private; assert(m->manager == manager); zwlr_foreign_toplevel_manager_v1_destroy(m->manager); m->manager = NULL; } static const struct zwlr_foreign_toplevel_manager_v1_listener manager_listener = { .toplevel = &toplevel, .finished = &finished, }; static void output_xdg_output(struct output *output) { struct private *m = output->mod->private; if (m->xdg_output_manager == NULL) return; if (output->xdg_output != NULL) return; output->xdg_output = zxdg_output_manager_v1_get_xdg_output(m->xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct module *mod = data; struct private *m = mod->private; if (strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { if (!verify_iface_version(interface, version, required_manager_interface_version)) return; m->manager_wl_name = name; } else if (strcmp(interface, wl_output_interface.name) == 0) { const uint32_t required = 3; if (!verify_iface_version(interface, version, required)) return; struct output output = { .mod = mod, .wl_name = name, .wl_output = wl_registry_bind(registry, name, &wl_output_interface, required), }; mtx_lock(&mod->lock); tll_push_back(m->outputs, output); output_xdg_output(&tll_back(m->outputs)); mtx_unlock(&mod->lock); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { const uint32_t required = 2; if (!verify_iface_version(interface, version, required)) return; m->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required); mtx_lock(&mod->lock); tll_foreach(m->outputs, it) output_xdg_output(&it->item); mtx_unlock(&mod->lock); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct module *mod = data; struct private *m = mod->private; mtx_lock(&mod->lock); tll_foreach(m->outputs, it) { const struct output *output = &it->item; if (output->wl_name == name) { /* Loop all toplevels */ tll_foreach(m->toplevels, it2) { /* And remove this output from their list of tracked * outputs */ tll_foreach(it2->item.outputs, it3) { if (it3->item == output) { tll_remove(it2->item.outputs, it3); break; } } } tll_remove(m->outputs, it); goto out; } } out: mtx_unlock(&mod->lock); } static const struct wl_registry_listener registry_listener = { .global = &handle_global, .global_remove = &handle_global_remove, }; static int run(struct module *mod) { struct private *m = mod->private; int ret = -1; struct wl_display *display = NULL; struct wl_registry *registry = NULL; if ((display = wl_display_connect(NULL)) == NULL) { LOG_ERR("no Wayland compositor running"); goto out; } if ((registry = wl_display_get_registry(display)) == NULL || wl_registry_add_listener(registry, ®istry_listener, mod) != 0) { LOG_ERR("failed to get Wayland registry"); goto out; } wl_display_roundtrip(display); if (m->manager_wl_name == 0) { LOG_ERR("compositor does not implement the foreign-toplevel-manager interface"); goto out; } m->manager = wl_registry_bind(registry, m->manager_wl_name, &zwlr_foreign_toplevel_manager_v1_interface, required_manager_interface_version); zwlr_foreign_toplevel_manager_v1_add_listener(m->manager, &manager_listener, mod); while (true) { wl_display_flush(display); struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = wl_display_get_fd(display), .events = POLLIN}, }; int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (r < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & (POLLIN | POLLHUP)) { ret = 0; break; } if (fds[1].revents & POLLHUP) { LOG_ERR("disconnected from the Wayland compositor"); break; } assert(fds[1].revents & POLLIN); wl_display_dispatch(display); } out: tll_foreach(m->toplevels, it) { toplevel_free(&it->item); tll_remove(m->toplevels, it); } tll_foreach(m->outputs, it) { output_free(&it->item); tll_remove(m->outputs, it); } if (m->xdg_output_manager != NULL) zxdg_output_manager_v1_destroy(m->xdg_output_manager); if (m->manager != NULL) zwlr_foreign_toplevel_manager_v1_destroy(m->manager); if (registry != NULL) wl_registry_destroy(registry); if (display != NULL) wl_display_disconnect(display); return ret; } static struct module * ftop_new(struct particle *label, bool all_monitors) { struct private *m = calloc(1, sizeof(*m)); m->template = label; m->all_monitors = all_monitors; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *all_monitors = yml_get_value(node, "all-monitors"); return ftop_new(conf_to_particle(c, inherited), all_monitors != NULL ? yml_value_as_bool(all_monitors) : false); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"all-monitors", false, &conf_verify_bool}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_foreign_toplevel_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_foreign_toplevel_iface"))); #endif yambar-1.11.0/modules/i3-common.c000066400000000000000000000220401460770427600164750ustar00rootroot00000000000000#include "i3-common.h" #include #include #include #include #include #if defined(ENABLE_X11) #include #include #endif #include #define LOG_MODULE "i3:common" #define LOG_ENABLE_DBG 0 #include "../log.h" #if defined(ENABLE_X11) #include "../xcb.h" #endif #include "i3-ipc.h" #if defined(ENABLE_X11) static bool get_socket_address_x11(struct sockaddr_un *addr) { int default_screen; xcb_connection_t *conn = xcb_connect(NULL, &default_screen); if (xcb_connection_has_error(conn) > 0) { LOG_ERR("failed to connect to X"); xcb_disconnect(conn); return false; } xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen); xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH"); assert(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(conn, false, screen->root, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path)); xcb_generic_error_t *err = NULL; xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, cookie, &err); bool ret = false; if (err != NULL) { LOG_ERR("failed to get i3 socket path: %s", xcb_error(err)); goto err; } const int len = xcb_get_property_value_length(reply); assert(len < sizeof(addr->sun_path)); if (len == 0) { LOG_ERR("failed to get i3 socket path: empty reply"); goto err; } memcpy(addr->sun_path, xcb_get_property_value(reply), len); addr->sun_path[len] = '\0'; ret = true; err: free(err); free(reply); xcb_disconnect(conn); return ret; } #endif bool i3_get_socket_address(struct sockaddr_un *addr) { *addr = (struct sockaddr_un){.sun_family = AF_UNIX}; const char *sway_sock = getenv("SWAYSOCK"); if (sway_sock == NULL) { sway_sock = getenv("I3SOCK"); if (sway_sock == NULL) { #if defined(ENABLE_X11) return get_socket_address_x11(addr); #else return false; #endif } } strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); return true; } bool i3_send_pkg(int sock, int cmd, char *data) { const size_t size = data != NULL ? strlen(data) : 0; const i3_ipc_header_t hdr = {.magic = I3_IPC_MAGIC, .size = size, .type = cmd}; if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) return false; if (data != NULL) { if (write(sock, data, size) != (ssize_t)size) return false; } return true; } bool i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *cbs, void *data) { /* Initial reply typically requires a couple of KB. But we often * need more later. For example, switching workspaces can result * in quite big notification messages. */ size_t reply_buf_size = 4096; char *buf = malloc(reply_buf_size); size_t buf_idx = 0; bool err = false; while (!err) { struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}, {.fd = sock, .events = POLLIN}}; int res = poll(fds, 2, -1); if (res <= 0) { LOG_ERRNO("failed to poll()"); err = true; break; } if (fds[0].revents & POLLIN) { LOG_DBG("aborted"); break; } if (fds[1].revents & POLLHUP) { LOG_DBG("disconnected"); break; } assert(fds[1].revents & POLLIN); /* Grow receive buffer, if necessary */ if (buf_idx == reply_buf_size) { LOG_DBG("growing reply buffer: %zu -> %zu", reply_buf_size, reply_buf_size * 2); char *new_buf = realloc(buf, reply_buf_size * 2); if (new_buf == NULL) { LOG_ERR("failed to grow reply buffer from %zu to %zu bytes", reply_buf_size, reply_buf_size * 2); err = true; break; } buf = new_buf; reply_buf_size *= 2; } assert(reply_buf_size > buf_idx); ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx); if (bytes < 0) { LOG_ERRNO("failed to read from i3's socket"); err = true; break; } buf_idx += bytes; while (!err && buf_idx >= sizeof(i3_ipc_header_t)) { const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf; if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) { LOG_ERR("i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"", (int)sizeof(hdr->magic), I3_IPC_MAGIC, (int)sizeof(hdr->magic), hdr->magic); err = true; break; } size_t total_size = sizeof(i3_ipc_header_t) + hdr->size; if (total_size > buf_idx) { LOG_DBG("got %zd bytes, need %zu", bytes, total_size); break; } LOG_DBG("header: type=%x", hdr->type); /* Json-c expects a NULL-terminated string */ char json_str[hdr->size + 1]; memcpy(json_str, &buf[sizeof(*hdr)], hdr->size); json_str[hdr->size] = '\0'; // printf("raw: %s\n", json_str); LOG_DBG("raw: %s\n", json_str); // json_tokener *tokener = json_tokener_new(); struct json_object *json = json_tokener_parse(json_str); if (json == NULL) { LOG_ERR("failed to parse json"); err = true; break; } i3_ipc_callback_t pkt_handler = NULL; switch (hdr->type) { case I3_IPC_REPLY_TYPE_COMMAND: pkt_handler = cbs->reply_command; break; case I3_IPC_REPLY_TYPE_WORKSPACES: pkt_handler = cbs->reply_workspaces; break; case I3_IPC_REPLY_TYPE_SUBSCRIBE: pkt_handler = cbs->reply_subscribe; break; case I3_IPC_REPLY_TYPE_OUTPUTS: pkt_handler = cbs->reply_outputs; break; case I3_IPC_REPLY_TYPE_TREE: pkt_handler = cbs->reply_tree; break; case I3_IPC_REPLY_TYPE_MARKS: pkt_handler = cbs->reply_marks; break; case I3_IPC_REPLY_TYPE_BAR_CONFIG: pkt_handler = cbs->reply_bar_config; break; case I3_IPC_REPLY_TYPE_VERSION: pkt_handler = cbs->reply_version; break; case I3_IPC_REPLY_TYPE_BINDING_MODES: pkt_handler = cbs->reply_binding_modes; break; case I3_IPC_REPLY_TYPE_CONFIG: pkt_handler = cbs->reply_config; break; case I3_IPC_REPLY_TYPE_TICK: pkt_handler = cbs->reply_tick; break; #if defined(I3_IPC_REPLY_TYPE_SYNC) case I3_IPC_REPLY_TYPE_SYNC: pkt_handler = cbs->reply_sync; break; #endif /* Sway extensions */ case 100: /* IPC_GET_INPUTS */ pkt_handler = cbs->reply_inputs; break; /* * Events */ case I3_IPC_EVENT_WORKSPACE: pkt_handler = cbs->event_workspace; break; case I3_IPC_EVENT_OUTPUT: pkt_handler = cbs->event_output; break; case I3_IPC_EVENT_MODE: pkt_handler = cbs->event_mode; break; case I3_IPC_EVENT_WINDOW: pkt_handler = cbs->event_window; break; case I3_IPC_EVENT_BARCONFIG_UPDATE: pkt_handler = cbs->event_barconfig_update; break; case I3_IPC_EVENT_BINDING: pkt_handler = cbs->event_binding; break; case I3_IPC_EVENT_SHUTDOWN: pkt_handler = cbs->event_shutdown; break; case I3_IPC_EVENT_TICK: pkt_handler = cbs->event_tick; break; /* Sway extensions */ #define SWAY_IPC_EVENT_INPUT ((1u << 31) | 21) case SWAY_IPC_EVENT_INPUT: pkt_handler = cbs->event_input; break; #undef SWAY_IPC_EVENT_INPUT default: LOG_ERR("unimplemented IPC reply type: %d", hdr->type); pkt_handler = NULL; break; } if (pkt_handler != NULL) err = !pkt_handler(sock, hdr->type, json, data); else LOG_DBG("no handler for reply/event %d; ignoring", hdr->type); json_object_put(json); assert(total_size <= buf_idx); memmove(buf, &buf[total_size], buf_idx - total_size); buf_idx -= total_size; } if (cbs->burst_done != NULL) cbs->burst_done(data); } free(buf); return !err; } yambar-1.11.0/modules/i3-common.h000066400000000000000000000025071460770427600165100ustar00rootroot00000000000000#pragma once #include #include #include #include #include bool i3_get_socket_address(struct sockaddr_un *addr); bool i3_send_pkg(int sock, int cmd, char *data); typedef bool (*i3_ipc_callback_t)(int sock, int type, const struct json_object *json, void *data); struct i3_ipc_callbacks { void (*burst_done)(void *data); i3_ipc_callback_t reply_command; i3_ipc_callback_t reply_workspaces; i3_ipc_callback_t reply_subscribe; i3_ipc_callback_t reply_outputs; i3_ipc_callback_t reply_tree; i3_ipc_callback_t reply_marks; i3_ipc_callback_t reply_bar_config; i3_ipc_callback_t reply_version; i3_ipc_callback_t reply_binding_modes; i3_ipc_callback_t reply_config; i3_ipc_callback_t reply_tick; i3_ipc_callback_t reply_sync; i3_ipc_callback_t reply_inputs; i3_ipc_callback_t event_workspace; i3_ipc_callback_t event_output; i3_ipc_callback_t event_mode; i3_ipc_callback_t event_window; i3_ipc_callback_t event_barconfig_update; i3_ipc_callback_t event_binding; i3_ipc_callback_t event_shutdown; i3_ipc_callback_t event_tick; /* Sway extensions */ i3_ipc_callback_t event_input; }; bool i3_receive_loop(int abort_fd, int sock, const struct i3_ipc_callbacks *callbacks, void *data); yambar-1.11.0/modules/i3-ipc.h000066400000000000000000000064661460770427600160030ustar00rootroot00000000000000/* * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * This public header defines the different constants and message types to use * for the IPC interface to i3 (see docs/ipc for more information). * */ #pragma once #include typedef struct i3_ipc_header { /* 6 = strlen(I3_IPC_MAGIC) */ char magic[6]; uint32_t size; uint32_t type; } __attribute__((packed)) i3_ipc_header_t; /* * Messages from clients to i3 * */ /** Never change this, only on major IPC breakage (don’t do that) */ #define I3_IPC_MAGIC "i3-ipc" /** Deprecated: use I3_IPC_MESSAGE_TYPE_RUN_COMMAND */ #define I3_IPC_MESSAGE_TYPE_COMMAND 0 /** The payload of the message will be interpreted as a command */ #define I3_IPC_MESSAGE_TYPE_RUN_COMMAND 0 /** Requests the current workspaces from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 /** Subscribe to the specified events */ #define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 /** Requests the current outputs from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 /** Requests the tree layout from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_TREE 4 /** Request the current defined marks from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 /** Request the configuration for a specific 'bar' */ #define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 /** Request the i3 version */ #define I3_IPC_MESSAGE_TYPE_GET_VERSION 7 /** Request a list of configured binding modes. */ #define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8 /** Request the raw last loaded i3 config. */ #define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 /** Send a tick event to all subscribers. */ #define I3_IPC_MESSAGE_TYPE_SEND_TICK 10 /** Trigger an i3 sync protocol message via IPC. */ #define I3_IPC_MESSAGE_TYPE_SYNC 11 /* * Messages from i3 to clients * */ #define I3_IPC_REPLY_TYPE_COMMAND 0 #define I3_IPC_REPLY_TYPE_WORKSPACES 1 #define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 #define I3_IPC_REPLY_TYPE_OUTPUTS 3 #define I3_IPC_REPLY_TYPE_TREE 4 #define I3_IPC_REPLY_TYPE_MARKS 5 #define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 #define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_CONFIG 9 #define I3_IPC_REPLY_TYPE_TICK 10 #define I3_IPC_REPLY_TYPE_SYNC 11 /* * Events from i3 to clients. Events have the first bit set high. * */ #define I3_IPC_EVENT_MASK (1UL << 31) /* The workspace event will be triggered upon changes in the workspace list */ #define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) /* The output event will be triggered upon changes in the output list */ #define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) /* The output event will be triggered upon mode changes */ #define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2) /* The window event will be triggered upon window changes */ #define I3_IPC_EVENT_WINDOW (I3_IPC_EVENT_MASK | 3) /** Bar config update will be triggered to update the bar config */ #define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4) /** The binding event will be triggered when bindings run */ #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) /** The shutdown event will be triggered when the ipc shuts down */ #define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) /** The tick event will be sent upon a tick IPC message */ #define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7) yambar-1.11.0/modules/i3.c000066400000000000000000000747751460770427600152350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #define LOG_MODULE "i3" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "i3-common.h" #include "i3-ipc.h" enum sort_mode { SORT_NONE, SORT_NATIVE, SORT_ASCENDING, SORT_DESCENDING }; struct ws_content { char *name; struct particle *content; }; struct workspace { int id; char *name; int name_as_int; /* -1 if name is not a decimal number */ bool persistent; char *output; bool visible; bool focused; bool urgent; bool empty; struct { unsigned id; char *title; char *application; pid_t pid; } window; }; struct private { int left_spacing; int right_spacing; bool dirty; char *mode; struct { struct ws_content *v; size_t count; } ws_content; bool strip_workspace_numbers; enum sort_mode sort_mode; tll(struct workspace) workspaces; size_t persistent_count; char **persistent_workspaces; }; static int workspace_name_as_int(const char *name) { int name_as_int = 0; /* First check for N:name pattern (set $ws1 “1:foobar”) */ const char *colon = strchr(name, ':'); if (colon != NULL) { for (const char *p = name; p < colon; p++) { if (!(*p >= '0' && *p < '9')) return -1; name_as_int *= 10; name_as_int += *p - '0'; } return name_as_int; } /* Then, if the name is a number *only* (set $ws1 1) */ for (const char *p = name; *p != '\0'; p++) { if (!(*p >= '0' && *p <= '9')) return -1; name_as_int *= 10; name_as_int += *p - '0'; } return name_as_int; } static bool workspace_from_json(const struct json_object *json, struct workspace *ws) { /* Always present */ struct json_object *id, *name, *output; if (!json_object_object_get_ex(json, "id", &id) || !json_object_object_get_ex(json, "name", &name) || !json_object_object_get_ex(json, "output", &output)) { LOG_ERR("workspace reply/event without 'name' and/or 'output' " "properties"); return false; } /* Sway only */ struct json_object *focus = NULL; json_object_object_get_ex(json, "focus", &focus); /* Optional */ struct json_object *visible = NULL, *focused = NULL, *urgent = NULL; json_object_object_get_ex(json, "visible", &visible); json_object_object_get_ex(json, "focused", &focused); json_object_object_get_ex(json, "urgent", &urgent); const char *name_as_string = json_object_get_string(name); const size_t node_count = focus != NULL ? json_object_array_length(focus) : 0; const bool is_empty = node_count == 0; int name_as_int = workspace_name_as_int(name_as_string); *ws = (struct workspace){ .id = json_object_get_int(id), .name = strdup(name_as_string), .name_as_int = name_as_int, .persistent = false, .output = strdup(json_object_get_string(output)), .visible = json_object_get_boolean(visible), .focused = json_object_get_boolean(focused), .urgent = json_object_get_boolean(urgent), .empty = is_empty && json_object_get_boolean(focused), .window = {.title = NULL, .pid = -1}, }; return true; } static void workspace_free_persistent(struct workspace *ws) { free(ws->output); ws->output = NULL; free(ws->window.title); ws->window.title = NULL; free(ws->window.application); ws->window.application = NULL; ws->id = -1; } static void workspace_free(struct workspace *ws) { workspace_free_persistent(ws); free(ws->name); ws->name = NULL; } static void workspaces_free(struct private *m, bool free_persistent) { tll_foreach(m->workspaces, it) { if (free_persistent || !it->item.persistent) { workspace_free(&it->item); tll_remove(m->workspaces, it); } } } static void workspace_add(struct private *m, struct workspace ws) { switch (m->sort_mode) { case SORT_NONE: tll_push_back(m->workspaces, ws); return; case SORT_NATIVE: if (ws.name_as_int >= 0) { tll_foreach(m->workspaces, it) { if (it->item.name_as_int < 0) continue; if (it->item.name_as_int > ws.name_as_int) { tll_insert_before(m->workspaces, it, ws); return; } } }; tll_push_back(m->workspaces, ws); return; case SORT_ASCENDING: if (ws.name_as_int >= 0) { tll_foreach(m->workspaces, it) { if (it->item.name_as_int < 0) continue; if (it->item.name_as_int > ws.name_as_int) { tll_insert_before(m->workspaces, it, ws); return; } } } else { tll_foreach(m->workspaces, it) { if (strcoll(it->item.name, ws.name) > 0 || it->item.name_as_int >= 0) { tll_insert_before(m->workspaces, it, ws); return; } } } tll_push_back(m->workspaces, ws); return; case SORT_DESCENDING: if (ws.name_as_int >= 0) { tll_foreach(m->workspaces, it) { if (it->item.name_as_int < ws.name_as_int) { tll_insert_before(m->workspaces, it, ws); return; } } } else { tll_foreach(m->workspaces, it) { if (it->item.name_as_int >= 0) continue; if (strcoll(it->item.name, ws.name) < 0) { tll_insert_before(m->workspaces, it, ws); return; } } } tll_push_back(m->workspaces, ws); return; } } static void workspace_del(struct private *m, int id) { tll_foreach(m->workspaces, it) { struct workspace *ws = &it->item; if (ws->id != id) continue; workspace_free(ws); tll_remove(m->workspaces, it); break; } } static struct workspace * workspace_lookup(struct private *m, int id) { tll_foreach(m->workspaces, it) { struct workspace *ws = &it->item; if (ws->id == id) return ws; } return NULL; } static struct workspace * workspace_lookup_by_name(struct private *m, const char *name) { tll_foreach(m->workspaces, it) { struct workspace *ws = &it->item; if (strcmp(ws->name, name) == 0) return ws; } return NULL; } static bool handle_get_version_reply(int sock, int type, const struct json_object *json, void *_m) { struct json_object *version; if (!json_object_object_get_ex(json, "human_readable", &version)) { LOG_ERR("version reply without 'humand_readable' property"); return false; } LOG_INFO("i3: %s", json_object_get_string(version)); return true; } static bool handle_subscribe_reply(int sock, int type, const struct json_object *json, void *_m) { struct json_object *success; if (!json_object_object_get_ex(json, "success", &success)) { LOG_ERR("subscribe reply without 'success' property"); return false; } if (!json_object_get_boolean(success)) { LOG_ERR("failed to subscribe"); return false; } return true; } static bool workspace_update_or_add(struct private *m, const struct json_object *ws_json) { struct json_object *_id; if (!json_object_object_get_ex(ws_json, "id", &_id)) return false; const int id = json_object_get_int(_id); struct workspace *already_exists = workspace_lookup(m, id); if (already_exists == NULL) { /* * No workspace with this ID. * * Try looking it up again, but this time using the name. If * we get a match, check if it’s an empty, persistent * workspace, and if so, use it. * * This is necessary, since empty, persistent workspaces don’t * exist in the i3/Sway server, and thus we don’t _have_ an * ID. */ struct json_object *_name; if (json_object_object_get_ex(ws_json, "name", &_name)) { const char *name = json_object_get_string(_name); if (name != NULL) { struct workspace *maybe_persistent = workspace_lookup_by_name(m, name); if (maybe_persistent != NULL && maybe_persistent->persistent && maybe_persistent->id < 0) { already_exists = maybe_persistent; } } } } if (already_exists != NULL) { bool persistent = already_exists->persistent; assert(persistent); workspace_free(already_exists); if (!workspace_from_json(ws_json, already_exists)) return false; already_exists->persistent = persistent; } else { struct workspace ws; if (!workspace_from_json(ws_json, &ws)) return false; workspace_add(m, ws); } return true; } static bool handle_get_workspaces_reply(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; mtx_lock(&mod->lock); workspaces_free(m, false); m->dirty = true; size_t count = json_object_array_length(json); for (size_t i = 0; i < count; i++) { if (!workspace_update_or_add(m, json_object_array_get_idx(json, i))) goto err; } mtx_unlock(&mod->lock); return true; err: workspaces_free(m, false); mtx_unlock(&mod->lock); return false; } static bool handle_workspace_event(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; struct json_object *change; if (!json_object_object_get_ex(json, "change", &change)) { LOG_ERR("workspace event without 'change' property"); return false; } const char *change_str = json_object_get_string(change); bool is_init = strcmp(change_str, "init") == 0; bool is_empty = strcmp(change_str, "empty") == 0; bool is_focused = strcmp(change_str, "focus") == 0; bool is_rename = strcmp(change_str, "rename") == 0; bool is_move = strcmp(change_str, "move") == 0; bool is_urgent = strcmp(change_str, "urgent") == 0; bool is_reload = strcmp(change_str, "reload") == 0; struct json_object *current, *_current_id; if ((!json_object_object_get_ex(json, "current", ¤t) || !json_object_object_get_ex(current, "id", &_current_id)) && !is_reload) { LOG_ERR("workspace event without 'current' and/or 'id' properties"); return false; } int current_id = json_object_get_int(_current_id); mtx_lock(&mod->lock); if (is_init) { if (!workspace_update_or_add(m, current)) goto err; } else if (is_empty) { struct workspace *ws = workspace_lookup(m, current_id); assert(ws != NULL); if (!ws->persistent) workspace_del(m, current_id); else { workspace_free_persistent(ws); ws->empty = true; } } else if (is_focused) { struct json_object *old, *_old_id, *urgent; if (!json_object_object_get_ex(json, "old", &old) || !json_object_object_get_ex(old, "id", &_old_id) || !json_object_object_get_ex(current, "urgent", &urgent)) { LOG_ERR("workspace 'focused' event without 'old', 'name' and/or 'urgent' property"); mtx_unlock(&mod->lock); return false; } struct workspace *w = workspace_lookup(m, current_id); assert(w != NULL); LOG_DBG("w: %s", w->name); /* Mark all workspaces on current's output invisible */ tll_foreach(m->workspaces, it) { struct workspace *ws = &it->item; if (ws->output != NULL && strcmp(ws->output, w->output) == 0) ws->visible = false; } w->urgent = json_object_get_boolean(urgent); w->focused = true; w->visible = true; /* Old workspace is no longer focused */ int old_id = json_object_get_int(_old_id); struct workspace *old_w = workspace_lookup(m, old_id); if (old_w != NULL) old_w->focused = false; } else if (is_rename) { struct workspace *w = workspace_lookup(m, current_id); assert(w != NULL); struct json_object *_current_name; if (!json_object_object_get_ex(current, "name", &_current_name)) { LOG_ERR("workspace 'rename' event without 'name' property"); mtx_unlock(&mod->lock); return false; } free(w->name); w->name = strdup(json_object_get_string(_current_name)); w->name_as_int = workspace_name_as_int(w->name); /* Re-add the workspace to ensure correct sorting */ struct workspace ws = *w; tll_foreach(m->workspaces, it) { if (it->item.id == current_id) { tll_remove(m->workspaces, it); break; } } workspace_add(m, ws); } else if (is_move) { struct workspace *w = workspace_lookup(m, current_id); assert(w != NULL); struct json_object *_current_output; if (!json_object_object_get_ex(current, "output", &_current_output)) { LOG_ERR("workspace 'move' event without 'output' property"); mtx_unlock(&mod->lock); return false; } free(w->output); w->output = strdup(json_object_get_string(_current_output)); /* * If the moved workspace was focused, schedule a full update because * visibility for other workspaces may have changed. */ if (w->focused) { i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); } } else if (is_urgent) { struct json_object *urgent; if (!json_object_object_get_ex(current, "urgent", &urgent)) { LOG_ERR("workspace 'urgent' event without 'urgent' property"); mtx_unlock(&mod->lock); return false; } struct workspace *w = workspace_lookup(m, current_id); w->urgent = json_object_get_boolean(urgent); } else if (is_reload) { /* Schedule full update to check if anything was changed * during reload */ i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); } else { LOG_WARN("unimplemented workspace event '%s'", change_str); } m->dirty = true; mtx_unlock(&mod->lock); return true; err: mtx_unlock(&mod->lock); return false; } static bool handle_window_event(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; struct json_object *change; if (!json_object_object_get_ex(json, "change", &change)) { LOG_ERR("window event without 'change' property"); return false; } const char *change_str = json_object_get_string(change); bool is_focus = strcmp(change_str, "focus") == 0; bool is_close = strcmp(change_str, "close") == 0; bool is_title = strcmp(change_str, "title") == 0; if (!is_focus && !is_close && !is_title) return true; mtx_lock(&mod->lock); struct workspace *ws = NULL; size_t focused = 0; tll_foreach(m->workspaces, it) { if (it->item.focused) { ws = &it->item; focused++; } } assert(focused == 1); assert(ws != NULL); struct json_object *container, *id, *name; if (!json_object_object_get_ex(json, "container", &container) || !json_object_object_get_ex(container, "id", &id) || !json_object_object_get_ex(container, "name", &name)) { mtx_unlock(&mod->lock); LOG_ERR("window event without 'container' with 'id' and 'name'"); return false; } if ((is_close || is_title) && ws->window.id != json_object_get_int(id)) { /* Ignore close event and title changed event if it's not current window */ mtx_unlock(&mod->lock); return true; } if (is_close) { free(ws->window.title); free(ws->window.application); ws->window.id = -1; ws->window.title = ws->window.application = NULL; ws->window.pid = -1; m->dirty = true; mtx_unlock(&mod->lock); return true; } free(ws->window.title); const char *title = json_object_get_string(name); ws->window.title = title != NULL ? strdup(title) : NULL; ws->window.id = json_object_get_int(id); /* * Sway only! * * Use 'app_id' for 'application' tag, if it exists. * * Otherwise, use 'pid' if it exists, and read application name * from /proc/zpid>/comm */ struct json_object *app_id; struct json_object *pid; if (json_object_object_get_ex(container, "app_id", &app_id) && json_object_get_string(app_id) != NULL) { free(ws->window.application); ws->window.application = strdup(json_object_get_string(app_id)); LOG_DBG("application: \"%s\", via 'app_id'", ws->window.application); } /* If PID has changed, update application name from /proc//comm */ else if (json_object_object_get_ex(container, "pid", &pid) && ws->window.pid != json_object_get_int(pid)) { ws->window.pid = json_object_get_int(pid); char path[64]; snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid); int fd = open(path, O_RDONLY); if (fd == -1) { /* Application may simply have terminated */ free(ws->window.application); ws->window.application = NULL; ws->window.pid = -1; m->dirty = true; mtx_unlock(&mod->lock); return true; } char application[128]; ssize_t bytes = read(fd, application, sizeof(application)); assert(bytes >= 0); application[bytes - 1] = '\0'; free(ws->window.application); ws->window.application = strdup(application); close(fd); LOG_DBG("application: \"%s\", via 'pid'", ws->window.application); } m->dirty = true; mtx_unlock(&mod->lock); return true; } static bool handle_mode_event(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; struct json_object *change; if (!json_object_object_get_ex(json, "change", &change)) { LOG_ERR("mode event without 'change' property"); return false; } const char *current_mode = json_object_get_string(change); mtx_lock(&mod->lock); { free(m->mode); m->mode = strdup(current_mode); m->dirty = true; } mtx_unlock(&mod->lock); return true; } static void burst_done(void *_mod) { struct module *mod = _mod; struct private *m = mod->private; if (m->dirty) { m->dirty = false; mod->bar->refresh(mod->bar); } } static int run(struct module *mod) { struct sockaddr_un addr; if (!i3_get_socket_address(&addr)) return 1; int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sock == -1) { LOG_ERRNO("failed to create UNIX socket"); return 1; } int r = connect(sock, (const struct sockaddr *)&addr, sizeof(addr)); if (r == -1) { LOG_ERRNO("failed to connect to i3 socket"); close(sock); return 1; } struct private *m = mod->private; for (size_t i = 0; i < m->persistent_count; i++) { const char *name_as_string = m->persistent_workspaces[i]; int name_as_int = workspace_name_as_int(name_as_string); if (m->strip_workspace_numbers) { const char *colon = strchr(name_as_string, ':'); if (colon != NULL) name_as_string = colon++; } struct workspace ws = { .id = -1, .name = strdup(name_as_string), .name_as_int = name_as_int, .persistent = true, .empty = true, }; workspace_add(m, ws); } i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL); i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\", \"mode\"]"); i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); static const struct i3_ipc_callbacks callbacks = { .burst_done = &burst_done, .reply_version = &handle_get_version_reply, .reply_subscribe = &handle_subscribe_reply, .reply_workspaces = &handle_get_workspaces_reply, .event_workspace = &handle_workspace_event, .event_window = &handle_window_event, .event_mode = &handle_mode_event, }; bool ret = i3_receive_loop(mod->abort_fd, sock, &callbacks, mod); close(sock); return ret ? 0 : 1; } static void destroy(struct module *mod) { struct private *m = mod->private; for (size_t i = 0; i < m->ws_content.count; i++) { struct particle *p = m->ws_content.v[i].content; p->destroy(p); free(m->ws_content.v[i].name); } free(m->ws_content.v); workspaces_free(m, true); for (size_t i = 0; i < m->persistent_count; i++) free(m->persistent_workspaces[i]); free(m->persistent_workspaces); free(m->mode); free(m); module_default_destroy(mod); } static struct ws_content * ws_content_for_name(struct private *m, const char *name) { for (size_t i = 0; i < m->ws_content.count; i++) { struct ws_content *content = &m->ws_content.v[i]; if (strcmp(content->name, name) == 0) return content; } return NULL; } static const char * description(const struct module *mod) { return "i3/sway"; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); size_t particle_count = 0; struct exposable *particles[tll_length(m->workspaces) + 1]; struct exposable *current = NULL; tll_foreach(m->workspaces, it) { struct workspace *ws = &it->item; const struct ws_content *template = NULL; /* Lookup content template for workspace. Fall back to default * template if this workspace doesn't have a specific * template */ if (ws->name == NULL) { LOG_ERR("%d %d", ws->name_as_int, ws->id); } template = ws_content_for_name(m, ws->name); if (template == NULL) { LOG_DBG("no ws template for %s, using default template", ws->name); template = ws_content_for_name(m, ""); } const char *state = ws->urgent ? "urgent" : ws->visible ? ws->focused ? "focused" : "unfocused" : "invisible"; LOG_DBG("name=%s (name-as-int=%d): visible=%s, focused=%s, urgent=%s, empty=%s, state=%s, " "application=%s, title=%s, mode=%s", ws->name, ws->name_as_int, ws->visible ? "yes" : "no", ws->focused ? "yes" : "no", ws->urgent ? "yes" : "no", ws->empty ? "yes" : "no", state, ws->window.application, ws->window.title, m->mode); const char *name = ws->name; if (m->strip_workspace_numbers) { const char *colon = strchr(name, ':'); if (colon != NULL) name = colon + 1; } struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", name), tag_new_bool(mod, "visible", ws->visible), tag_new_bool(mod, "focused", ws->focused), tag_new_bool(mod, "urgent", ws->urgent), tag_new_bool(mod, "empty", ws->empty), tag_new_string(mod, "state", state), tag_new_string(mod, "application", ws->window.application), tag_new_string(mod, "title", ws->window.title), tag_new_string(mod, "mode", m->mode), }, .count = 9, }; if (ws->focused) { const struct ws_content *cur = ws_content_for_name(m, "current"); if (cur != NULL) current = cur->content->instantiate(cur->content, &tags); } if (template == NULL) { LOG_WARN("no ws template for %s, and no default template available", ws->name); } else { particles[particle_count++] = template->content->instantiate(template->content, &tags); } tag_set_destroy(&tags); } if (current != NULL) particles[particle_count++] = current; mtx_unlock(&mod->lock); return dynlist_exposable_new(particles, particle_count, m->left_spacing, m->right_spacing); } /* Maps workspace name to a content particle. */ struct i3_workspaces { const char *name; struct particle *content; }; static struct module * i3_new(struct i3_workspaces workspaces[], size_t workspace_count, int left_spacing, int right_spacing, enum sort_mode sort_mode, size_t persistent_count, const char *persistent_workspaces[static persistent_count], bool strip_workspace_numbers) { struct private *m = calloc(1, sizeof(*m)); m->mode = strdup("default"); m->left_spacing = left_spacing; m->right_spacing = right_spacing; m->ws_content.count = workspace_count; m->ws_content.v = malloc(workspace_count * sizeof(m->ws_content.v[0])); for (size_t i = 0; i < workspace_count; i++) { m->ws_content.v[i].name = strdup(workspaces[i].name); m->ws_content.v[i].content = workspaces[i].content; } m->strip_workspace_numbers = strip_workspace_numbers; m->sort_mode = sort_mode; m->persistent_count = persistent_count; m->persistent_workspaces = calloc(persistent_count, sizeof(m->persistent_workspaces[0])); for (size_t i = 0; i < persistent_count; i++) m->persistent_workspaces[i] = strdup(persistent_workspaces[i]); struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *spacing = yml_get_value(node, "spacing"); const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); const struct yml_node *sort = yml_get_value(node, "sort"); const struct yml_node *persistent = yml_get_value(node, "persistent"); const struct yml_node *strip_workspace_number = yml_get_value(node, "strip-workspace-numbers"); int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int right = spacing != NULL ? yml_value_as_int(spacing) : right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; const char *sort_value = sort != NULL ? yml_value_as_string(sort) : NULL; enum sort_mode sort_mode = sort_value == NULL ? SORT_NONE : strcmp(sort_value, "none") == 0 ? SORT_NONE : strcmp(sort_value, "native") == 0 ? SORT_NATIVE : strcmp(sort_value, "ascending") == 0 ? SORT_ASCENDING : SORT_DESCENDING; const size_t persistent_count = persistent != NULL ? yml_list_length(persistent) : 0; const char *persistent_workspaces[persistent_count]; if (persistent != NULL) { size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(persistent); it.node != NULL; yml_list_next(&it), idx++) { persistent_workspaces[idx] = yml_value_as_string(it.node); } } struct i3_workspaces workspaces[yml_dict_length(c)]; size_t idx = 0; for (struct yml_dict_iter it = yml_dict_iter(c); it.key != NULL; yml_dict_next(&it), idx++) { workspaces[idx].name = yml_value_as_string(it.key); workspaces[idx].content = conf_to_particle(it.value, inherited); } return i3_new(workspaces, yml_dict_length(c), left, right, sort_mode, persistent_count, persistent_workspaces, (strip_workspace_number != NULL ? yml_value_as_bool(strip_workspace_number) : false)); } static bool verify_content(keychain_t *chain, const struct yml_node *node) { if (!yml_is_dict(node)) { LOG_ERR("%s: must be a dictionary of workspace-name: particle mappings", conf_err_prefix(chain, node)); return false; } for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) { const char *key = yml_value_as_string(it.key); if (key == NULL) { LOG_ERR("%s: key must be a string (a i3 workspace name)", conf_err_prefix(chain, it.key)); return false; } if (!conf_verify_particle(chain_push(chain, key), it.value)) return false; chain_pop(chain); } return true; } static bool verify_sort(keychain_t *chain, const struct yml_node *node) { return conf_verify_enum(chain, node, (const char *[]){"none", "native", "ascending", "descending"}, 4); } static bool verify_persistent(keychain_t *chain, const struct yml_node *node) { return conf_verify_list(chain, node, &conf_verify_string); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, {"sort", false, &verify_sort}, {"persistent", false, &verify_persistent}, {"strip-workspace-numbers", false, &conf_verify_bool}, {"content", true, &verify_content}, {"anchors", false, NULL}, {NULL, false, NULL}, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_i3_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_i3_iface"))); #endif yambar-1.11.0/modules/label.c000066400000000000000000000032101460770427600157510ustar00rootroot00000000000000#include #include #include #include "../config-verify.h" #include "../config.h" #include "../module.h" #include "../plugin.h" struct private { struct particle *label; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "label"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; return m->label->instantiate(m->label, NULL); } static int run(struct module *mod) { return 0; } static struct module * label_new(struct particle *label) { struct private *m = calloc(1, sizeof(*m)); m->label = label; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); return label_new(conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_label_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_label_iface"))); #endif yambar-1.11.0/modules/mem.c000066400000000000000000000120771460770427600154630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "mem" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" static const long min_poll_interval = 250; struct private { struct particle *label; uint16_t interval; uint64_t mem_free; uint64_t mem_total; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "mem"; } static bool get_mem_stats(uint64_t *mem_free, uint64_t *mem_total) { bool mem_total_found = false; bool mem_free_found = false; FILE *fp = NULL; char *line = NULL; size_t len = 0; ssize_t read = 0; fp = fopen("/proc/meminfo", "r"); if (NULL == fp) { LOG_ERRNO("unable to open /proc/meminfo"); return false; } while ((read = getline(&line, &len, fp)) != -1) { if (strncmp(line, "MemTotal:", sizeof("MemTotal:") - 1) == 0) { read = sscanf(line + sizeof("MemTotal:") - 1, "%" SCNu64, mem_total); mem_total_found = (read == 1); } if (strncmp(line, "MemAvailable:", sizeof("MemAvailable:") - 1) == 0) { read = sscanf(line + sizeof("MemAvailable:"), "%" SCNu64, mem_free); mem_free_found = (read == 1); } } free(line); fclose(fp); return mem_free_found && mem_total_found; } static struct exposable * content(struct module *mod) { const struct private *p = mod->private; mtx_lock(&mod->lock); const uint64_t mem_free = p->mem_free; const uint64_t mem_total = p->mem_total; const uint64_t mem_used = mem_total - mem_free; double percent_used = ((double)mem_used * 100) / (mem_total + 1); double percent_free = ((double)mem_free * 100) / (mem_total + 1); struct tag_set tags = { .tags = (struct tag *[]){tag_new_int(mod, "free", mem_free * 1024), tag_new_int(mod, "used", mem_used * 1024), tag_new_int(mod, "total", mem_total * 1024), tag_new_int_range(mod, "percent_free", round(percent_free), 0, 100), tag_new_int_range(mod, "percent_used", round(percent_used), 0, 100)}, .count = 5, }; struct exposable *exposable = p->label->instantiate(p->label, &tags); tag_set_destroy(&tags); mtx_unlock(&mod->lock); return exposable; } static int run(struct module *mod) { const struct bar *bar = mod->bar; bar->refresh(bar); struct private *p = mod->private; while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, 1, p->interval); if (res < 0) { if (EINTR == errno) { continue; } LOG_ERRNO("unable to poll abort fd"); return -1; } if (fds[0].revents & POLLIN) break; mtx_lock(&mod->lock); p->mem_free = 0; p->mem_total = 0; if (!get_mem_stats(&p->mem_free, &p->mem_total)) { LOG_ERR("unable to retrieve the memory stats"); } mtx_unlock(&mod->lock); bar->refresh(bar); } return 0; } static struct module * mem_new(uint16_t interval, struct particle *label) { struct private *p = calloc(1, sizeof(*p)); p->label = label; p->interval = interval; struct module *mod = module_common_new(); mod->private = p; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *interval = yml_get_value(node, "poll-interval"); const struct yml_node *c = yml_get_value(node, "content"); return mem_new(interval == NULL ? min_poll_interval : yml_value_as_int(interval), conf_to_particle(c, inherited)); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; if (yml_value_as_int(node) < min_poll_interval) { LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"poll-interval", false, &conf_verify_poll_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_mem_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_mem_iface"))); #endif yambar-1.11.0/modules/meson.build000066400000000000000000000131141460770427600166740ustar00rootroot00000000000000module_sdk = declare_dependency(dependencies: [pixman, threads, tllist, fcft]) modules = [] # Optional deps alsa = dependency('alsa', required: get_option('plugin-alsa')) plugin_alsa_enabled = alsa.found() udev_backlight = dependency('libudev', required: get_option('plugin-backlight')) plugin_backlight_enabled = udev_backlight.found() udev_battery = dependency('libudev', required: get_option('plugin-battery')) plugin_battery_enabled = udev_battery.found() plugin_clock_enabled = get_option('plugin-clock').allowed() plugin_cpu_enabled = get_option('plugin-cpu').allowed() plugin_disk_io_enabled = get_option('plugin-disk-io').allowed() plugin_dwl_enabled = get_option('plugin-dwl').allowed() plugin_foreign_toplevel_enabled = backend_wayland and get_option('plugin-foreign-toplevel').allowed() plugin_mem_enabled = get_option('plugin-mem').allowed() mpd = dependency('libmpdclient', required: get_option('plugin-mpd')) plugin_mpd_enabled = mpd.found() json_i3 = dependency('json-c', required: get_option('plugin-i3')) plugin_i3_enabled = json_i3.found() plugin_label_enabled = get_option('plugin-label').allowed() plugin_network_enabled = get_option('plugin-network').allowed() pipewire = dependency('libpipewire-0.3', required: get_option('plugin-pipewire')) json_pipewire = dependency('json-c', required: get_option('plugin-pipewire')) plugin_pipewire_enabled = pipewire.found() and json_pipewire.found() pulse = dependency('libpulse', required: get_option('plugin-pulse')) plugin_pulse_enabled = pulse.found() udev_removables = dependency('libudev', required: get_option('plugin-removables')) plugin_removables_enabled = udev_removables.found() plugin_river_enabled = backend_wayland and get_option('plugin-river').allowed() plugin_script_enabled = get_option('plugin-script').allowed() json_sway_xkb = dependency('json-c', required: get_option('plugin-sway-xkb')) plugin_sway_xkb_enabled = json_sway_xkb.found() xcb_xkb = dependency('xcb-xkb', required: get_option('plugin-xkb')) plugin_xkb_enabled = backend_x11 and xcb_xkb.found() plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed() # Module name -> (source-list, dep-list) mod_data = {} if plugin_alsa_enabled mod_data += {'alsa': [[], [m, alsa]]} endif if plugin_backlight_enabled mod_data += {'backlight': [[], [m, udev_backlight]]} endif if plugin_battery_enabled mod_data += {'battery': [[], [udev_battery]]} endif if plugin_clock_enabled mod_data += {'clock': [[], []]} endif if plugin_cpu_enabled mod_data += {'cpu': [[], [m, dynlist]]} endif if plugin_disk_io_enabled mod_data += {'disk-io': [[], [dynlist]]} endif if plugin_dwl_enabled mod_data += {'dwl': [[], [dynlist]]} endif if plugin_mem_enabled mod_data += {'mem': [[], [m]]} endif if plugin_mpd_enabled mod_data += {'mpd': [[], [mpd]]} endif if plugin_i3_enabled mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]} endif if plugin_label_enabled mod_data += {'label': [[], []]} endif if plugin_network_enabled mod_data += {'network': [[], [dynlist]]} endif if plugin_pipewire_enabled mod_data += {'pipewire': [[], [m, pipewire, dynlist, json_pipewire]]} endif if plugin_pulse_enabled mod_data += {'pulse': [[], [m, pulse]]} endif if plugin_removables_enabled mod_data += {'removables': [[], [dynlist, udev_removables]]} endif if plugin_script_enabled mod_data += {'script': [[], []]} endif if plugin_sway_xkb_enabled mod_data += {'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json_sway_xkb]]} endif if plugin_xkb_enabled mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]} endif if plugin_xwindow_enabled mod_data += {'xwindow': [[], [xcb_stuff]]} endif if plugin_river_enabled river_proto_headers = [] river_proto_src = [] foreach prot : ['../external/river-status-unstable-v1.xml'] river_proto_headers += custom_target( prot.underscorify() + '-client-header', output: '@BASENAME@.h', input: prot, command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@']) river_proto_src += custom_target( prot.underscorify() + '-private-code', output: '@BASENAME@.c', input: prot, command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) endforeach mod_data += {'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist, wayland_client]]} endif if plugin_foreign_toplevel_enabled ftop_proto_headers = [] ftop_proto_src = [] foreach prot : ['../external/wlr-foreign-toplevel-management-unstable-v1.xml'] ftop_proto_headers += custom_target( prot.underscorify() + '-client-header', output: '@BASENAME@.h', input: prot, command: [wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@']) ftop_proto_src += custom_target( prot.underscorify() + '-private-code', output: '@BASENAME@.c', input: prot, command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) endforeach mod_data += {'foreign-toplevel': [[wl_proto_src + wl_proto_headers + ftop_proto_headers + ftop_proto_src], [m, dynlist, wayland_client]]} endif foreach mod, data : mod_data sources = data[0] deps = data[1] if plugs_as_libs shared_module(mod, '@0@.c'.format(mod), sources, dependencies: [module_sdk] + deps, name_prefix: 'module_', install: true, install_dir: join_paths(get_option('libdir'), 'yambar')) else modules += [declare_dependency( sources: ['@0@.c'.format(mod)] + sources, dependencies: [module_sdk] + deps, compile_args: '-DHAVE_PLUGIN_@0@'.format(mod.underscorify()))] endif endforeach yambar-1.11.0/modules/mpd.c000066400000000000000000000427731460770427600154730ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "mpd" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" struct private { char *host; uint16_t port; struct particle *label; struct mpd_connection *conn; enum mpd_state state; bool repeat; bool random; bool consume; int volume; char *album; char *artist; char *title; char *file; struct { uint64_t value; struct timespec when; } elapsed; uint64_t duration; thrd_t refresh_thread_id; int refresh_abort_fd; }; static void destroy(struct module *mod) { struct private *m = mod->private; if (m->refresh_thread_id != 0) { assert(m->refresh_abort_fd != -1); if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) { LOG_ERRNO("failed to signal abort to refresher thread"); } else { int res; thrd_join(m->refresh_thread_id, &res); } close(m->refresh_abort_fd); }; free(m->host); free(m->album); free(m->artist); free(m->title); free(m->file); assert(m->conn == NULL); m->label->destroy(m->label); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "mpd"; } static uint64_t timespec_diff_milli_seconds(const struct timespec *a, const struct timespec *b) { /* TODO */ uint64_t nsecs_a = a->tv_sec * 1000000000 + a->tv_nsec; uint64_t nsecs_b = b->tv_sec * 1000000000 + b->tv_nsec; assert(nsecs_a >= nsecs_b); uint64_t nsec_diff = nsecs_a - nsecs_b; return nsec_diff / 1000000; } static void secs_to_str(unsigned secs, char *s, size_t sz) { unsigned hours = secs / (60 * 60); unsigned minutes = secs % (60 * 60) / 60; secs %= 60; if (hours > 0) snprintf(s, sz, "%02u:%02u:%02u", hours, minutes, secs); else snprintf(s, sz, "%02u:%02u", minutes, secs); } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); mtx_lock(&mod->lock); /* Calculate what 'elapsed' is now */ uint64_t elapsed = m->elapsed.value; if (m->state == MPD_STATE_PLAY) { elapsed += timespec_diff_milli_seconds(&now, &m->elapsed.when); if (elapsed > m->duration) { LOG_DBG("dynamic update of elapsed overflowed: " "elapsed=%" PRIu64 ", duration=%" PRIu64, elapsed, m->duration); elapsed = m->duration; } } unsigned elapsed_secs = elapsed / 1000; unsigned duration_secs = m->duration / 1000; /* elapsed/duration as strings (e.g. 03:37) */ char pos[16], end[16]; secs_to_str(elapsed_secs, pos, sizeof(pos)); secs_to_str(duration_secs, end, sizeof(end)); /* State as string */ const char *state_str = NULL; if (m->conn == NULL) state_str = "offline"; else { switch (m->state) { case MPD_STATE_UNKNOWN: state_str = "unknown"; break; case MPD_STATE_STOP: state_str = "stopped"; break; case MPD_STATE_PAUSE: state_str = "paused"; break; case MPD_STATE_PLAY: state_str = "playing"; break; } } /* Tell particle to real-time track? */ enum tag_realtime_unit realtime = m->state == MPD_STATE_PLAY ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "state", state_str), tag_new_bool(mod, "repeat", m->repeat), tag_new_bool(mod, "random", m->random), tag_new_bool(mod, "consume", m->consume), tag_new_int_range(mod, "volume", m->volume, 0, 100), tag_new_string(mod, "album", m->album), tag_new_string(mod, "artist", m->artist), tag_new_string(mod, "title", m->title), tag_new_string(mod, "file", m->file), tag_new_string(mod, "pos", pos), tag_new_string(mod, "end", end), tag_new_int(mod, "duration", m->duration), tag_new_int_realtime( mod, "elapsed", elapsed, 0, m->duration, realtime), }, .count = 13, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } /* Returns true if aborted, false otherwise (regardless of whether * socket exists or not) */ static bool wait_for_socket_create(const struct module *mod) { const struct private *m = mod->private; assert(m->port == 0); char *copy = strdup(m->host); const char *base = basename(copy); const char *directory = dirname(copy); LOG_DBG("monitoring %s for %s to be created", directory, base); int fd = inotify_init(); if (fd == -1) { free(copy); return false; } int wd = inotify_add_watch(fd, directory, IN_CREATE); if (wd == -1) { close(fd); free(copy); return false; } bool have_mpd_socket = false; /* Check if socket already exists *and* is connectable */ struct stat st; if (stat(m->host, &st) == 0 && S_ISSOCK(st.st_mode)) { int s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, m->host, sizeof(addr.sun_path) - 1); int r = connect(s, (const struct sockaddr *)&addr, sizeof(addr)); if (r == 0) { LOG_DBG("%s: already exists, and is connectable", m->host); have_mpd_socket = true; } else { LOG_DBG("%s: already exists, but isn't connectable: %s", m->host, strerror(errno)); } close(s); } if (!have_mpd_socket) LOG_WARN("MPD doesn't appear to be running"); bool ret = false; while (!have_mpd_socket) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = fd, .events = POLLIN}}; if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = true; break; } assert(fds[1].revents & POLLIN); char buf[1024]; ssize_t len = read(fd, buf, sizeof(buf)); for (const char *ptr = buf; ptr < buf + len;) { const struct inotify_event *e = (const struct inotify_event *)ptr; LOG_DBG("inotify: CREATED: %s/%.*s", directory, e->len, e->name); if (strncmp(base, e->name, e->len) == 0) { LOG_DBG("MPD socket created"); have_mpd_socket = true; break; } ptr += sizeof(*e) + e->len; } } inotify_rm_watch(fd, wd); close(fd); free(copy); return ret; } static struct mpd_connection * connect_to_mpd(const struct module *mod) { const struct private *m = mod->private; struct mpd_connection *conn = mpd_connection_new(m->host, m->port, 0); if (conn == NULL) { LOG_ERR("failed to create MPD connection"); return NULL; } enum mpd_error merr = mpd_connection_get_error(conn); if (merr != MPD_ERROR_SUCCESS) { LOG_WARN("failed to connect to MPD: %s", mpd_connection_get_error_message(conn)); mpd_connection_free(conn); return NULL; } const unsigned *version = mpd_connection_get_server_version(conn); LOG_INFO("MPD %u.%u.%u", version[0], version[1], version[2]); return conn; } static bool update_status(struct module *mod) { struct private *m = mod->private; struct mpd_status *status = mpd_run_status(m->conn); if (status == NULL) { LOG_ERR("failed to get status: %s", mpd_connection_get_error_message(m->conn)); return false; } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); mtx_lock(&mod->lock); m->state = mpd_status_get_state(status); m->repeat = mpd_status_get_repeat(status); m->random = mpd_status_get_random(status); m->consume = mpd_status_get_consume(status); m->volume = mpd_status_get_volume(status); m->duration = mpd_status_get_total_time(status) * 1000; m->elapsed.value = mpd_status_get_elapsed_ms(status); m->elapsed.when = now; mtx_unlock(&mod->lock); mpd_status_free(status); struct mpd_song *song = mpd_run_current_song(m->conn); if (song == NULL && mpd_connection_get_error(m->conn) != MPD_ERROR_SUCCESS) { LOG_ERR("failed to get current song: %s", mpd_connection_get_error_message(m->conn)); return false; } if (song == NULL) { mtx_lock(&mod->lock); free(m->album); m->album = NULL; free(m->artist); m->artist = NULL; free(m->title); m->title = NULL; free(m->file); m->file = NULL; mtx_unlock(&mod->lock); } else { const char *album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); const char *file = mpd_song_get_uri(song); mtx_lock(&mod->lock); free(m->album); free(m->artist); free(m->title); free(m->file); m->album = album != NULL ? strdup(album) : NULL; m->artist = artist != NULL ? strdup(artist) : NULL; m->title = title != NULL ? strdup(title) : NULL; m->file = file != NULL ? strdup(file) : NULL; mtx_unlock(&mod->lock); mpd_song_free(song); } return true; } static int run(struct module *mod) { const struct bar *bar = mod->bar; struct private *m = mod->private; bool aborted = false; int ret = 0; while (!aborted && ret == 0) { if (m->conn != NULL) { mpd_connection_free(m->conn); m->conn = NULL; } /* Reset state */ mtx_lock(&mod->lock); free(m->album); m->album = NULL; free(m->artist); m->artist = NULL; free(m->title); m->title = NULL; free(m->file); m->file = NULL; m->state = MPD_STATE_UNKNOWN; m->elapsed.value = m->duration = 0; m->elapsed.when.tv_sec = m->elapsed.when.tv_nsec = 0; mtx_unlock(&mod->lock); /* Keep trying to connect, until we succeed */ while (!aborted && ret == 0) { if (m->port == 0) { /* Use inotify to watch for socket creation */ aborted = wait_for_socket_create(mod); if (aborted) break; } m->conn = connect_to_mpd(mod); if (m->conn != NULL) break; /* * In case we can't use inotify to watch for socket * creation (for example, we're connecting to a remote * host), wait for a while until we try to re-connect * again. */ while (!aborted) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; int res = poll(fds, sizeof(fds) / sizeof(fds[0]), 10 * 1000); if (res < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); ret = 1; break; } if (res == 1) { assert(fds[0].revents & POLLIN); aborted = true; } } } if (aborted || ret != 0) break; /* Initial state (after establishing a connection) */ assert(m->conn != NULL); if (!update_status(mod)) continue; bar->refresh(bar); /* Monitor for events from MPD */ while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = mpd_connection_get_fd(m->conn), .events = POLLIN}, }; if (!mpd_send_idle(m->conn)) { LOG_ERR("failed to send IDLE command: %s", mpd_connection_get_error_message(m->conn)); break; } if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); ret = 1; break; } if (fds[0].revents & POLLIN) { aborted = true; break; } if (fds[1].revents & POLLHUP) { LOG_WARN("disconnected from MPD daemon"); break; } if (fds[1].revents & POLLIN) { enum mpd_idle idle __attribute__((unused)) = mpd_recv_idle(m->conn, true); LOG_DBG("IDLE mask: %d", idle); if (!update_status(mod)) break; bar->refresh(bar); } } } if (m->conn != NULL) { mpd_connection_free(m->conn); m->conn = NULL; } return aborted ? 0 : ret; } struct refresh_context { struct module *mod; int abort_fd; long milli_seconds; }; static int refresh_in_thread(void *arg) { struct refresh_context *ctx = arg; struct module *mod = ctx->mod; /* Extract data from context so that we can free it */ int abort_fd = ctx->abort_fd; long milli_seconds = ctx->milli_seconds; free(ctx); LOG_DBG("going to sleep for %ldms", milli_seconds); /* Wait for timeout, or abort signal */ struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; int r = poll(fds, 1, milli_seconds); if (r < 0) { LOG_ERRNO("failed to poll() in refresh thread"); return 1; } /* Aborted? */ if (r == 1) { assert(fds[0].revents & POLLIN); LOG_DBG("refresh thread aborted"); return 0; } LOG_DBG("timed refresh"); mod->bar->refresh(mod->bar); return 0; } static bool refresh_in(struct module *mod, long milli_seconds) { struct private *m = mod->private; /* Abort currently running refresh thread */ if (m->refresh_thread_id != 0) { LOG_DBG("aborting current refresh thread"); /* Signal abort to thread */ assert(m->refresh_abort_fd != -1); if (write(m->refresh_abort_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) { LOG_ERRNO("failed to signal abort to refresher thread"); return false; } /* Wait for it to finish */ int res; thrd_join(m->refresh_thread_id, &res); /* Close and cleanup */ close(m->refresh_abort_fd); m->refresh_abort_fd = -1; m->refresh_thread_id = 0; } /* Create a new eventfd, to be able to signal abort to the thread */ int abort_fd = eventfd(0, EFD_CLOEXEC); if (abort_fd == -1) { LOG_ERRNO("failed to create eventfd"); return false; } /* Thread context */ struct refresh_context *ctx = malloc(sizeof(*ctx)); ctx->mod = mod; ctx->abort_fd = m->refresh_abort_fd = abort_fd; ctx->milli_seconds = milli_seconds; /* Create thread */ int r = thrd_create(&m->refresh_thread_id, &refresh_in_thread, ctx); if (r != thrd_success) { LOG_ERR("failed to create refresh thread"); close(m->refresh_abort_fd); m->refresh_abort_fd = -1; m->refresh_thread_id = 0; free(ctx); } /* Detach - we don't want to have to thrd_join() it */ // thrd_detach(tid); return r == 0; } static struct module * mpd_new(const char *host, uint16_t port, struct particle *label) { struct private *priv = calloc(1, sizeof(*priv)); priv->host = strdup(host); priv->port = port; priv->label = label; priv->state = MPD_STATE_UNKNOWN; priv->refresh_abort_fd = -1; struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->refresh_in = &refresh_in; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *host = yml_get_value(node, "host"); const struct yml_node *port = yml_get_value(node, "port"); const struct yml_node *c = yml_get_value(node, "content"); return mpd_new(yml_value_as_string(host), port != NULL ? yml_value_as_int(port) : 0, conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"host", true, &conf_verify_string}, {"port", false, &conf_verify_unsigned}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_mpd_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_mpd_iface"))); #endif yambar-1.11.0/modules/network.c000066400000000000000000001274731460770427600164050ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "network" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../module.h" #include "../particles/dynlist.h" #include "../plugin.h" #define max(x, y) ((x) > (y) ? (x) : (y)) static const long min_poll_interval = 250; struct rt_stats_msg { struct rtmsg rth; struct rtnl_link_stats64 stats; }; struct af_addr { int family; union { struct in_addr ipv4; struct in6_addr ipv6; } addr; }; struct iface { char *name; uint32_t get_stats_seq_nr; int index; uint8_t mac[6]; bool carrier; uint8_t state; /* IFLA_OPERSTATE */ /* IPv4 and IPv6 addresses */ tll(struct af_addr) addrs; /* WiFi extensions */ char *ssid; int signal_strength_dbm; uint32_t rx_bitrate; uint32_t tx_bitrate; double ul_speed; uint64_t ul_bits; double dl_speed; uint64_t dl_bits; }; struct private { struct particle *label; int poll_interval; int left_spacing; int right_spacing; bool get_addresses; int genl_sock; int rt_sock; int urandom_fd; struct { uint16_t family_id; uint32_t get_interface_seq_nr; uint32_t get_scan_seq_nr; } nl80211; tll(struct iface) ifaces; }; static void free_iface(struct iface iface) { tll_free(iface.addrs); free(iface.ssid); free(iface.name); } static void destroy(struct module *mod) { struct private *m = mod->private; assert(m->rt_sock == -1); m->label->destroy(m->label); if (m->urandom_fd >= 0) close(m->urandom_fd); tll_foreach(m->ifaces, it) free_iface(it->item); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "network"; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); struct exposable *exposables[max(tll_length(m->ifaces), 1)]; size_t idx = 0; tll_foreach(m->ifaces, it) { struct iface *iface = &it->item; const char *state = NULL; switch (iface->state) { case IF_OPER_UNKNOWN: state = "unknown"; break; case IF_OPER_NOTPRESENT: state = "not present"; break; case IF_OPER_DOWN: state = "down"; break; case IF_OPER_LOWERLAYERDOWN: state = "lower layers down"; break; case IF_OPER_TESTING: state = "testing"; break; case IF_OPER_DORMANT: state = "dormant"; break; case IF_OPER_UP: state = "up"; break; default: state = "unknown"; break; } char mac_str[6 * 2 + 5 + 1]; char ipv4_str[INET_ADDRSTRLEN] = {0}; char ipv6_str[INET6_ADDRSTRLEN] = {0}; snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", iface->mac[0], iface->mac[1], iface->mac[2], iface->mac[3], iface->mac[4], iface->mac[5]); /* TODO: this exposes the *last* added address of each kind. Can * we expose all in some way? */ tll_foreach(iface->addrs, it) { if (it->item.family == AF_INET) inet_ntop(AF_INET, &it->item.addr.ipv4, ipv4_str, sizeof(ipv4_str)); else if (it->item.family == AF_INET6) if (!IN6_IS_ADDR_LINKLOCAL(&it->item.addr.ipv6)) inet_ntop(AF_INET6, &it->item.addr.ipv6, ipv6_str, sizeof(ipv6_str)); } int quality = 0; if (iface->signal_strength_dbm != 0) { if (iface->signal_strength_dbm <= -100) quality = 0; else if (iface->signal_strength_dbm >= -50) quality = 100; else quality = 2 * (iface->signal_strength_dbm + 100); } struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", iface->name), tag_new_int(mod, "index", iface->index), tag_new_bool(mod, "carrier", iface->carrier), tag_new_string(mod, "state", state), tag_new_string(mod, "mac", mac_str), tag_new_string(mod, "ipv4", ipv4_str), tag_new_string(mod, "ipv6", ipv6_str), tag_new_string(mod, "ssid", iface->ssid), tag_new_int(mod, "signal", iface->signal_strength_dbm), tag_new_int_range(mod, "quality", quality, 0, 100), tag_new_int(mod, "rx-bitrate", iface->rx_bitrate), tag_new_int(mod, "tx-bitrate", iface->tx_bitrate), tag_new_float(mod, "dl-speed", iface->dl_speed), tag_new_float(mod, "ul-speed", iface->ul_speed), }, .count = 14, }; exposables[idx++] = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); } mtx_unlock(&mod->lock); return dynlist_exposable_new(exposables, idx, m->left_spacing, m->right_spacing); } /* Returns a value suitable for nl_pid/nlmsg_pid */ static uint32_t nl_pid_value(void) { return (pid_t)(uintptr_t)thrd_current() ^ getpid(); } /* Connect and bind to netlink socket. Returns socket fd, or -1 on error */ static int netlink_connect_rt(void) { int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); if (sock == -1) { LOG_ERRNO("failed to create netlink socket"); return -1; } const struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = nl_pid_value(), .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, }; if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { LOG_ERRNO("failed to bind netlink RT socket"); close(sock); return -1; } return sock; } static int netlink_connect_genl(void) { int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC); if (sock == -1) { LOG_ERRNO("failed to create netlink socket"); return -1; } const struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = nl_pid_value(), /* no multicast notifications by default, will be added later */ }; if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { LOG_ERRNO("failed to bind netlink socket"); close(sock); return -1; } return sock; } static bool send_nlmsg(int sock, const void *nlmsg, size_t len) { int r = sendto(sock, nlmsg, len, 0, (struct sockaddr *)&(struct sockaddr_nl){.nl_family = AF_NETLINK}, sizeof(struct sockaddr_nl)); return r == len; } static bool send_rt_request(struct private *m, int request) { struct { struct nlmsghdr hdr; struct rtgenmsg rt __attribute__((aligned(NLMSG_ALIGNTO))); } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)), .nlmsg_type = request, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = 1, .nlmsg_pid = nl_pid_value(), }, .rt = { .rtgen_family = AF_UNSPEC, }, }; if (!send_nlmsg(m->rt_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("failed to send netlink RT request (%d)", request); return false; } return true; } static bool send_rt_getstats_request(struct private *m, struct iface *iface) { if (iface->get_stats_seq_nr > 0) { LOG_DBG("%s: RT get-stats request already in progress", iface->name); return true; } LOG_DBG("%s: sending RT get-stats request", iface->name); uint32_t seq; if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) { LOG_ERRNO("failed to read from /dev/urandom"); return false; } struct { struct nlmsghdr hdr; struct if_stats_msg rt; } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)), .nlmsg_type = RTM_GETSTATS, .nlmsg_flags = NLM_F_REQUEST, .nlmsg_seq = seq, .nlmsg_pid = nl_pid_value(), }, .rt = { .ifindex = iface->index, .filter_mask = IFLA_STATS_LINK_64, .family = AF_UNSPEC, }, }; if (!send_nlmsg(m->rt_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("%s: failed to send netlink RT getstats request (%d)", iface->name, RTM_GETSTATS); return false; } iface->get_stats_seq_nr = seq; return true; } static bool send_ctrl_get_family_request(struct private *m) { const struct { struct nlmsghdr hdr; struct { struct genlmsghdr genl; struct { struct nlattr hdr; char data[8] __attribute__((aligned(NLA_ALIGNTO))); } family_name_attr __attribute__((aligned(NLA_ALIGNTO))); } msg __attribute__((aligned(NLMSG_ALIGNTO))); } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)), .nlmsg_type = GENL_ID_CTRL, .nlmsg_flags = NLM_F_REQUEST, .nlmsg_seq = 1, .nlmsg_pid = nl_pid_value(), }, .msg = { .genl = { .cmd = CTRL_CMD_GETFAMILY, .version = 1, }, .family_name_attr = { .hdr = { .nla_type = CTRL_ATTR_FAMILY_NAME, .nla_len = sizeof(req.msg.family_name_attr), }, .data = NL80211_GENL_NAME, }, }, }; _Static_assert(sizeof(req.msg.family_name_attr) == NLA_HDRLEN + NLA_ALIGN(sizeof(req.msg.family_name_attr.data)), ""); if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("failed to send netlink ctrl-get-family request"); return false; } return true; } static bool send_nl80211_request(struct private *m, uint8_t cmd, uint32_t seq) { if (m->nl80211.family_id == (uint16_t)-1) return false; const struct { struct nlmsghdr hdr; struct { struct genlmsghdr genl; } msg __attribute__((aligned(NLMSG_ALIGNTO))); } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)), .nlmsg_type = m->nl80211.family_id, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = seq, .nlmsg_pid = nl_pid_value(), }, .msg = { .genl = { .cmd = cmd, .version = 1, }, }, }; if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("failed to send netlink nl80211 get-inteface request"); return false; } return true; } static bool send_nl80211_get_interface(struct private *m) { if (m->nl80211.get_interface_seq_nr > 0) { LOG_DBG("nl80211 get-interface request already in progress"); return true; } uint32_t seq; if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) { LOG_ERRNO("failed to read from /dev/urandom"); return false; } LOG_DBG("sending nl80211 get-interface request %d", seq); if (!send_nl80211_request(m, NL80211_CMD_GET_INTERFACE, seq)) return false; m->nl80211.get_interface_seq_nr = seq; return true; } static bool send_nl80211_get_station(struct private *m, struct iface *iface) { LOG_DBG("sending nl80211 get-station request"); if (m->nl80211.family_id == (uint16_t)-1) return false; const struct { struct nlmsghdr hdr; struct { struct genlmsghdr genl; struct { struct nlattr attr; int index __attribute__((aligned(NLA_ALIGNTO))); } ifindex __attribute__((aligned(NLA_ALIGNTO))); } msg __attribute__((aligned(NLMSG_ALIGNTO))); } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)), .nlmsg_type = m->nl80211.family_id, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = 1, .nlmsg_pid = nl_pid_value(), }, .msg = { .genl = { .cmd = NL80211_CMD_GET_STATION, .version = 1, }, .ifindex = { .attr = { .nla_type = NL80211_ATTR_IFINDEX, .nla_len = sizeof(req.msg.ifindex), }, .index = iface->index, }, }, }; if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("failed to send netlink nl80211 get-inteface request"); return false; } return true; } static bool send_nl80211_get_scan(struct private *m) { if (m->nl80211.get_scan_seq_nr > 0) { LOG_ERR("nl80211 get-scan request already in progress"); return true; } uint32_t seq; if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) { LOG_ERRNO("failed to read from /dev/urandom"); return false; } LOG_DBG("sending nl80211 get-scan request %d", seq); if (!send_nl80211_request(m, NL80211_CMD_GET_SCAN, seq)) return false; m->nl80211.get_scan_seq_nr = seq; return true; } static void handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size_t len) { assert(type == RTM_NEWLINK || type == RTM_DELLINK); struct private *m = mod->private; if (type == RTM_DELLINK) { tll_foreach(m->ifaces, it) { if (msg->ifi_index != it->item.index) continue; mtx_lock(&mod->lock); tll_remove_and_free(m->ifaces, it, free_iface); mtx_unlock(&mod->lock); break; } mod->bar->refresh(mod->bar); return; } struct iface *iface = NULL; tll_foreach(m->ifaces, it) { if (msg->ifi_index != it->item.index) continue; iface = &it->item; break; } if (iface == NULL) { mtx_lock(&mod->lock); tll_push_back(m->ifaces, ((struct iface){ .index = msg->ifi_index, .state = IF_OPER_DOWN, .addrs = tll_init(), })); mtx_unlock(&mod->lock); iface = &tll_back(m->ifaces); } for (const struct rtattr *attr = IFLA_RTA(msg); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { switch (attr->rta_type) { case IFLA_IFNAME: mtx_lock(&mod->lock); iface->name = strdup((const char *)RTA_DATA(attr)); LOG_DBG("%s: index=%d", iface->name, iface->index); mtx_unlock(&mod->lock); case IFLA_OPERSTATE: { uint8_t operstate = *(const uint8_t *)RTA_DATA(attr); if (iface->state == operstate) break; LOG_DBG("%s: IFLA_OPERSTATE: %hhu -> %hhu", iface->name, iface->state, operstate); mtx_lock(&mod->lock); iface->state = operstate; mtx_unlock(&mod->lock); break; } case IFLA_CARRIER: { uint8_t carrier = *(const uint8_t *)RTA_DATA(attr); if (iface->carrier == carrier) break; LOG_DBG("%s: IFLA_CARRIER: %hhu -> %hhu", iface->name, iface->carrier, carrier); mtx_lock(&mod->lock); iface->carrier = carrier; mtx_unlock(&mod->lock); break; } case IFLA_ADDRESS: { if (RTA_PAYLOAD(attr) != 6) break; const uint8_t *mac = RTA_DATA(attr); if (memcmp(iface->mac, mac, sizeof(iface->mac)) == 0) break; LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x", iface->name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mtx_lock(&mod->lock); memcpy(iface->mac, mac, sizeof(iface->mac)); mtx_unlock(&mod->lock); break; } } } assert(iface->name != NULL); /* Reset address initialization */ m->get_addresses = true; send_nl80211_get_interface(m); mod->bar->refresh(mod->bar); } static void handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, size_t len) { assert(type == RTM_NEWADDR || type == RTM_DELADDR); struct private *m = mod->private; bool update_bar = false; struct iface *iface = NULL; tll_foreach(m->ifaces, it) { if (msg->ifa_index != it->item.index) continue; iface = &it->item; break; } if (iface == NULL) { LOG_ERR("failed to find network interface with index %d. Probaly a yambar bug", msg->ifa_index); return; } for (const struct rtattr *attr = IFA_RTA(msg); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { switch (attr->rta_type) { case IFA_ADDRESS: { const void *raw_addr = RTA_DATA(attr); size_t addr_len = RTA_PAYLOAD(attr); #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG char s[INET6_ADDRSTRLEN]; inet_ntop(msg->ifa_family, raw_addr, s, sizeof(s)); #endif LOG_DBG("%s: IFA_ADDRESS (%s): %s", iface->name, type == RTM_NEWADDR ? "add" : "del", s); mtx_lock(&mod->lock); if (type == RTM_DELADDR) { /* Find address in our list and remove it */ tll_foreach(iface->addrs, it) { if (it->item.family != msg->ifa_family) continue; if (memcmp(&it->item.addr, raw_addr, addr_len) != 0) continue; tll_remove(iface->addrs, it); update_bar = true; break; } } else { /* Append address to our list */ struct af_addr a = {.family = msg->ifa_family}; memcpy(&a.addr, raw_addr, addr_len); tll_push_back(iface->addrs, a); update_bar = true; } mtx_unlock(&mod->lock); break; } } } if (update_bar) mod->bar->refresh(mod->bar); } static bool foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len, bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *ctx), void *ctx) { const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN; const uint8_t *end = (const uint8_t *)genl + len; for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { uint16_t type = attr->nla_type & NLA_TYPE_MASK; bool nested = (attr->nla_type & NLA_F_NESTED) != 0; ; const void *payload = raw + NLA_HDRLEN; if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) return false; } return true; } static bool foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len, bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *ctx), void *ctx) { const uint8_t *raw = parent_payload; const uint8_t *end = parent_payload + len; for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end; raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) { uint16_t type = attr->nla_type & NLA_TYPE_MASK; bool nested = (attr->nla_type & NLA_F_NESTED) != 0; const void *payload = raw + NLA_HDRLEN; if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) return false; } return true; } struct mcast_group { uint32_t id; const char *name; }; static bool parse_mcast_group(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct mcast_group *ctx = _ctx; switch (type) { case CTRL_ATTR_MCAST_GRP_ID: { ctx->id = *(uint32_t *)payload; break; } case CTRL_ATTR_MCAST_GRP_NAME: { ctx->name = (const char *)payload; break; } default: LOG_WARN("unrecognized GENL MCAST GRP attribute: " "type=%hu, nested=%d, len=%zu", type, nested, len); break; } return true; } static bool parse_mcast_groups(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct private *m = mod->private; struct mcast_group group = {0}; foreach_nlattr_nested(mod, NULL, payload, len, &parse_mcast_group, &group); LOG_DBG("MCAST: %s -> %u", group.name, group.id); if (strcmp(group.name, NL80211_MULTICAST_GROUP_MLME) == 0) { /* * Join the nl80211 MLME multicast group - for * CONNECT/DISCONNECT events. */ int r = setsockopt(m->genl_sock, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group.id, sizeof(int)); if (r < 0) LOG_ERRNO("failed to joint the nl80211 MLME mcast group"); } return true; } static bool handle_genl_ctrl(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct private *m = mod->private; switch (type) { case CTRL_ATTR_FAMILY_ID: { m->nl80211.family_id = *(const uint16_t *)payload; send_nl80211_get_interface(m); break; } case CTRL_ATTR_FAMILY_NAME: // LOG_INFO("NAME: %.*s (%zu bytes)", (int)len, (const char *)payload, len); break; case CTRL_ATTR_MCAST_GROUPS: foreach_nlattr_nested(mod, NULL, payload, len, &parse_mcast_groups, NULL); break; default: LOG_DBG("unrecognized GENL CTRL attribute: " "type=%hu, nested=%d, len=%zu", type, nested, len); break; } return true; } static bool find_nl80211_iface(struct module *mod, struct iface *_iface, uint16_t type, bool nested, const void *payload, size_t len, void *ctx) { struct private *m = mod->private; struct iface **iface = ctx; switch (type) { case NL80211_ATTR_IFINDEX: if (*iface != NULL) if (*(uint32_t *)payload == (*iface)->index) return false; tll_foreach(m->ifaces, it) { if (*(uint32_t *)payload != it->item.index) continue; *iface = &it->item; return false; } LOG_ERR("could not find interface with index %d", *(uint32_t *)payload); break; } return true; } static bool handle_nl80211_new_interface(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { switch (type) { case NL80211_ATTR_IFINDEX: assert(*(uint32_t *)payload == iface->index); break; case NL80211_ATTR_SSID: { const char *ssid = payload; if (iface->ssid == NULL || strncmp(iface->ssid, ssid, len) != 0) LOG_INFO("%s: SSID: %.*s", iface->name, (int)len, ssid); mtx_lock(&mod->lock); free(iface->ssid); iface->ssid = strndup(ssid, len); mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); break; } default: LOG_DBG("%s: unrecognized nl80211 attribute: " "type=%hu, nested=%d, len=%zu", iface->name, type, nested, len); break; } return true; } struct rate_info_ctx { unsigned bitrate; }; static bool handle_nl80211_rate_info(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct rate_info_ctx *ctx = _ctx; switch (type) { case NL80211_RATE_INFO_BITRATE32: { uint32_t bitrate_100kbit = *(uint32_t *)payload; ctx->bitrate = bitrate_100kbit * 100 * 1000; break; } case NL80211_RATE_INFO_BITRATE: if (ctx->bitrate == 0) { uint16_t bitrate_100kbit = *(uint16_t *)payload; ctx->bitrate = bitrate_100kbit * 100 * 1000; } else { /* Prefer the BITRATE32 attribute */ } break; default: LOG_DBG("%s: unrecognized nl80211 rate info attribute: " "type=%hu, nested=%d, len=%zu", iface->name, type, nested, len); break; } return true; } struct station_info_ctx { bool update_bar; }; static bool handle_nl80211_station_info(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct station_info_ctx *ctx = _ctx; switch (type) { case NL80211_STA_INFO_SIGNAL: LOG_DBG("signal strength (last): %hhd dBm", *(uint8_t *)payload); break; case NL80211_STA_INFO_SIGNAL_AVG: LOG_DBG("signal strength (average): %hhd dBm", *(uint8_t *)payload); mtx_lock(&mod->lock); iface->signal_strength_dbm = *(int8_t *)payload; mtx_unlock(&mod->lock); ctx->update_bar = true; break; case NL80211_STA_INFO_TX_BITRATE: { struct rate_info_ctx rctx = {0}; foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_rate_info, &rctx); LOG_DBG("TX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.); mtx_lock(&mod->lock); iface->tx_bitrate = rctx.bitrate; mtx_unlock(&mod->lock); ctx->update_bar = true; break; } case NL80211_STA_INFO_RX_BITRATE: { struct rate_info_ctx rctx = {0}; foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_rate_info, &rctx); LOG_DBG("RX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.); mtx_lock(&mod->lock); iface->rx_bitrate = rctx.bitrate; mtx_unlock(&mod->lock); ctx->update_bar = true; break; } default: LOG_DBG("%s: unrecognized nl80211 station info attribute: " "type=%hu, nested=%d, len=%zu", iface->name, type, nested, len); break; } return true; } static bool handle_nl80211_new_station(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { switch (type) { case NL80211_ATTR_IFINDEX: break; case NL80211_ATTR_STA_INFO: { struct station_info_ctx ctx = {0}; foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_station_info, &ctx); if (ctx.update_bar) mod->bar->refresh(mod->bar); break; } default: LOG_DBG("%s: unrecognized nl80211 attribute: " "type=%hu, nested=%d, len=%zu", iface->name, type, nested, len); break; } return true; } static bool handle_ies(struct module *mod, struct iface *iface, const void *_ies, size_t len) { const uint8_t *ies = _ies; while (len >= 2 && len - 2 >= ies[1]) { switch (ies[0]) { case 0: { /* SSID */ const char *ssid = (const char *)&ies[2]; const size_t ssid_len = ies[1]; if (iface->ssid == NULL || strncmp(iface->ssid, ssid, ssid_len) != 0) LOG_INFO("%s: SSID: %.*s", iface->name, (int)ssid_len, ssid); mtx_lock(&mod->lock); free(iface->ssid); iface->ssid = strndup(ssid, ssid_len); mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); break; } } len -= ies[1] + 2; ies += ies[1] + 2; } return true; } struct scan_results_context { bool associated; const void *ies; size_t ies_size; }; static bool handle_nl80211_bss(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct scan_results_context *ctx = _ctx; switch (type) { case NL80211_BSS_STATUS: { const uint32_t status = *(uint32_t *)payload; if (status == NL80211_BSS_STATUS_ASSOCIATED) { ctx->associated = true; if (ctx->ies != NULL) { /* Deferred handling of BSS_INFORMATION_ELEMENTS */ return handle_ies(mod, iface, ctx->ies, ctx->ies_size); } } break; } case NL80211_BSS_INFORMATION_ELEMENTS: if (ctx->associated) return handle_ies(mod, iface, payload, len); else { /* * We’re either not associated, or, we haven’t seen the * BSS_STATUS attribute yet. * * Save a pointer to the IES payload, so that we can * process it later, if we see a * BSS_STATUS == BSS_STATUS_ASSOCIATED. */ ctx->ies = payload; ctx->ies_size = len; } } return true; } static bool handle_nl80211_scan_results(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len, void *_ctx) { struct scan_results_context ctx = {0}; switch (type) { case NL80211_ATTR_BSS: foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_bss, &ctx); break; } return true; } /* * Reads at least one (possibly more) message. * * On success, 'reply' will point to a malloc:ed buffer, to be freed * by the caller. 'len' is set to the size of the message (note that * the allocated size may actually be larger). * * Returns true on success, otherwise false */ static bool netlink_receive_messages(int sock, void **reply, size_t *len) { /* Use MSG_PEEK to find out how large buffer we need */ const size_t chunk_sz = 1024; size_t sz = chunk_sz; *reply = malloc(sz); while (true) { ssize_t bytes = recvfrom(sock, *reply, sz, MSG_PEEK, NULL, NULL); if (bytes == -1) { LOG_ERRNO("failed to receive from netlink socket"); free(*reply); return false; } if (bytes < sz) break; sz += chunk_sz; *reply = realloc(*reply, sz); } *len = recvfrom(sock, *reply, sz, 0, NULL, NULL); assert(*len >= 0); assert(*len < sz); return true; } static void handle_stats(struct module *mod, uint32_t seq, struct rt_stats_msg *msg) { struct private *m = mod->private; struct iface *iface = NULL; tll_foreach(m->ifaces, it) { if (seq != it->item.get_stats_seq_nr) continue; iface = &it->item; /* Current request is now considered complete */ iface->get_stats_seq_nr = 0; break; } if (iface == NULL) { LOG_ERR("Couldn't find iface"); return; } uint64_t ul_bits = msg->stats.tx_bytes * 8; uint64_t dl_bits = msg->stats.rx_bytes * 8; const double poll_interval_secs = (double)m->poll_interval / 1000.; if (iface->ul_bits != 0) iface->ul_speed = (double)(ul_bits - iface->ul_bits) / poll_interval_secs; if (iface->dl_bits != 0) iface->dl_speed = (double)(dl_bits - iface->dl_bits) / poll_interval_secs; iface->ul_bits = ul_bits; iface->dl_bits = dl_bits; } static bool parse_rt_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) { struct private *m = mod->private; /* Process response */ for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { switch (hdr->nlmsg_type) { case NLMSG_DONE: /* Request initial list of IPv4/6 addresses */ if (m->get_addresses) { m->get_addresses = false; send_rt_request(m, RTM_GETADDR); } break; case RTM_NEWLINK: case RTM_DELLINK: { const struct ifinfomsg *msg = NLMSG_DATA(hdr); size_t msg_len = IFLA_PAYLOAD(hdr); handle_link(mod, hdr->nlmsg_type, msg, msg_len); break; } case RTM_NEWADDR: case RTM_DELADDR: { const struct ifaddrmsg *msg = NLMSG_DATA(hdr); size_t msg_len = IFA_PAYLOAD(hdr); handle_address(mod, hdr->nlmsg_type, msg, msg_len); break; } case RTM_NEWSTATS: { struct rt_stats_msg *msg = NLMSG_DATA(hdr); handle_stats(mod, hdr->nlmsg_seq, msg); break; } case NLMSG_ERROR: { const struct nlmsgerr *err = NLMSG_DATA(hdr); LOG_ERRNO_P(-err->error, "netlink RT reply"); return false; } default: LOG_WARN("unrecognized netlink message type: 0x%x", hdr->nlmsg_type); return false; } } return true; } static bool parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) { struct private *m = mod->private; struct iface *iface = NULL; for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { if (hdr->nlmsg_type == GENL_ID_CTRL) { assert(hdr->nlmsg_seq == 1); const struct genlmsghdr *genl = NLMSG_DATA(hdr); const size_t msg_size = NLMSG_PAYLOAD(hdr, 0); foreach_nlattr(mod, NULL, genl, msg_size, &handle_genl_ctrl, NULL); continue; } if (hdr->nlmsg_seq == m->nl80211.get_interface_seq_nr) { /* Current request is now considered complete */ m->nl80211.get_interface_seq_nr = 0; /* Can’t issue both get-station and get-scan at the * same time. So, always run a get-scan when a * get-station is complete */ send_nl80211_get_scan(m); } if (hdr->nlmsg_type == NLMSG_DONE) { if (hdr->nlmsg_seq == m->nl80211.get_scan_seq_nr) { /* Current request is now considered complete */ m->nl80211.get_scan_seq_nr = 0; tll_foreach(m->ifaces, it) send_nl80211_get_station(m, &it->item); } } else if (hdr->nlmsg_type == m->nl80211.family_id) { const struct genlmsghdr *genl = NLMSG_DATA(hdr); const size_t msg_size = NLMSG_PAYLOAD(hdr, 0); switch (genl->cmd) { case NL80211_CMD_NEW_INTERFACE: if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface)) continue; LOG_DBG("%s: got interface information", iface->name); foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_interface, NULL); break; case NL80211_CMD_CONNECT: /* * Update SSID * * Unfortunately, the SSID doesn’t appear to be * included in *any* of the notifications sent when * associating, authenticating and connecting to a * station. * * Thus, we need to explicitly request an update. */ LOG_DBG("connected, requesting interface information"); send_nl80211_get_interface(m); break; case NL80211_CMD_DISCONNECT: if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface)) continue; LOG_DBG("%s: disconnected, resetting SSID etc", iface->name); mtx_lock(&mod->lock); free(iface->ssid); iface->ssid = NULL; iface->signal_strength_dbm = 0; iface->rx_bitrate = iface->tx_bitrate = 0; mtx_unlock(&mod->lock); break; case NL80211_CMD_NEW_STATION: if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface)) continue; LOG_DBG("%s: got station information", iface->name); foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_station, NULL); LOG_DBG("%s: signal: %d dBm, RX=%u Mbit/s, TX=%u Mbit/s", iface->name, iface->signal_strength_dbm, iface->rx_bitrate / 1000 / 1000, iface->tx_bitrate / 1000 / 1000); break; case NL80211_CMD_NEW_SCAN_RESULTS: if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface)) continue; LOG_DBG("%s: got scan results", iface->name); foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_scan_results, NULL); break; default: LOG_DBG("unrecognized nl80211 command: %hhu", genl->cmd); break; } } else if (hdr->nlmsg_type == NLMSG_ERROR) { const struct nlmsgerr *err = NLMSG_DATA(hdr); int nl_errno = -err->error; if (nl_errno == ENODEV) ; /* iface is not an nl80211 device */ else if (nl_errno == ENOENT) ; /* iface down? */ else { LOG_ERRNO_P(nl_errno, "nl80211 reply (seq-nr: %u)", hdr->nlmsg_seq); } } else { LOG_WARN("unrecognized netlink message type: 0x%x", hdr->nlmsg_type); return false; } } return true; } static int run(struct module *mod) { int ret = 1; struct private *m = mod->private; int timer_fd = -1; if (m->poll_interval > 0) { timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (timer_fd < 0) { LOG_ERRNO("failed to create poll timer FD"); goto out; } const long secs = m->poll_interval / 1000; const long msecs = m->poll_interval % 1000; struct itimerspec poll_time = { .it_value = {.tv_sec = secs, .tv_nsec = msecs * 1000000}, .it_interval = {.tv_sec = secs, .tv_nsec = msecs * 1000000}, }; if (timerfd_settime(timer_fd, 0, &poll_time, NULL) < 0) { LOG_ERRNO("failed to arm poll timer"); goto out; } } m->rt_sock = netlink_connect_rt(); m->genl_sock = netlink_connect_genl(); if (m->rt_sock < 0 || m->genl_sock < 0) goto out; if (!send_rt_request(m, RTM_GETLINK) || !send_ctrl_get_family_request(m)) { goto out; } /* Main loop */ while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = m->rt_sock, .events = POLLIN}, {.fd = m->genl_sock, .events = POLLIN}, {.fd = timer_fd, .events = POLLIN}, }; poll(fds, 3 + (timer_fd >= 0 ? 1 : 0), -1); if (fds[0].revents & (POLLIN | POLLHUP)) break; if ((fds[1].revents & POLLHUP) || (fds[2].revents & POLLHUP)) { LOG_ERR("disconnected from netlink socket"); break; } if (fds[3].revents & POLLHUP) { LOG_ERR("disconnected from timer FD"); break; } if (fds[1].revents & POLLIN) { /* Read one (or more) messages */ void *reply; size_t len; if (!netlink_receive_messages(m->rt_sock, &reply, &len)) break; /* Parse (and act upon) the received message(s) */ if (!parse_rt_reply(mod, (const struct nlmsghdr *)reply, len)) { free(reply); break; } free(reply); } if (fds[2].revents & POLLIN) { /* Read one (or more) messages */ void *reply; size_t len; if (!netlink_receive_messages(m->genl_sock, &reply, &len)) break; if (!parse_genl_reply(mod, (const struct nlmsghdr *)reply, len)) { free(reply); break; } free(reply); } if (fds[3].revents & POLLIN) { uint64_t count; ssize_t amount = read(timer_fd, &count, sizeof(count)); if (amount < 0) { LOG_ERRNO("failed to read from timer FD"); break; } tll_foreach(m->ifaces, it) { send_nl80211_get_station(m, &it->item); send_rt_getstats_request(m, &it->item); }; } } ret = 0; out: if (m->rt_sock >= 0) close(m->rt_sock); if (m->genl_sock >= 0) close(m->genl_sock); if (timer_fd >= 0) close(timer_fd); m->rt_sock = m->genl_sock = -1; return ret; } static struct module * network_new(struct particle *label, int poll_interval, int left_spacing, int right_spacing) { int urandom_fd = open("/dev/urandom", O_RDONLY); if (urandom_fd < 0) { LOG_ERRNO("failed to open /dev/urandom"); return NULL; } struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->poll_interval = poll_interval; priv->left_spacing = left_spacing; priv->right_spacing = right_spacing; priv->genl_sock = -1; priv->rt_sock = -1; priv->urandom_fd = urandom_fd; priv->get_addresses = false; priv->nl80211.family_id = -1; struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *content = yml_get_value(node, "content"); const struct yml_node *poll = yml_get_value(node, "poll-interval"); const struct yml_node *spacing = yml_get_value(node, "spacing"); const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int right = spacing != NULL ? yml_value_as_int(spacing) : right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; return network_new(conf_to_particle(content, inherited), poll != NULL ? yml_value_as_int(poll) : 0, left, right); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; int interval = yml_value_as_int(node); if (interval > 0 && interval < min_poll_interval) { LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, {"poll-interval", false, &conf_verify_poll_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_network_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_network_iface"))); #endif yambar-1.11.0/modules/pipewire.c000066400000000000000000000743551460770427600165400ustar00rootroot00000000000000#include "spa/utils/list.h" #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "pipewire" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../module.h" #include "../particle.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "../yml.h" #define ARRAY_LENGTH(x) (sizeof((x)) / sizeof((x)[0])) /* clang-format off */ #define X_FREE_SET(t, v) do { free((t)); (t) = (v); } while (0) /* clang-format on */ #define X_STRDUP(s) ((s) != NULL ? strdup((s)) : NULL) struct output_informations { /* internal */ uint32_t device_id; uint32_t card_profile_device_id; /* informations */ bool muted; uint16_t linear_volume; /* classic volume */ uint16_t cubic_volume; /* volume a la pulseaudio */ char *name; char *description; char *form_factor; /* headset, headphone, speaker, ..., can be null */ char *bus; /* alsa, bluetooth, etc */ char *icon; }; static struct output_informations const output_informations_null; struct data; struct private { struct particle *label; struct data *data; /* pipewire related */ struct output_informations sink_informations; struct output_informations source_informations; }; /* This struct is needed because when param event occur, the function * `node_events_param` will receive the corresponding event about the node * but there's no simple way of knowing from which node the event come from */ struct node_data { struct data *data; /* otherwise is_source */ bool is_sink; }; /* struct data */ struct node; struct data { /* yambar module */ struct module *module; char *target_sink; char *target_source; struct node *binded_sink; struct node *binded_source; struct node_data node_data_sink; struct node_data node_data_source; /* proxies */ void *metadata; void *node_sink; void *node_source; /* main struct */ struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; /* listener */ struct spa_hook registry_listener; struct spa_hook core_listener; struct spa_hook metadata_listener; struct spa_hook node_sink_listener; struct spa_hook node_source_listener; /* list */ struct spa_list node_list; struct spa_list device_list; int sync; }; /* struct Route */ struct route { struct device *device; struct spa_list link; enum spa_direction direction; /* direction */ int profile_device_id; /* device */ char *form_factor; /* info.type */ char *icon_name; /* info.icon-name */ }; static void route_free(struct route *route) { free(route->form_factor); free(route->icon_name); spa_list_remove(&route->link); free(route); } /* struct Device */ struct device { struct data *data; struct spa_list link; uint32_t id; struct spa_list routes; void *proxy; struct spa_hook listener; }; static void device_free(struct device *device, struct data *data) { struct route *route = NULL; spa_list_consume(route, &device->routes, link) route_free(route); spa_hook_remove(&device->listener); pw_proxy_destroy((struct pw_proxy *)device->proxy); spa_list_remove(&device->link); free(device); } static struct route * route_find_or_create(struct device *device, uint32_t profile_device_id) { struct route *route = NULL; spa_list_for_each(route, &device->routes, link) { if (route->profile_device_id == profile_device_id) return route; } /* route not found, let's create it */ route = calloc(1, sizeof(struct route)); assert(route != NULL); route->device = device; route->profile_device_id = profile_device_id; spa_list_append(&device->routes, &route->link); return route; } struct node { struct spa_list link; uint32_t id; char *name; }; /* struct node */ static struct route * node_find_route(struct data *data, bool is_sink) { struct private *private = data->module->private; struct output_informations *output_informations = NULL; if (is_sink) { if (data->node_sink == NULL) return NULL; output_informations = &private->sink_informations; } else { if (data->node_source == NULL) return NULL; output_informations = &private->source_informations; } struct device *device = NULL; spa_list_for_each(device, &data->device_list, link) { if (device->id != output_informations->device_id) continue; struct route *route = NULL; spa_list_for_each(route, &device->routes, link) { if (route->profile_device_id == output_informations->card_profile_device_id) return route; } } return NULL; } static void node_unhook_binded_node(struct data *data, bool is_sink) { struct node **target_node = NULL; struct spa_hook *target_listener = NULL; void **target_proxy = NULL; if (is_sink) { target_node = &data->binded_sink; target_listener = &data->node_sink_listener; target_proxy = &data->node_sink; } else { target_node = &data->binded_source; target_listener = &data->node_source_listener; target_proxy = &data->node_source; } if (*target_node == NULL) return; spa_hook_remove(target_listener); pw_proxy_destroy(*target_proxy); *target_node = NULL; *target_proxy = NULL; } static void node_free(struct node *node, struct data *data) { if (data->binded_sink == node) node_unhook_binded_node(data, true); else if (data->binded_source == node) node_unhook_binded_node(data, false); spa_list_remove(&node->link); free(node->name); free(node); } /* Device events */ static void device_events_info(void *userdata, const struct pw_device_info *info) { struct device *device = userdata; /* We only want the "Route" param, which is in Params */ if (!(info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS)) return; for (size_t i = 0; i < info->n_params; ++i) { if (info->params[i].id == SPA_PARAM_Route) { pw_device_enum_params(device->proxy, 0, info->params[i].id, 0, -1, NULL); break; } } } static void device_events_param(void *userdata, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { /* We should only receive ParamRoute */ assert(spa_pod_is_object_type(param, SPA_TYPE_OBJECT_ParamRoute)); struct route data = {0}; struct spa_pod_prop const *prop = NULL; /* device must be present otherwise I can't do anything with the data */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_device); if (prop == NULL) return; spa_pod_get_int(&prop->value, &data.profile_device_id); /* same for direction, required too */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_direction); if (prop == NULL) return; char const *direction = NULL; spa_pod_get_string(&prop->value, &direction); if (spa_streq(direction, "Output")) data.direction = SPA_DIRECTION_OUTPUT; else data.direction = SPA_DIRECTION_INPUT; /* same for info, it's required */ prop = spa_pod_find_prop(param, NULL, SPA_PARAM_ROUTE_info); if (prop == NULL) return; struct spa_pod *iter = NULL; char const *header = NULL; SPA_POD_STRUCT_FOREACH(&prop->value, iter) { /* no previous header */ if (header == NULL) { /* headers are always string */ if (spa_pod_is_string(iter)) spa_pod_get_string(iter, &header); /* otherwise it's the first iteration (number of elements in the struct) */ continue; } /* Values needed: * - (string) device.icon_name [icon_name] * - (string) port.type [form_factor] */ if (spa_pod_is_string(iter)) { if (spa_streq(header, "device.icon_name")) spa_pod_get_string(iter, (char const **)&data.icon_name); else if (spa_streq(header, "port.type")) { spa_pod_get_string(iter, (char const **)&data.form_factor); } } header = NULL; } struct device *device = userdata; struct route *route = route_find_or_create(device, data.profile_device_id); X_FREE_SET(route->form_factor, X_STRDUP(data.form_factor)); X_FREE_SET(route->icon_name, X_STRDUP(data.icon_name)); route->direction = data.direction; /* set missing informations if possible */ struct private *private = device->data->module->private; struct node *binded_node = NULL; struct output_informations *output_informations = NULL; if (route->direction == SPA_DIRECTION_INPUT) { binded_node = private->data->binded_source; output_informations = &private->source_informations; } else { binded_node = private->data->binded_sink; output_informations = &private->sink_informations; } /* Node not binded */ if (binded_node == NULL) return; /* Node's device is the the same as route's device */ if (output_informations->device_id != route->device->id) return; /* Route is not the Node's device route */ if (output_informations->card_profile_device_id != route->profile_device_id) return; /* Update missing informations */ X_FREE_SET(output_informations->form_factor, X_STRDUP(route->form_factor)); X_FREE_SET(output_informations->icon, X_STRDUP(route->icon_name)); device->data->module->bar->refresh(device->data->module->bar); } static struct pw_device_events const device_events = { PW_VERSION_DEVICE_EVENTS, .info = device_events_info, .param = device_events_param, }; /* Node events */ static void node_events_info(void *userdata, struct pw_node_info const *info) { struct node_data *node_data = userdata; struct data *data = node_data->data; struct private *private = data->module->private; if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { /* We only need the Props param, so let's try to find it */ for (size_t i = 0; i < info->n_params; ++i) { if (info->params[i].id == SPA_PARAM_Props) { void *target_node = (node_data->is_sink ? data->node_sink : data->node_source); /* Found it, will emit a param event, the parem will then be handled * in node_events_param */ pw_node_enum_params(target_node, 0, info->params[i].id, 0, -1, NULL); break; } } } if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { struct output_informations *output_informations = (node_data->is_sink ? &private->sink_informations : &private->source_informations); struct spa_dict_item const *item = NULL; item = spa_dict_lookup_item(info->props, "node.name"); if (item != NULL) X_FREE_SET(output_informations->name, X_STRDUP(item->value)); item = spa_dict_lookup_item(info->props, "node.description"); if (item != NULL) X_FREE_SET(output_informations->description, X_STRDUP(item->value)); item = spa_dict_lookup_item(info->props, "device.id"); if (item != NULL) { uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->device_id = value; } item = spa_dict_lookup_item(info->props, "card.profile.device"); if (item != NULL) { uint32_t value = 0; spa_atou32(item->value, &value, 10); output_informations->card_profile_device_id = value; } /* Device's informations has an more important priority than node's informations */ /* icon_name */ struct route *route = node_find_route(data, node_data->is_sink); if (route != NULL && route->icon_name != NULL) output_informations->icon = X_STRDUP(route->icon_name); else { item = spa_dict_lookup_item(info->props, "device.icon-name"); if (item != NULL) X_FREE_SET(output_informations->icon, X_STRDUP(item->value)); } /* form_factor */ if (route != NULL && route->form_factor != NULL) output_informations->form_factor = X_STRDUP(route->form_factor); else { item = spa_dict_lookup_item(info->props, "device.form-factor"); if (item != NULL) X_FREE_SET(output_informations->form_factor, X_STRDUP(item->value)); } item = spa_dict_lookup_item(info->props, "device.bus"); if (item != NULL) X_FREE_SET(output_informations->bus, X_STRDUP(item->value)); data->module->bar->refresh(data->module->bar); } } static void node_events_param(void *userdata, __attribute__((unused)) int seq, __attribute__((unused)) uint32_t id, __attribute__((unused)) uint32_t index, __attribute__((unused)) uint32_t next, const struct spa_pod *param) { struct node_data *node_data = userdata; struct data *data = node_data->data; struct private *private = data->module->private; struct output_informations *output_informations = (node_data->is_sink ? &private->sink_informations : &private->source_informations); struct spa_pod_prop const *prop = NULL; prop = spa_pod_find_prop(param, NULL, SPA_PROP_mute); if (prop != NULL) { bool value = false; spa_pod_get_bool(&prop->value, &value); output_informations->muted = value; } prop = spa_pod_find_prop(param, NULL, SPA_PROP_channelVolumes); if (prop != NULL) { uint32_t n_values = 0; float *values = spa_pod_get_array(&prop->value, &n_values); float total = 0.0f; /* Array can be empty some times, assume that values have not changed */ if (n_values != 0) { for (uint32_t i = 0; i < n_values; ++i) total += values[i]; float base_volume = total / n_values; output_informations->linear_volume = roundf(base_volume * 100); output_informations->cubic_volume = roundf(cbrtf(base_volume) * 100); } } data->module->bar->refresh(data->module->bar); } static struct pw_node_events const node_events = { PW_VERSION_NODE_EVENTS, .info = node_events_info, .param = node_events_param, }; /* Metadata events */ static int metadata_property(void *userdata, __attribute__((unused)) uint32_t id, char const *key, __attribute__((unused)) char const *type, char const *value) { struct data *data = userdata; bool is_sink = false; // true for source mode char **target_name = NULL; /* We only want default.audio.sink or default.audio.source */ if (spa_streq(key, "default.audio.sink")) { is_sink = true; target_name = &data->target_sink; } else if (spa_streq(key, "default.audio.source")) { is_sink = false; /* just to be explicit */ target_name = &data->target_source; } else return 0; /* Value is NULL when the profile is set to `off`. */ if (value == NULL) { node_unhook_binded_node(data, is_sink); free(*target_name); *target_name = NULL; data->module->bar->refresh(data->module->bar); return 0; } struct json_object *json = json_tokener_parse(value); struct json_object_iterator json_it = json_object_iter_begin(json); struct json_object_iterator json_it_end = json_object_iter_end(json); while (!json_object_iter_equal(&json_it, &json_it_end)) { char const *key = json_object_iter_peek_name(&json_it); if (!spa_streq(key, "name")) { json_object_iter_next(&json_it); continue; } /* Found name */ struct json_object *value = json_object_iter_peek_value(&json_it); assert(json_object_is_type(value, json_type_string)); char const *name = json_object_get_string(value); /* `auto_null` is the same as `value == NULL` see lines above. */ if (spa_streq(name, "auto_null")) { node_unhook_binded_node(data, is_sink); free(*target_name); *target_name = NULL; data->module->bar->refresh(data->module->bar); break; } /* target_name is the same */ if (spa_streq(name, *target_name)) break; /* Unhook the binded_node */ node_unhook_binded_node(data, is_sink); /* Update the target */ free(*target_name); *target_name = strdup(name); /* Sync the core, core_events_done will then try to bind the good node */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); break; } json_object_put(json); return 0; } static struct pw_metadata_events const metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property, }; /* Registry events */ static void registry_event_global(void *userdata, uint32_t id, __attribute__((unused)) uint32_t permissions, char const *type, __attribute__((unused)) uint32_t version, struct spa_dict const *props) { struct data *data = userdata; /* New device */ if (spa_streq(type, PW_TYPE_INTERFACE_Device)) { struct device *device = calloc(1, sizeof(struct device)); assert(device != NULL); device->data = data; device->id = id; spa_list_init(&device->routes); device->proxy = pw_registry_bind(data->registry, id, type, PW_VERSION_DEVICE, 0); assert(device->proxy != NULL); pw_device_add_listener(device->proxy, &device->listener, &device_events, device); spa_list_append(&data->device_list, &device->link); } /* New node */ else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { /* Fill a new node */ struct node *node = calloc(1, sizeof(struct node)); assert(node != NULL); node->id = id; node->name = strdup(spa_dict_lookup(props, PW_KEY_NODE_NAME)); /* Store it */ spa_list_append(&data->node_list, &node->link); } /* New metadata */ else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { /* A metadata has already been bind */ if (data->metadata != NULL) return; /* Target only metadata which has "default" key */ char const *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); if (name == NULL || !spa_streq(name, "default")) return; /* Bind metadata */ data->metadata = pw_registry_bind(data->registry, id, type, PW_VERSION_METADATA, 0); assert(data->metadata != NULL); pw_metadata_add_listener(data->metadata, &data->metadata_listener, &metadata_events, data); } /* `core_events_done` will then try to bind the good node */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); } static void registry_event_global_remove(void *userdata, uint32_t id) { struct data *data = userdata; /* Try to find a node with the same `id` */ struct node *node = NULL, *node_temp = NULL; spa_list_for_each_safe(node, node_temp, &data->node_list, link) { if (node->id == id) { node_free(node, data); return; } } /* No node with this `id` maybe it's a device */ struct device *device = NULL, *device_temp = NULL; spa_list_for_each_safe(device, device_temp, &data->device_list, link) { if (device->id == id) { device_free(device, data); return; } } } static struct pw_registry_events const registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void try_to_bind_node(struct node_data *node_data, char const *target_name, struct node **target_node, void **target_proxy, struct spa_hook *target_listener) { /* profile deactived */ if (target_name == NULL) return; struct data *data = node_data->data; struct node *node = NULL; spa_list_for_each(node, &data->node_list, link) { if (!spa_streq(target_name, node->name)) continue; /* Found good node */ *target_node = node; *target_proxy = pw_registry_bind(data->registry, node->id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); assert(*target_proxy != NULL); pw_node_add_listener(*target_proxy, target_listener, &node_events, node_data); break; } } /* Core events */ static void core_events_done(void *userdata, uint32_t id, int seq) { struct data *data = userdata; if (id != PW_ID_CORE) return; /* Not our seq */ if (data->sync != seq) return; /* Sync ended, try to bind the node which has the targeted sink or the targeted source */ /* Node sink not binded and target_sink is set */ if (data->binded_sink == NULL && data->target_sink != NULL) try_to_bind_node(&data->node_data_sink, data->target_sink, &data->binded_sink, &data->node_sink, &data->node_sink_listener); /* Node source not binded and target_source is set */ if (data->binded_source == NULL && data->target_source != NULL) try_to_bind_node(&data->node_data_source, data->target_source, &data->binded_source, &data->node_source, &data->node_source_listener); } static void core_events_error(void *userdata, uint32_t id, int seq, int res, char const *message) { pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) { struct data *data = userdata; pw_main_loop_quit(data->loop); } } static struct pw_core_events const core_events = { PW_VERSION_CORE_EVENTS, .done = core_events_done, .error = core_events_error, }; /* init, deinit */ static struct data * pipewire_init(struct module *module) { pw_init(NULL, NULL); /* Data */ struct data *data = calloc(1, sizeof(struct data)); assert(data != NULL); spa_list_init(&data->node_list); spa_list_init(&data->device_list); /* Main loop */ data->loop = pw_main_loop_new(NULL); if (data->loop == NULL) { LOG_ERR("failed to instantiate main loop"); goto err; } /* Context */ data->context = pw_context_new(pw_main_loop_get_loop(data->loop), NULL, 0); if (data->context == NULL) { LOG_ERR("failed to instantiate pipewire context"); goto err; } /* Core */ data->core = pw_context_connect(data->context, NULL, 0); if (data->core == NULL) { LOG_ERR("failed to connect to pipewire"); goto err; } pw_core_add_listener(data->core, &data->core_listener, &core_events, data); /* Registry */ data->registry = pw_core_get_registry(data->core, PW_VERSION_REGISTRY, 0); if (data->registry == NULL) { LOG_ERR("failed to get core registry"); goto err; } pw_registry_add_listener(data->registry, &data->registry_listener, ®istry_events, data); /* Sync */ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); data->module = module; /* node_events_param_data */ data->node_data_sink.data = data; data->node_data_sink.is_sink = true; data->node_data_source.data = data; data->node_data_source.is_sink = false; return data; err: if (data->registry != NULL) pw_proxy_destroy((struct pw_proxy *)data->registry); if (data->core != NULL) pw_core_disconnect(data->core); if (data->context != NULL) pw_context_destroy(data->context); if (data->loop != NULL) pw_main_loop_destroy(data->loop); free(data); return NULL; } static void pipewire_deinit(struct data *data) { if (data == NULL) return; struct node *node = NULL; spa_list_consume(node, &data->node_list, link) node_free(node, data); struct device *device = NULL; spa_list_consume(device, &data->device_list, link) device_free(device, data); if (data->metadata) pw_proxy_destroy((struct pw_proxy *)data->metadata); spa_hook_remove(&data->registry_listener); pw_proxy_destroy((struct pw_proxy *)data->registry); spa_hook_remove(&data->core_listener); spa_hook_remove(&data->metadata_listener); pw_core_disconnect(data->core); pw_context_destroy(data->context); pw_main_loop_destroy(data->loop); free(data->target_sink); free(data->target_source); pw_deinit(); } static void destroy(struct module *module) { struct private *private = module->private; pipewire_deinit(private->data); private->label->destroy(private->label); /* sink */ free(private->sink_informations.name); free(private->sink_informations.description); free(private->sink_informations.icon); free(private->sink_informations.form_factor); free(private->sink_informations.bus); /* source */ free(private->source_informations.name); free(private->source_informations.description); free(private->source_informations.icon); free(private->source_informations.form_factor); free(private->source_informations.bus); free(private); module_default_destroy(module); } static char const * description(const struct module *module) { return "pipewire"; } static struct exposable * content(struct module *module) { struct private *private = module->private; if (private->data == NULL) return dynlist_exposable_new(NULL, 0, 0, 0); mtx_lock(&module->lock); struct exposable *exposables[2]; size_t exposables_length = ARRAY_LENGTH(exposables); struct output_informations const *output_informations = NULL; /* sink */ output_informations = (private->data->target_sink == NULL ? &output_informations_null : &private->sink_informations); struct tag_set sink_tag_set = (struct tag_set){ .tags = (struct tag *[]){ tag_new_string(module, "type", "sink"), tag_new_string(module, "name", output_informations->name), tag_new_string(module, "description", output_informations->description), tag_new_string(module, "icon", output_informations->icon), tag_new_string(module, "form_factor", output_informations->form_factor), tag_new_string(module, "bus", output_informations->bus), tag_new_bool(module, "muted", output_informations->muted), tag_new_int_range(module, "linear_volume", output_informations->linear_volume, 0, 100), tag_new_int_range(module, "cubic_volume", output_informations->cubic_volume, 0, 100), }, .count = 9, }; /* source */ output_informations = (private->data->target_source == NULL ? &output_informations_null : &private->source_informations); struct tag_set source_tag_set = (struct tag_set){ .tags = (struct tag *[]){ tag_new_string(module, "type", "source"), tag_new_string(module, "name", output_informations->name), tag_new_string(module, "description", output_informations->description), tag_new_string(module, "icon", output_informations->icon), tag_new_string(module, "form_factor", output_informations->form_factor), tag_new_string(module, "bus", output_informations->bus), tag_new_bool(module, "muted", output_informations->muted), tag_new_int_range(module, "linear_volume", output_informations->linear_volume, 0, 100), tag_new_int_range(module, "cubic_volume", output_informations->cubic_volume, 0, 100), }, .count = 9, }; exposables[0] = private->label->instantiate(private->label, &sink_tag_set); exposables[1] = private->label->instantiate(private->label, &source_tag_set); tag_set_destroy(&sink_tag_set); tag_set_destroy(&source_tag_set); mtx_unlock(&module->lock); return dynlist_exposable_new(exposables, exposables_length, 0, 0); } static int run(struct module *module) { struct private *private = module->private; if (private->data == NULL) return 1; struct pw_loop *pw_loop = pw_main_loop_get_loop(private->data->loop); struct pollfd pollfds[] = { /* abort_fd */ (struct pollfd){.fd = module->abort_fd, .events = POLLIN}, /* pipewire */ (struct pollfd){.fd = pw_loop_get_fd(pw_loop), .events = POLLIN}, }; while (true) { if (poll(pollfds, ARRAY_LENGTH(pollfds), -1) == -1) { if (errno == EINTR) continue; LOG_ERRNO("Unable to poll: %s", strerror(errno)); break; } /* abort_fd */ if (pollfds[0].revents & POLLIN) break; /* pipewire */ if (!(pollfds[1].revents & POLLIN)) /* issue happened */ break; int result = pw_loop_iterate(pw_loop, 0); if (result < 0) { LOG_ERRNO("Unable to iterate pipewire loop: %s", spa_strerror(result)); break; } } return 0; } static struct module * pipewire_new(struct particle *label) { struct private *private = calloc(1, sizeof(struct private)); assert(private != NULL); private->label = label; struct module *module = module_common_new(); module->private = private; module->run = &run; module->destroy = &destroy; module->content = &content; module->description = &description; private->data = pipewire_init(module); return module; } static struct module * from_conf(struct yml_node const *node, struct conf_inherit inherited) { struct yml_node const *content = yml_get_value(node, "content"); return pipewire_new(conf_to_particle(content, inherited)); } static bool verify_conf(keychain_t *keychain, struct yml_node const *node) { static struct attr_info const attrs[] = { MODULE_COMMON_ATTRS, }; return conf_verify_dict(keychain, node, attrs); } struct module_iface const module_pipewire_iface = { .from_conf = &from_conf, .verify_conf = &verify_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern struct module_iface const iface __attribute__((weak, alias("module_pipewire_iface"))); #endif yambar-1.11.0/modules/pulse.c000066400000000000000000000333221460770427600160310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #define LOG_MODULE "pulse" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" struct private { char *sink_name; char *source_name; struct particle *label; bool online; bool sink_online; pa_cvolume sink_volume; bool sink_muted; char *sink_port; uint32_t sink_index; bool source_online; pa_cvolume source_volume; bool source_muted; char *source_port; uint32_t source_index; int refresh_timer_fd; bool refresh_scheduled; pa_mainloop *mainloop; pa_context *context; }; static void destroy(struct module *mod) { struct private *priv = mod->private; priv->label->destroy(priv->label); free(priv->sink_name); free(priv->source_name); free(priv->sink_port); free(priv->source_port); free(priv); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "pulse"; } static struct exposable * content(struct module *mod) { struct private *priv = mod->private; mtx_lock(&mod->lock); pa_volume_t sink_volume_max = pa_cvolume_max(&priv->sink_volume); pa_volume_t source_volume_max = pa_cvolume_max(&priv->source_volume); int sink_percent = round(100.0 * sink_volume_max / PA_VOLUME_NORM); int source_percent = round(100.0 * source_volume_max / PA_VOLUME_NORM); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_bool(mod, "online", priv->online), tag_new_bool(mod, "sink_online", priv->sink_online), tag_new_int_range(mod, "sink_percent", sink_percent, 0, 100), tag_new_bool(mod, "sink_muted", priv->sink_muted), tag_new_string(mod, "sink_port", priv->sink_port), tag_new_bool(mod, "source_online", priv->source_online), tag_new_int_range(mod, "source_percent", source_percent, 0, 100), tag_new_bool(mod, "source_muted", priv->source_muted), tag_new_string(mod, "source_port", priv->source_port), }, .count = 9, }; mtx_unlock(&mod->lock); struct exposable *exposable = priv->label->instantiate(priv->label, &tags); tag_set_destroy(&tags); return exposable; } static const char * context_error(pa_context *c) { return pa_strerror(pa_context_errno(c)); } static void abort_event_cb(pa_mainloop_api *api, pa_io_event *event, int fd, pa_io_event_flags_t flags, void *userdata) { struct module *mod = userdata; struct private *priv = mod->private; pa_context_disconnect(priv->context); } static void refresh_timer_cb(pa_mainloop_api *api, pa_io_event *event, int fd, pa_io_event_flags_t flags, void *userdata) { struct module *mod = userdata; struct private *priv = mod->private; // Drain the refresh timer. uint64_t n; if (read(priv->refresh_timer_fd, &n, sizeof n) < 0) LOG_ERRNO("failed to read from timerfd"); // Clear the refresh flag. priv->refresh_scheduled = false; // Refresh the bar. mod->bar->refresh(mod->bar); } // Refresh the bar after a small delay. Without the delay, the bar // would be refreshed multiple times per event (e.g., a volume change), // and sometimes the active port would be reported incorrectly for a // brief moment. (This behavior was observed with PipeWire 0.3.61.) static void schedule_refresh(struct module *mod) { struct private *priv = mod->private; // Do nothing if a refresh has already been scheduled. if (priv->refresh_scheduled) return; // Start the refresh timer. struct itimerspec t = { .it_interval = {.tv_sec = 0, .tv_nsec = 0}, .it_value = {.tv_sec = 0, .tv_nsec = 50000000}, }; timerfd_settime(priv->refresh_timer_fd, 0, &t, NULL); // Set the refresh flag. priv->refresh_scheduled = true; } static void set_server_online(struct module *mod) { struct private *priv = mod->private; mtx_lock(&mod->lock); priv->online = true; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void set_server_offline(struct module *mod) { struct private *priv = mod->private; mtx_lock(&mod->lock); priv->online = false; priv->sink_online = false; priv->source_online = false; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void set_sink_info(struct module *mod, const pa_sink_info *sink_info) { struct private *priv = mod->private; mtx_lock(&mod->lock); free(priv->sink_port); priv->sink_online = true; priv->sink_index = sink_info->index; priv->sink_volume = sink_info->volume; priv->sink_muted = sink_info->mute; priv->sink_port = sink_info->active_port != NULL ? strdup(sink_info->active_port->description) : NULL; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void set_sink_offline(struct module *mod) { struct private *priv = mod->private; mtx_lock(&mod->lock); priv->sink_online = false; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void set_source_info(struct module *mod, const pa_source_info *source_info) { struct private *priv = mod->private; mtx_lock(&mod->lock); free(priv->source_port); priv->source_online = true; priv->source_index = source_info->index; priv->source_volume = source_info->volume; priv->source_muted = source_info->mute; priv->source_port = source_info->active_port != NULL ? strdup(source_info->active_port->description) : NULL; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void set_source_offline(struct module *mod) { struct private *priv = mod->private; mtx_lock(&mod->lock); priv->source_online = false; mtx_unlock(&mod->lock); schedule_refresh(mod); } static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { struct module *mod = userdata; if (eol < 0) { LOG_ERR("failed to get sink info: %s", context_error(c)); set_sink_offline(mod); } else if (eol == 0) { set_sink_info(mod, i); } } static void source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { struct module *mod = userdata; if (eol < 0) { LOG_ERR("failed to get source info: %s", context_error(c)); set_source_offline(mod); } else if (eol == 0) { set_source_info(mod, i); } } static void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { LOG_INFO("%s, version %s", i->server_name, i->server_version); } static void get_sink_info_by_name(pa_context *c, const char *name, void *userdata) { pa_operation *o = pa_context_get_sink_info_by_name(c, name, sink_info_cb, userdata); pa_operation_unref(o); } static void get_source_info_by_name(pa_context *c, const char *name, void *userdata) { pa_operation *o = pa_context_get_source_info_by_name(c, name, source_info_cb, userdata); pa_operation_unref(o); } static void get_sink_info_by_index(pa_context *c, uint32_t index, void *userdata) { pa_operation *o = pa_context_get_sink_info_by_index(c, index, sink_info_cb, userdata); pa_operation_unref(o); } static void get_source_info_by_index(pa_context *c, uint32_t index, void *userdata) { pa_operation *o = pa_context_get_source_info_by_index(c, index, source_info_cb, userdata); pa_operation_unref(o); } static void get_server_info(pa_context *c, void *userdata) { pa_operation *o = pa_context_get_server_info(c, server_info_cb, userdata); pa_operation_unref(o); } static void subscribe(pa_context *c, void *userdata) { pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; pa_operation *o = pa_context_subscribe(c, mask, NULL, userdata); pa_operation_unref(o); } static pa_context *connect_to_server(struct module *mod); static void context_state_change_cb(pa_context *c, void *userdata) { struct module *mod = userdata; struct private *priv = mod->private; pa_context_state_t state = pa_context_get_state(c); switch (state) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: set_server_online(mod); subscribe(c, mod); get_server_info(c, mod); get_sink_info_by_name(c, priv->sink_name, mod); get_source_info_by_name(c, priv->source_name, mod); break; case PA_CONTEXT_FAILED: LOG_WARN("connection lost"); set_server_offline(mod); pa_context_unref(priv->context); priv->context = connect_to_server(mod); break; case PA_CONTEXT_TERMINATED: LOG_DBG("connection terminated"); set_server_offline(mod); pa_mainloop_quit(priv->mainloop, 0); break; } } static void subscription_event_cb(pa_context *c, pa_subscription_event_type_t event_type, uint32_t index, void *userdata) { struct module *mod = userdata; struct private *priv = mod->private; int facility = event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; int type = event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; switch (facility) { case PA_SUBSCRIPTION_EVENT_SERVER: get_sink_info_by_name(c, priv->sink_name, mod); get_source_info_by_name(c, priv->source_name, mod); break; case PA_SUBSCRIPTION_EVENT_SINK: if (index == priv->sink_index) { if (type == PA_SUBSCRIPTION_EVENT_CHANGE) get_sink_info_by_index(c, index, mod); else if (type == PA_SUBSCRIPTION_EVENT_REMOVE) set_sink_offline(mod); } break; case PA_SUBSCRIPTION_EVENT_SOURCE: if (index == priv->source_index) { if (type == PA_SUBSCRIPTION_EVENT_CHANGE) get_source_info_by_index(c, index, mod); else if (type == PA_SUBSCRIPTION_EVENT_REMOVE) set_source_offline(mod); } break; } } static pa_context * connect_to_server(struct module *mod) { struct private *priv = mod->private; // Create connection context. pa_mainloop_api *api = pa_mainloop_get_api(priv->mainloop); pa_context *c = pa_context_new(api, "yambar"); if (c == NULL) { LOG_ERR("failed to create PulseAudio connection context"); return NULL; } // Register callback functions. pa_context_set_state_callback(c, context_state_change_cb, mod); pa_context_set_subscribe_callback(c, subscription_event_cb, mod); // Connect to server. pa_context_flags_t flags = PA_CONTEXT_NOFAIL | PA_CONTEXT_NOAUTOSPAWN; if (pa_context_connect(c, NULL, flags, NULL) < 0) { LOG_ERR("failed to connect to PulseAudio server: %s", context_error(c)); pa_context_unref(c); return NULL; } return c; } static int run(struct module *mod) { struct private *priv = mod->private; int ret = -1; // Create main loop. priv->mainloop = pa_mainloop_new(); if (priv->mainloop == NULL) { LOG_ERR("failed to create PulseAudio main loop"); return -1; } // Create refresh timer. priv->refresh_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if (priv->refresh_timer_fd < 0) { LOG_ERRNO("failed to create timerfd"); pa_mainloop_free(priv->mainloop); return -1; } // Connect to server. priv->context = connect_to_server(mod); if (priv->context == NULL) { pa_mainloop_free(priv->mainloop); close(priv->refresh_timer_fd); return -1; } // Poll refresh timer and abort event. pa_mainloop_api *api = pa_mainloop_get_api(priv->mainloop); api->io_new(api, priv->refresh_timer_fd, PA_IO_EVENT_INPUT, refresh_timer_cb, mod); api->io_new(api, mod->abort_fd, PA_IO_EVENT_INPUT | PA_IO_EVENT_HANGUP, abort_event_cb, mod); // Run main loop. if (pa_mainloop_run(priv->mainloop, &ret) < 0) { LOG_ERR("PulseAudio main loop error"); ret = -1; } // Clean up. pa_context_unref(priv->context); pa_mainloop_free(priv->mainloop); close(priv->refresh_timer_fd); return ret; } static struct module * pulse_new(const char *sink_name, const char *source_name, struct particle *label) { struct private *priv = calloc(1, sizeof *priv); priv->label = label; priv->sink_name = strdup(sink_name); priv->source_name = strdup(source_name); struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *sink = yml_get_value(node, "sink"); const struct yml_node *source = yml_get_value(node, "source"); const struct yml_node *content = yml_get_value(node, "content"); return pulse_new(sink != NULL ? yml_value_as_string(sink) : "@DEFAULT_SINK@", source != NULL ? yml_value_as_string(source) : "@DEFAULT_SOURCE@", conf_to_particle(content, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"sink", false, &conf_verify_string}, {"source", false, &conf_verify_string}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_pulse_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_pulse_iface"))); #endif yambar-1.11.0/modules/removables.c000066400000000000000000000560761460770427600170530ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "removables" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #define max(x, y) ((x) > (y) ? (x) : (y)) typedef tll(char *) mount_point_list_t; struct partition { const struct block_device *block; char *sys_path; char *dev_path; char *label; uint64_t size; bool audio_cd; mount_point_list_t mount_points; }; struct block_device { char *sys_path; char *dev_path; uint64_t size; char *vendor; char *model; bool optical; bool media; tll(struct partition) partitions; }; struct private { struct particle *label; int left_spacing; int right_spacing; tll(char *) ignore; tll(struct block_device) devices; }; static void free_partition(struct partition *p) { free(p->sys_path); free(p->dev_path); free(p->label); tll_free_and_free(p->mount_points, free); } static void free_device(struct block_device *b) { tll_foreach(b->partitions, it) free_partition(&it->item); tll_free(b->partitions); free(b->sys_path); free(b->dev_path); free(b->vendor); free(b->model); } static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); tll_foreach(m->devices, it) free_device(&it->item); tll_free(m->devices); tll_free_and_free(m->ignore, free); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "removables"; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; tll(const struct partition *) partitions = tll_init(); tll_foreach(m->devices, dev) { tll_foreach(dev->item.partitions, part) { tll_push_back(partitions, &part->item); } } struct exposable *exposables[max(tll_length(partitions), 1)]; size_t idx = 0; tll_foreach(partitions, it) { const struct partition *p = it->item; char dummy_label[16]; const char *label = p->label; if (label == NULL) { snprintf(dummy_label, sizeof(dummy_label), "%.1f GB", (double)p->size / 1024 / 1024 / 1024 * 512); label = dummy_label; } bool is_mounted = tll_length(p->mount_points) > 0; const char *mount_point = is_mounted ? tll_front(p->mount_points) : ""; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "vendor", p->block->vendor), tag_new_string(mod, "model", p->block->model), tag_new_bool(mod, "optical", p->block->optical), tag_new_bool(mod, "audio", p->audio_cd), tag_new_string(mod, "device", p->dev_path), tag_new_int_range(mod, "size", p->size, 0, p->block->size), tag_new_string(mod, "label", label), tag_new_bool(mod, "mounted", is_mounted), tag_new_string(mod, "mount_point", mount_point), }, .count = 9, }; exposables[idx++] = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); } tll_free(partitions); return dynlist_exposable_new(exposables, idx, m->left_spacing, m->right_spacing); } static void find_mount_points(const char *dev_path, mount_point_list_t *mount_points) { int fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); FILE *f = fd >= 0 ? fdopen(fd, "r") : NULL; if (fd < 0 || f == NULL) { LOG_ERRNO("failed to open /proc/self/mountinfo"); if (fd >= 0) close(fd); return; } char line[4096]; while (fgets(line, sizeof(line), f) != NULL) { char *dev = NULL, *path = NULL; if (sscanf(line, "%*u %*u %*u:%*u %*s %ms %*[^-] - %*s %ms %*s", &path, &dev) != 2) { LOG_ERR("failed to parse /proc/self/mountinfo: %s", line); free(dev); free(path); break; } if (strcmp(dev, dev_path) == 0) tll_push_back(*mount_points, strdup(path)); free(dev); free(path); } fclose(f); } static bool update_mount_points(struct partition *partition) { mount_point_list_t new_mounts = tll_init(); find_mount_points(partition->dev_path, &new_mounts); bool updated = false; /* Remove mount points that no longer exists (i.e. old mount * points that aren't in the new list) */ tll_foreach(partition->mount_points, old) { bool gone = true; tll_foreach(new_mounts, new) { if (strcmp(new->item, old->item) == 0) { /* Remove from new list, as it's already in the * partitions list */ tll_remove_and_free(new_mounts, new, free); gone = false; break; } } if (gone) { LOG_DBG("%s: unmounted from %s", partition->dev_path, old->item); tll_remove_and_free(partition->mount_points, old, free); updated = true; } } /* Add new mount points (i.e. mount points in the new list, that * aren't in the old list) */ tll_foreach(new_mounts, new) { LOG_DBG("%s: mounted on %s", partition->dev_path, new->item); tll_push_back(partition->mount_points, new->item); /* Remove, but don't free, since it's now owned by partition's list */ tll_remove(new_mounts, new); updated = true; } assert(tll_length(new_mounts) == 0); return updated; } static struct partition * add_partition(struct module *mod, struct block_device *block, struct udev_device *dev) { struct private *m = mod->private; const char *_size = udev_device_get_sysattr_value(dev, "size"); uint64_t size = 0; if (_size != NULL) sscanf(_size, "%" SCNu64, &size); #if 0 struct udev_list_entry *e = NULL; udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) { LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e)); } #endif const char *devname = udev_device_get_property_value(dev, "DEVNAME"); if (devname != NULL) { tll_foreach(m->ignore, it) { if (strcmp(it->item, devname) == 0) { LOG_DBG("ignoring %s because it is on the ignore list", devname); return NULL; } } } const char *label = udev_device_get_property_value(dev, "ID_FS_LABEL"); if (label == NULL) label = udev_device_get_property_value(dev, "ID_LABEL"); LOG_INFO("partition: add: %s: label=%s, size=%" PRIu64, udev_device_get_devnode(dev), label, size); mtx_lock(&mod->lock); tll_push_back(block->partitions, ((struct partition){.block = block, .sys_path = strdup(udev_device_get_devpath(dev)), .dev_path = strdup(udev_device_get_devnode(dev)), .label = label != NULL ? strdup(label) : NULL, .size = size, .audio_cd = false, .mount_points = tll_init()})); struct partition *p = &tll_back(block->partitions); update_mount_points(p); mtx_unlock(&mod->lock); return p; } static struct partition * add_audio_cd(struct module *mod, struct block_device *block, struct udev_device *dev) { struct private *m = mod->private; const char *_size = udev_device_get_sysattr_value(dev, "size"); uint64_t size = 0; if (_size != NULL) sscanf(_size, "%" SCNu64, &size); #if 0 struct udev_list_entry *e = NULL; udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) { LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e)); } #endif const char *devname = udev_device_get_property_value(dev, "DEVNAME"); if (devname != NULL) { tll_foreach(m->ignore, it) { if (strcmp(it->item, devname) == 0) { LOG_DBG("ignoring %s because it is on the ignore list", devname); return NULL; } } } const char *_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); unsigned long track_count = strtoul(_track_count, NULL, 10); char label[64]; snprintf(label, sizeof(label), "Audio CD - %lu tracks", track_count); LOG_INFO("audio CD: add: %s: tracks=%lu, label=%s, size=%" PRIu64, udev_device_get_devnode(dev), track_count, label, size); mtx_lock(&mod->lock); tll_push_back(block->partitions, ((struct partition){.block = block, .sys_path = strdup(udev_device_get_devpath(dev)), .dev_path = strdup(udev_device_get_devnode(dev)), .label = label != NULL ? strdup(label) : NULL, .size = size, .audio_cd = true, .mount_points = tll_init()})); struct partition *p = &tll_back(block->partitions); update_mount_points(p); mtx_unlock(&mod->lock); return p; } static bool del_partition(struct module *mod, struct block_device *block, struct udev_device *dev) { const char *sys_path = udev_device_get_devpath(dev); mtx_lock(&mod->lock); tll_foreach(block->partitions, it) { if (strcmp(it->item.sys_path, sys_path) == 0) { LOG_INFO("%s: del: %s", it->item.audio_cd ? "audio CD" : "partition", it->item.dev_path); free_partition(&it->item); tll_remove(block->partitions, it); mtx_unlock(&mod->lock); return true; } } mtx_unlock(&mod->lock); return false; } static struct block_device * add_device(struct module *mod, struct udev_device *dev) { struct private *m = mod->private; const char *_removable = udev_device_get_sysattr_value(dev, "removable"); bool removable = _removable != NULL && strcmp(_removable, "1") == 0; const char *_sd = udev_device_get_property_value(dev, "ID_DRIVE_FLASH_SD"); bool sd = _sd != NULL && strcmp(_sd, "1") == 0; if (!removable && !sd) return NULL; const char *devname = udev_device_get_property_value(dev, "DEVNAME"); if (devname != NULL) { tll_foreach(m->ignore, it) { if (strcmp(it->item, devname) == 0) { LOG_DBG("ignoring %s because it is on the ignore list", devname); return NULL; } } } const char *_size = udev_device_get_sysattr_value(dev, "size"); uint64_t size = 0; if (_size != NULL) sscanf(_size, "%" SCNu64, &size); #if 1 struct udev_list_entry *e = NULL; udev_list_entry_foreach(e, udev_device_get_properties_list_entry(dev)) { LOG_DBG("%s -> %s", udev_list_entry_get_name(e), udev_list_entry_get_value(e)); } #endif const char *vendor = udev_device_get_property_value(dev, "ID_VENDOR"); const char *model = udev_device_get_property_value(dev, "ID_MODEL"); const char *_optical = udev_device_get_property_value(dev, "ID_CDROM"); bool optical = _optical != NULL && strcmp(_optical, "1") == 0; const char *_media = udev_device_get_property_value(dev, "ID_CDROM_MEDIA"); bool media = _media != NULL && strcmp(_media, "1") == 0; const char *_fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE"); bool have_fs = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0; const char *_audio_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); unsigned long audio_track_count = _audio_track_count != NULL ? strtoul(_audio_track_count, NULL, 10) : 0; LOG_DBG("device: add: %s: vendor=%s, model=%s, optical=%d, size=%" PRIu64, udev_device_get_devnode(dev), vendor, model, optical, size); mtx_lock(&mod->lock); tll_push_back(m->devices, ((struct block_device){.sys_path = strdup(udev_device_get_devpath(dev)), .dev_path = strdup(udev_device_get_devnode(dev)), .size = size, .vendor = vendor != NULL ? strdup(vendor) : NULL, .model = model != NULL ? strdup(model) : NULL, .optical = optical, .media = media, .partitions = tll_init()})); mtx_unlock(&mod->lock); struct block_device *block = &tll_back(m->devices); if (optical) { if (have_fs) add_partition(mod, block, dev); else if (audio_track_count > 0) add_audio_cd(mod, block, dev); } return &tll_back(m->devices); } static bool del_device(struct module *mod, struct udev_device *dev) { struct private *m = mod->private; const char *sys_path = udev_device_get_devpath(dev); mtx_lock(&mod->lock); tll_foreach(m->devices, it) { if (strcmp(it->item.sys_path, sys_path) == 0) { LOG_DBG("device: del: %s", it->item.dev_path); free_device(&it->item); tll_remove(m->devices, it); mtx_unlock(&mod->lock); return true; } } mtx_unlock(&mod->lock); return false; } static bool change_device(struct module *mod, struct udev_device *dev) { struct private *m = mod->private; const char *sys_path = udev_device_get_devpath(dev); mtx_lock(&mod->lock); struct block_device *block = NULL; tll_foreach(m->devices, it) { if (strcmp(it->item.sys_path, sys_path) == 0) { block = &it->item; break; } } if (block == NULL) goto out; LOG_DBG("device: change: %s", block->dev_path); if (!block->optical) goto out; const char *_media = udev_device_get_property_value(dev, "ID_CDROM_MEDIA"); bool media = _media != NULL && strcmp(_media, "1") == 0; const char *_fs_usage = udev_device_get_property_value(dev, "ID_FS_USAGE"); bool have_fs = _fs_usage != NULL && strcmp(_fs_usage, "filesystem") == 0; const char *_audio_track_count = udev_device_get_property_value(dev, "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO"); unsigned long audio_track_count = _audio_track_count != NULL ? strtoul(_audio_track_count, NULL, 10) : 0; bool media_change = media != block->media; block->media = media; mtx_unlock(&mod->lock); if (media_change) { LOG_INFO("device: change: %s: media %s", block->dev_path, media ? "inserted" : "removed"); if (media) { if (have_fs) return add_partition(mod, block, dev) != NULL; else if (audio_track_count > 0) return add_audio_cd(mod, block, dev) != NULL; } else return del_partition(mod, block, dev); } out: mtx_unlock(&mod->lock); return false; } static bool handle_udev_event(struct module *mod, struct udev_device *dev) { struct private *m = mod->private; const char *action = udev_device_get_action(dev); bool add = strcmp(action, "add") == 0; bool del = strcmp(action, "remove") == 0; bool change = strcmp(action, "change") == 0; if (!add && !del && !change) { LOG_WARN("%s: unhandled action: %s", udev_device_get_devpath(dev), action); return false; } const char *devtype = udev_device_get_property_value(dev, "DEVTYPE"); if (strcmp(devtype, "disk") == 0) { if (add) return add_device(mod, dev) != NULL; else if (del) return del_device(mod, dev); else return change_device(mod, dev); } if (strcmp(devtype, "partition") == 0) { struct udev_device *parent = udev_device_get_parent(dev); const char *parent_sys_path = udev_device_get_devpath(parent); tll_foreach(m->devices, it) { if (strcmp(it->item.sys_path, parent_sys_path) != 0) continue; if (add) return add_partition(mod, &it->item, dev) != NULL; else if (del) return del_partition(mod, &it->item, dev); else { LOG_ERR("unimplemented: 'change' event on partition: %s", udev_device_get_devpath(dev)); return false; } break; } } return false; } static int run(struct module *mod) { struct private *m = mod->private; struct udev *udev = udev_new(); struct udev_monitor *dev_mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(dev_mon, "block", NULL); udev_monitor_enable_receiving(dev_mon); struct udev_enumerate *dev_enum = udev_enumerate_new(udev); assert(dev_enum != NULL); udev_enumerate_add_match_subsystem(dev_enum, "block"); /* TODO: verify how an optical presents itself */ // udev_enumerate_add_match_sysattr(dev_enum, "removable", "1"); udev_enumerate_add_match_property(dev_enum, "DEVTYPE", "disk"); udev_enumerate_scan_devices(dev_enum); /* Loop list, and for each device, enumerate its partitions */ struct udev_list_entry *entry = NULL; udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(dev_enum)) { struct udev_device *dev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(entry)); struct block_device *block = add_device(mod, dev); if (block == NULL) { udev_device_unref(dev); continue; } struct udev_enumerate *part_enum = udev_enumerate_new(udev); assert(dev_enum != NULL); udev_enumerate_add_match_subsystem(part_enum, "block"); udev_enumerate_add_match_parent(part_enum, dev); udev_enumerate_add_match_property(part_enum, "DEVTYPE", "partition"); udev_enumerate_scan_devices(part_enum); struct udev_list_entry *sub_entry = NULL; udev_list_entry_foreach(sub_entry, udev_enumerate_get_list_entry(part_enum)) { struct udev_device *partition = udev_device_new_from_syspath(udev, udev_list_entry_get_name(sub_entry)); add_partition(mod, block, partition); udev_device_unref(partition); } udev_enumerate_unref(part_enum); udev_device_unref(dev); } udev_enumerate_unref(dev_enum); mod->bar->refresh(mod->bar); /* To be able to poll() mountinfo for changes, to detect * mount/unmount operations */ int mount_info_fd = open("/proc/self/mountinfo", O_RDONLY | O_CLOEXEC); int ret = 1; while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = udev_monitor_get_fd(dev_mon), .events = POLLIN}, {.fd = mount_info_fd, .events = POLLPRI}, }; if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = 0; break; } bool update = false; if (fds[2].revents & POLLPRI) { tll_foreach(m->devices, dev) { tll_foreach(dev->item.partitions, part) { if (update_mount_points(&part->item)) update = true; } } } if (fds[1].revents & POLLIN) { struct udev_device *dev = udev_monitor_receive_device(dev_mon); if (dev == NULL) continue; if (handle_udev_event(mod, dev)) update = true; udev_device_unref(dev); } if (update) mod->bar->refresh(mod->bar); } close(mount_info_fd); udev_monitor_unref(dev_mon); udev_unref(udev); return ret; } static struct module * removables_new(struct particle *label, int left_spacing, int right_spacing, size_t ignore_count, const char *ignore[static ignore_count]) { struct private *priv = calloc(1, sizeof(*priv)); priv->label = label; priv->left_spacing = left_spacing; priv->right_spacing = right_spacing; for (size_t i = 0; i < ignore_count; i++) tll_push_back(priv->ignore, strdup(ignore[i])); struct module *mod = module_common_new(); mod->private = priv; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *content = yml_get_value(node, "content"); const struct yml_node *spacing = yml_get_value(node, "spacing"); const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); const struct yml_node *ignore_list = yml_get_value(node, "ignore"); int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int right = spacing != NULL ? yml_value_as_int(spacing) : right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; size_t ignore_count = ignore_list != NULL ? yml_list_length(ignore_list) : 0; const char *ignore[max(ignore_count, 1)]; if (ignore_list != NULL) { size_t i = 0; for (struct yml_list_iter iter = yml_list_iter(ignore_list); iter.node != NULL; yml_list_next(&iter), i++) { ignore[i] = yml_value_as_string(iter.node); } } return removables_new(conf_to_particle(content, inherited), left, right, ignore_count, ignore); } static bool verify_ignore(keychain_t *chain, const struct yml_node *node) { return conf_verify_list(chain, node, &conf_verify_string); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, {"ignore", false, &verify_ignore}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_removables_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_removables_iface"))); #endif yambar-1.11.0/modules/river.c000066400000000000000000000521171460770427600160330ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "river" #define LOG_ENABLE_DBG 0 #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "river-status-unstable-v1.h" #include "xdg-output-unstable-v1.h" #define min(x, y) ((x) < (y) ? (x) : (y)) struct private; struct output { struct private *m; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; struct zriver_output_status_v1 *status; uint32_t wl_name; char *name; /* Tags */ uint32_t occupied; uint32_t focused; uint32_t urgent; /* Layout */ char *layout; }; struct seat { struct private *m; struct wl_seat *wl_seat; struct zriver_seat_status_v1 *status; uint32_t wl_name; char *name; char *mode; char *title; struct output *output; }; struct private { struct module *mod; struct zxdg_output_manager_v1 *xdg_output_manager; struct zriver_status_manager_v1 *status_manager; struct particle *template; struct particle *title; bool all_monitors; tll(struct output) outputs; tll(struct seat) seats; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->template->destroy(m->template); if (m->title != NULL) m->title->destroy(m->title); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "river"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; const char *output_bar_is_on = mod->bar->output_name(mod->bar); mtx_lock(&m->mod->lock); uint32_t urgent = 0; uint32_t occupied = 0; uint32_t output_focused = 0; uint32_t seat_focused = 0; tll_foreach(m->outputs, it) { const struct output *output = &it->item; if (!m->all_monitors && output_bar_is_on != NULL && output->name != NULL && strcmp(output->name, output_bar_is_on) != 0) { continue; } output_focused |= output->focused; urgent |= output->urgent; occupied |= output->occupied; tll_foreach(m->seats, it2) { const struct seat *seat = &it2->item; if (seat->output == output) { seat_focused |= output->focused; } } } const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0; struct exposable *tag_parts[32 + seat_count]; for (unsigned i = 0; i < 32; i++) { /* It's visible if any output has it focused */ bool is_visible = output_focused & (1u << i); /* It's focused if any output that has seat focus has it focused */ bool is_focused = seat_focused & (1u << i); bool is_urgent = urgent & (1u << i); bool is_occupied = occupied & (1u << i); const char *state = is_urgent ? "urgent" : is_visible ? is_focused ? "focused" : "unfocused" : "invisible"; #if 0 LOG_DBG("tag: #%u, visible=%d, focused=%d, occupied=%d, state=%s", i, is_visible, is_focused, is_occupied & (1u << i), state); #endif struct tag_set tags = { .tags = (struct tag *[]){ tag_new_int(mod, "id", i + 1), tag_new_bool(mod, "urgent", is_urgent), tag_new_bool(mod, "visible", is_visible), tag_new_bool(mod, "focused", is_focused), tag_new_bool(mod, "occupied", is_occupied), tag_new_string(mod, "state", state), }, .count = 6, }; tag_parts[i] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); } if (m->title != NULL) { size_t i = 32; tll_foreach(m->seats, it) { const struct seat *seat = &it->item; const char *layout = seat->output != NULL && seat->output->layout != NULL ? seat->output->layout : ""; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "seat", seat->name), tag_new_string(mod, "title", seat->title), tag_new_string(mod, "mode", seat->mode), tag_new_string(mod, "layout", layout), }, .count = 4, }; tag_parts[i++] = m->title->instantiate(m->title, &tags); tag_set_destroy(&tags); } } mtx_unlock(&m->mod->lock); return dynlist_exposable_new(tag_parts, 32 + seat_count, 0, 0); } static bool verify_iface_version(const char *iface, uint32_t version, uint32_t wanted) { if (version >= wanted) return true; LOG_ERR("%s: need interface version %u, but compositor only implements %u", iface, wanted, version); return false; } static void output_destroy(struct output *output) { tll_foreach(output->m->seats, it) { struct seat *seat = &it->item; if (seat->output == output) seat->output = NULL; } free(output->name); free(output->layout); if (output->status != NULL) zriver_output_status_v1_destroy(output->status); if (output->xdg_output != NULL) zxdg_output_v1_destroy(output->xdg_output); if (output->wl_output != NULL) wl_output_release(output->wl_output); } static void seat_destroy(struct seat *seat) { free(seat->title); free(seat->name); free(seat->mode); if (seat->status != NULL) zriver_seat_status_v1_destroy(seat->status); if (seat->wl_seat != NULL) wl_seat_destroy(seat->wl_seat); } static void focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags) { struct output *output = data; if (output->focused == tags) return; LOG_DBG("output: %s: focused tags: 0x%08x", output->name, tags); struct module *mod = output->m->mod; mtx_lock(&mod->lock); output->focused = tags; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static void view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, struct wl_array *tags) { struct output *output = data; struct module *mod = output->m->mod; mtx_lock(&mod->lock); { output->occupied = 0; /* Each entry in the list is a view, and the value is the tags * associated with that view */ uint32_t *set; wl_array_for_each(set, tags) { output->occupied |= *set; } LOG_DBG("output: %s: occupied tags: 0x%0x", output->name, output->occupied); } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static void urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags) { struct output *output = data; struct module *mod = output->m->mod; mtx_lock(&mod->lock); { output->urgent = tags; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } #if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) static void layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, const char *name) { struct output *output = data; struct module *mod = output->m->mod; mtx_lock(&mod->lock); { free(output->layout); output->layout = name != NULL ? strdup(name) : NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } #endif #if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION) static void layout_name_clear(void *data, struct zriver_output_status_v1 *zriver_output_status_v1) { struct output *output = data; struct module *mod = output->m->mod; mtx_lock(&mod->lock); { free(output->layout); output->layout = NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } #endif static const struct zriver_output_status_v1_listener river_status_output_listener = { .focused_tags = &focused_tags, .view_tags = &view_tags, .urgent_tags = &urgent_tags, #if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) .layout_name = &layout_name, #endif #if defined(ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION) .layout_name_clear = &layout_name_clear, #endif }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { } static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { } static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct output *output = data; struct module *mod = output->m->mod; mtx_lock(&mod->lock); { free(output->name); output->name = name != NULL ? strdup(name) : NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { } static struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = xdg_output_handle_done, .name = xdg_output_handle_name, .description = xdg_output_handle_description, }; static void update_output(struct output *output) { assert(output->wl_output != NULL); if (output->m->status_manager != NULL) { /* * Bind river output status, if we have already bound the status manager */ if (output->status != NULL) { zriver_output_status_v1_destroy(output->status); output->status = NULL; } output->status = zriver_status_manager_v1_get_river_output_status(output->m->status_manager, output->wl_output); if (output->status != NULL) { zriver_output_status_v1_add_listener(output->status, &river_status_output_listener, output); } } if (output->m->xdg_output_manager != NULL && output->xdg_output == NULL) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output(output->m->xdg_output_manager, output->wl_output); if (output->xdg_output != NULL) { zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } } } static void focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *wl_output) { struct seat *seat = data; struct private *m = seat->m; struct module *mod = m->mod; struct output *output = NULL; tll_foreach(m->outputs, it) { if (it->item.wl_output == wl_output) { output = &it->item; break; } } LOG_DBG("seat: %s: focused output: %s", seat->name, output != NULL ? output->name : ""); if (output == NULL) LOG_WARN("seat: %s: couldn't find output we are mapped on", seat->name); if (seat->output != output) { mtx_lock(&mod->lock); seat->output = output; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } } static void unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *wl_output) { struct seat *seat = data; struct private *m = seat->m; struct module *mod = m->mod; mtx_lock(&mod->lock); { struct output *output = NULL; tll_foreach(m->outputs, it) { if (it->item.wl_output == wl_output) { output = &it->item; break; } } LOG_DBG("seat: %s: unfocused output: %s", seat->name, output != NULL ? output->name : ""); if (output == NULL) LOG_WARN("seat: %s: couldn't find output we were unmapped from", seat->name); seat->output = NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static void focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *title) { struct seat *seat = data; struct module *mod = seat->m->mod; if (seat->title == NULL && title == NULL) return; if (seat->title != NULL && title != NULL && strcmp(seat->title, title) == 0) return; LOG_DBG("seat: %s: focused view: %s", seat->name, title); const char *output_bar_is_on = mod->bar->output_name(mod->bar); if (seat->m->all_monitors || (output_bar_is_on != NULL && seat->output != NULL && seat->output->name != NULL && strcmp(output_bar_is_on, seat->output->name) == 0)) { mtx_lock(&mod->lock); { free(seat->title); seat->title = title != NULL ? strdup(title) : NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } } #if defined(ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) static void mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *name) { struct seat *seat = data; struct module *mod = seat->m->mod; mtx_lock(&mod->lock); { free(seat->mode); seat->mode = strdup(name); mtx_unlock(&mod->lock); } mod->bar->refresh(mod->bar); LOG_DBG("seat: %s, current mode: %s", seat->name, seat->mode); } #endif static const struct zriver_seat_status_v1_listener river_seat_status_listener = { .focused_output = &focused_output, .unfocused_output = &unfocused_output, .focused_view = &focused_view, #if defined(ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) .mode = &mode, #endif }; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { } static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) { struct seat *seat = data; struct module *mod = seat->m->mod; mtx_lock(&mod->lock); { free(seat->name); seat->name = name != NULL ? strdup(name) : NULL; } mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static const struct wl_seat_listener seat_listener = { .capabilities = seat_handle_capabilities, .name = seat_handle_name, }; static void update_seat(struct seat *seat) { assert(seat->wl_seat != NULL); if (seat->m->status_manager == NULL) return; if (seat->status != NULL) { zriver_seat_status_v1_destroy(seat->status); seat->status = NULL; } seat->status = zriver_status_manager_v1_get_river_seat_status(seat->m->status_manager, seat->wl_seat); if (seat->status == NULL) return; zriver_seat_status_v1_add_listener(seat->status, &river_seat_status_listener, seat); } static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct private *m = data; if (strcmp(interface, wl_output_interface.name) == 0) { const uint32_t required = 3; if (!verify_iface_version(interface, version, required)) return; struct wl_output *wl_output = wl_registry_bind(registry, name, &wl_output_interface, required); if (wl_output == NULL) return; struct output output = { .m = m, .wl_output = wl_output, .wl_name = name, }; mtx_lock(&m->mod->lock); tll_push_back(m->outputs, output); update_output(&tll_back(m->outputs)); tll_foreach(m->seats, it) update_seat(&it->item); mtx_unlock(&m->mod->lock); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { const uint32_t required = 2; if (!verify_iface_version(interface, version, required)) return; m->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, required); mtx_lock(&m->mod->lock); tll_foreach(m->outputs, it) update_output(&it->item); mtx_unlock(&m->mod->lock); } else if (strcmp(interface, wl_seat_interface.name) == 0) { const uint32_t required = 2; if (!verify_iface_version(interface, version, required)) return; struct wl_seat *wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, required); if (wl_seat == NULL) return; mtx_lock(&m->mod->lock); tll_push_back(m->seats, ((struct seat){.m = m, .wl_seat = wl_seat, .wl_name = name})); struct seat *seat = &tll_back(m->seats); wl_seat_add_listener(wl_seat, &seat_listener, seat); update_seat(seat); mtx_unlock(&m->mod->lock); } else if (strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { const uint32_t required = 2; if (!verify_iface_version(interface, version, required)) return; m->status_manager = wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, min(version, 4)); mtx_lock(&m->mod->lock); tll_foreach(m->outputs, it) update_output(&it->item); tll_foreach(m->seats, it) update_seat(&it->item); mtx_unlock(&m->mod->lock); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct private *m = data; mtx_lock(&m->mod->lock); tll_foreach(m->outputs, it) { if (it->item.wl_name == name) { output_destroy(&it->item); tll_remove(m->outputs, it); mtx_unlock(&m->mod->lock); return; } } tll_foreach(m->seats, it) { if (it->item.wl_name == name) { seat_destroy(&it->item); tll_remove(m->seats, it); mtx_unlock(&m->mod->lock); return; } } mtx_unlock(&m->mod->lock); } static const struct wl_registry_listener registry_listener = { .global = &handle_global, .global_remove = &handle_global_remove, }; static int run(struct module *mod) { struct private *m = mod->private; int ret = 1; struct wl_display *display = NULL; struct wl_registry *registry = NULL; if ((display = wl_display_connect(NULL)) == NULL) { LOG_ERR("no Wayland compositor running?"); goto out; } if ((registry = wl_display_get_registry(display)) == NULL || wl_registry_add_listener(registry, ®istry_listener, m) != 0) { LOG_ERR("failed to get Wayland registry"); goto out; } wl_display_roundtrip(display); if (m->status_manager == NULL) { LOG_ERR("river does not appear to be running"); goto out; } wl_display_roundtrip(display); while (true) { wl_display_flush(display); struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = wl_display_get_fd(display), .events = POLLIN}, }; int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (r == -1) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if ((fds[0].revents & POLLIN) || (fds[0].revents & POLLHUP)) break; if (fds[1].revents & POLLHUP) { LOG_ERRNO("disconnected from Wayland compositor"); break; } assert(fds[1].revents & POLLIN); wl_display_dispatch(display); } ret = 0; out: tll_foreach(m->seats, it) seat_destroy(&it->item); tll_free(m->seats); tll_foreach(m->outputs, it) output_destroy(&it->item); tll_free(m->outputs); if (m->xdg_output_manager != NULL) zxdg_output_manager_v1_destroy(m->xdg_output_manager); if (m->status_manager != NULL) zriver_status_manager_v1_destroy(m->status_manager); if (registry != NULL) wl_registry_destroy(registry); if (display != NULL) wl_display_disconnect(display); return ret; } static struct module * river_new(struct particle *template, struct particle *title, bool all_monitors) { struct private *m = calloc(1, sizeof(*m)); m->template = template; m->title = title; m->all_monitors = all_monitors; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; m->mod = mod; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *title = yml_get_value(node, "title"); const struct yml_node *all_monitors = yml_get_value(node, "all-monitors"); return river_new(conf_to_particle(c, inherited), title != NULL ? conf_to_particle(title, inherited) : NULL, all_monitors != NULL ? yml_value_as_bool(all_monitors) : false); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"title", false, &conf_verify_particle}, {"all-monitors", false, &conf_verify_bool}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_river_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_river_iface"))); #endif yambar-1.11.0/modules/script.c000066400000000000000000000454011460770427600162060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "script" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../module.h" #include "../plugin.h" static const long min_poll_interval = 250; struct private { char *path; size_t argc; char **argv; int poll_interval; bool aborted; struct particle *content; struct tag_set tags; struct { char *data; size_t sz; size_t idx; } recv_buf; }; static void destroy(struct module *mod) { struct private *m = mod->private; m->content->destroy(m->content); struct tag **tag_array = m->tags.tags; tag_set_destroy(&m->tags); free(tag_array); for (size_t i = 0; i < m->argc; i++) free(m->argv[i]); free(m->argv); free(m->recv_buf.data); free(m->path); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { static char desc[32]; const struct private *m = mod->private; char *path = strdup(m->path); snprintf(desc, sizeof(desc), "script(%s)", basename(path)); free(path); return desc; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); struct exposable *e = m->content->instantiate(m->content, &m->tags); mtx_unlock(&mod->lock); return e; } static struct tag * process_line(struct module *mod, const char *line, size_t len) { char *name = NULL; char *value = NULL; const char *_name = line; const char *type = memchr(line, '|', len); if (type == NULL) goto bad_tag; size_t name_len = type - _name; type++; const char *_value = memchr(type, '|', len - name_len - 1); if (_value == NULL) goto bad_tag; size_t type_len = _value - type; _value++; size_t value_len = line + len - _value; LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"", (int)len, line, (int)name_len, _name, (int)type_len, type, (int)value_len, _value); name = malloc(name_len + 1); memcpy(name, _name, name_len); name[name_len] = '\0'; value = malloc(value_len + 1); memcpy(value, _value, value_len); value[value_len] = '\0'; struct tag *tag = NULL; if (type_len == 6 && memcmp(type, "string", 6) == 0) tag = tag_new_string(mod, name, value); else if (type_len == 3 && memcmp(type, "int", 3) == 0) { errno = 0; char *end; long v = strtol(value, &end, 0); if (errno != 0 || *end != '\0') { LOG_ERR("tag value is not an integer: %s", value); goto bad_tag; } tag = tag_new_int(mod, name, v); } else if (type_len == 4 && memcmp(type, "bool", 4) == 0) { bool v; if (strcmp(value, "true") == 0) v = true; else if (strcmp(value, "false") == 0) v = false; else { LOG_ERR("tag value is not a boolean: %s", value); goto bad_tag; } tag = tag_new_bool(mod, name, v); } else if (type_len == 5 && memcmp(type, "float", 5) == 0) { errno = 0; char *end; double v = strtod(value, &end); if (errno != 0 || *end != '\0') { LOG_ERR("tag value is not a float: %s", value); goto bad_tag; } tag = tag_new_float(mod, name, v); } else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) || (type_len > 9 && memcmp(type, "realtime:", 9) == 0)) { const char *_start = type + 6; const char *split = memchr(_start, '-', type_len - 6); if (split == NULL || split == _start || (split + 1) - type >= type_len) { LOG_ERR("tag range delimiter ('-') not found in type: %.*s", (int)type_len, type); goto bad_tag; } const char *_end = split + 1; size_t start_len = split - _start; size_t end_len = type + type_len - _end; long start = 0; for (size_t i = 0; i < start_len; i++) { if (!(_start[i] >= '0' && _start[i] <= '9')) { LOG_ERR("tag range start is not an integer: %.*s", (int)start_len, _start); goto bad_tag; } start *= 10; start += _start[i] - '0'; } long end = 0; for (size_t i = 0; i < end_len; i++) { if (!(_end[i] >= '0' && _end[i] <= '9')) { LOG_ERR("tag range end is not an integer: %.*s", (int)end_len, _end); goto bad_tag; } end *= 10; end += _end[i] - '0'; } if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) { LOG_ERR("unimplemented: realtime tag"); goto bad_tag; } errno = 0; char *vend; long v = strtol(value, &vend, 0); if (errno != 0 || *vend != '\0') { LOG_ERR("tag value is not an integer: %s", value); goto bad_tag; } if (v < start || v > end) { LOG_ERR("tag value is outside range: %ld <= %ld <= %ld", start, v, end); goto bad_tag; } tag = tag_new_int_range(mod, name, v, start, end); } else { goto bad_tag; } free(name); free(value); return tag; bad_tag: LOG_ERR("invalid tag: %.*s", (int)len, line); free(name); free(value); return NULL; } static void process_transaction(struct module *mod, size_t size) { struct private *m = mod->private; mtx_lock(&mod->lock); size_t left = size; const char *line = m->recv_buf.data; size_t line_count = 0; { const char *p = line; while ((p = memchr(p, '\n', size - (p - line))) != NULL) { p++; line_count++; } } struct tag **old_tag_array = m->tags.tags; tag_set_destroy(&m->tags); free(old_tag_array); m->tags.tags = calloc(line_count, sizeof(m->tags.tags[0])); m->tags.count = line_count; size_t idx = 0; while (left > 0) { char *line_end = memchr(line, '\n', left); assert(line_end != NULL); size_t line_len = line_end - line; struct tag *tag = process_line(mod, line, line_len); if (tag != NULL) m->tags.tags[idx++] = tag; left -= line_len + 1; line += line_len + 1; } m->tags.count = idx; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } static bool data_received(struct module *mod, const char *data, size_t len) { struct private *m = mod->private; while (len > m->recv_buf.sz - m->recv_buf.idx) { size_t new_sz = m->recv_buf.sz == 0 ? 1024 : m->recv_buf.sz * 2; char *new_buf = realloc(m->recv_buf.data, new_sz); if (new_buf == NULL) return false; m->recv_buf.data = new_buf; m->recv_buf.sz = new_sz; } assert(m->recv_buf.sz >= m->recv_buf.idx); assert(m->recv_buf.sz - m->recv_buf.idx >= len); memcpy(&m->recv_buf.data[m->recv_buf.idx], data, len); m->recv_buf.idx += len; while (true) { const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2); if (eot == NULL) { /* End of transaction not yet available */ return true; } const size_t transaction_size = eot - m->recv_buf.data + 1; process_transaction(mod, transaction_size); assert(m->recv_buf.idx >= transaction_size + 1); memmove(m->recv_buf.data, &m->recv_buf.data[transaction_size + 1], m->recv_buf.idx - (transaction_size + 1)); m->recv_buf.idx -= transaction_size + 1; } return true; } static int run_loop(struct module *mod, pid_t pid, int comm_fd) { int ret = 1; while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, {.fd = comm_fd, .events = POLLIN}, }; int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (r < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[1].revents & POLLIN) { char data[4096]; ssize_t amount = read(comm_fd, data, sizeof(data)); if (amount < 0) { LOG_ERRNO("failed to read from script"); break; } LOG_DBG("recv: \"%.*s\"", (int)amount, data); data_received(mod, data, amount); } if (fds[0].revents & (POLLHUP | POLLIN)) { /* Aborted */ struct private *m = mod->private; m->aborted = true; ret = 0; break; } if (fds[1].revents & POLLHUP) { /* Child's stdout closed */ LOG_DBG("script pipe closed (script terminated?)"); ret = 0; break; } } return ret; } static int execute_script(struct module *mod) { struct private *m = mod->private; /* Pipe to detect exec() failures */ int exec_pipe[2]; if (pipe2(exec_pipe, O_CLOEXEC) < 0) { LOG_ERRNO("failed to create pipe"); return -1; } /* Stdout redirection pipe */ int comm_pipe[2]; if (pipe2(comm_pipe, O_CLOEXEC) < 0) { LOG_ERRNO("failed to create stdin/stdout redirection pipe"); close(exec_pipe[0]); close(exec_pipe[1]); return -1; } int pid = fork(); if (pid < 0) { LOG_ERRNO("failed to fork"); close(comm_pipe[0]); close(comm_pipe[1]); close(exec_pipe[0]); close(exec_pipe[1]); return -1; } if (pid == 0) { /* Child */ /* Construct argv for execvp() */ char *argv[1 + m->argc + 1]; argv[0] = m->path; for (size_t i = 0; i < m->argc; i++) argv[i + 1] = m->argv[i]; argv[1 + m->argc] = NULL; /* Restore signal handlers and signal mask */ sigset_t mask; sigemptyset(&mask); const struct sigaction sa = {.sa_handler = SIG_DFL}; if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0 || sigprocmask(SIG_SETMASK, &mask, NULL) < 0) { goto fail; } /* New process group, so that we can use killpg() */ setpgid(0, 0); /* Close pipe read ends */ close(exec_pipe[0]); close(comm_pipe[0]); /* Re-direct stdin/stdout */ int dev_null = open("/dev/null", O_RDONLY | O_CLOEXEC); if (dev_null < 0) goto fail; if (dup2(dev_null, STDIN_FILENO) < 0 || dup2(comm_pipe[1], STDOUT_FILENO) < 0) { goto fail; } /* We're done with the redirection pipe */ close(comm_pipe[1]); comm_pipe[1] = -1; execvp(m->path, argv); fail: (void)!write(exec_pipe[1], &errno, sizeof(errno)); close(exec_pipe[1]); if (comm_pipe[1] >= 0) close(comm_pipe[1]); _exit(errno); } /* Close pipe write ends */ close(exec_pipe[1]); close(comm_pipe[1]); int _errno; static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch"); /* Wait for errno from child, or FD being closed in execvp() */ int r = read(exec_pipe[0], &_errno, sizeof(_errno)); close(exec_pipe[0]); if (r < 0) { LOG_ERRNO("failed to read from pipe"); close(comm_pipe[0]); return -1; } if (r > 0) { LOG_ERRNO_P(_errno, "%s: failed to start", m->path); close(comm_pipe[0]); waitpid(pid, NULL, 0); return -1; } /* Pipe was closed. I.e. execvp() succeeded */ assert(r == 0); LOG_DBG("script running under PID=%u", pid); int ret = run_loop(mod, pid, comm_pipe[0]); close(comm_pipe[0]); if (waitpid(pid, NULL, WNOHANG) == 0) { static const struct { int signo; int timeout; const char *name; } sig_info[] = { {SIGINT, 2, "SIGINT"}, {SIGTERM, 5, "SIGTERM"}, {SIGKILL, 0, "SIGKILL"}, }; for (size_t i = 0; i < sizeof(sig_info) / sizeof(sig_info[0]); i++) { struct timeval start; gettimeofday(&start, NULL); const int signo = sig_info[i].signo; const int timeout = sig_info[i].timeout; const char *const name __attribute__((unused)) = sig_info[i].name; LOG_DBG("sending %s to PID=%u (timeout=%ds)", name, pid, timeout); killpg(pid, signo); /* * Child is unlikely to terminate *immediately*. Wait a * *short* period of time before checking waitpid() the * first time */ usleep(10000); pid_t waited_pid; while ((waited_pid = waitpid(pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0) { struct timeval now; gettimeofday(&now, NULL); struct timeval elapsed; timersub(&now, &start, &elapsed); if (elapsed.tv_sec >= timeout) break; /* Don't spinning */ thrd_yield(); usleep(100000); /* 100ms */ } if (waited_pid == pid) { /* Child finally dead */ break; } } } else LOG_DBG("PID=%u already terminated", pid); return ret; } static int run(struct module *mod) { struct private *m = mod->private; int ret = 1; bool keep_going = true; while (keep_going && !m->aborted) { ret = execute_script(mod); if (ret != 0) break; if (m->aborted) break; if (m->poll_interval <= 0) break; struct timeval now; if (gettimeofday(&now, NULL) < 0) { LOG_ERRNO("failed to get current time"); break; } struct timeval poll_interval = { .tv_sec = m->poll_interval / 1000, .tv_usec = (m->poll_interval % 1000) * 1000, }; struct timeval timeout; timeradd(&now, &poll_interval, &timeout); while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; struct timeval now; if (gettimeofday(&now, NULL) < 0) { LOG_ERRNO("failed to get current time"); keep_going = false; break; } if (!timercmp(&now, &timeout, <)) { /* We’ve reached the timeout, it’s time to execute the script again */ break; } struct timeval time_left; timersub(&timeout, &now, &time_left); int r = poll(fds, 1, time_left.tv_sec * 1000 + time_left.tv_usec / 1000); if (r < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); keep_going = false; break; } if (r > 0) { m->aborted = true; break; } } } return ret; } static struct module * script_new(char *path, size_t argc, const char *const argv[static argc], int poll_interval, struct particle *_content) { struct private *m = calloc(1, sizeof(*m)); m->path = path; m->content = _content; m->argc = argc; m->argv = malloc(argc * sizeof(m->argv[0])); for (size_t i = 0; i < argc; i++) m->argv[i] = strdup(argv[i]); m->poll_interval = poll_interval; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *path_node = yml_get_value(node, "path"); const struct yml_node *args = yml_get_value(node, "args"); const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *poll_interval = yml_get_value(node, "poll-interval"); size_t argc = args != NULL ? yml_list_length(args) : 0; const char *argv[argc]; if (args != NULL) { size_t i = 0; for (struct yml_list_iter iter = yml_list_iter(args); iter.node != NULL; yml_list_next(&iter), i++) { argv[i] = yml_value_as_string(iter.node); } } const char *yml_path = yml_value_as_string(path_node); char *path = NULL; if (yml_path[0] == '~' && yml_path[1] == '/') { const char *home_dir = getenv("HOME"); if (home_dir == NULL) { LOG_ERRNO("failed to expand '~"); return NULL; } if (asprintf(&path, "%s/%s", home_dir, yml_path + 2) < 0) { LOG_ERRNO("failed to expand '~"); return NULL; } } else path = strdup(yml_path); return script_new(path, argc, argv, poll_interval != NULL ? yml_value_as_int(poll_interval) : 0, conf_to_particle(c, inherited)); } static bool conf_verify_path(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_string(chain, node)) return false; const char *path = yml_value_as_string(node); const bool is_tilde = path[0] == '~' && path[1] == '/'; const bool is_absolute = path[0] == '/'; if (!is_tilde && !is_absolute) { LOG_ERR("%s: path must either be absolute, or begin with '~/'", conf_err_prefix(chain, node)); return false; } return true; } static bool conf_verify_args(keychain_t *chain, const struct yml_node *node) { return conf_verify_list(chain, node, &conf_verify_string); } static bool conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node) { if (!conf_verify_unsigned(chain, node)) return false; if (yml_value_as_int(node) < min_poll_interval) { LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval); return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"path", true, &conf_verify_path}, {"args", false, &conf_verify_args}, {"poll-interval", false, &conf_verify_poll_interval}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_script_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_script_iface"))); #endif yambar-1.11.0/modules/sway-xkb.c000066400000000000000000000250241460770427600164460ustar00rootroot00000000000000#include #include #define LOG_MODULE "sway-xkb" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particles/dynlist.h" #include "../plugin.h" #include "i3-common.h" #include "i3-ipc.h" #define max(x, y) ((x) > (y) ? (x) : (y)) struct input { bool exists; char *identifier; char *layout; }; struct private { struct particle *template; int left_spacing; int right_spacing; size_t num_inputs; size_t num_existing_inputs; struct input *inputs; bool dirty; }; static void free_input(struct input *input) { free(input->identifier); free(input->layout); } static void destroy(struct module *mod) { struct private *m = mod->private; m->template->destroy(m->template); for (size_t i = 0; i < m->num_inputs; i++) free_input(&m->inputs[i]); free(m->inputs); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "sway-xkb"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); assert(m->num_existing_inputs <= m->num_inputs); struct exposable *particles[max(m->num_existing_inputs, 1)]; for (size_t i = 0, j = 0; i < m->num_inputs; i++) { const struct input *input = &m->inputs[i]; if (!input->exists) continue; struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "id", input->identifier), tag_new_string(mod, "layout", input->layout), }, .count = 2, }; particles[j++] = m->template->instantiate(m->template, &tags); tag_set_destroy(&tags); } mtx_unlock(&mod->lock); return dynlist_exposable_new(particles, m->num_existing_inputs, m->left_spacing, m->right_spacing); } static bool handle_input_reply(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; assert(m->num_existing_inputs == 0); for (size_t i = 0; i < json_object_array_length(json); i++) { struct json_object *obj = json_object_array_get_idx(json, i); struct json_object *identifier; if (!json_object_object_get_ex(obj, "identifier", &identifier)) return false; const char *id = json_object_get_string(identifier); struct json_object *type; if (!json_object_object_get_ex(obj, "type", &type)) return false; if (strcmp(json_object_get_string(type), "keyboard") != 0) { LOG_DBG("ignoring non-keyboard input '%s'", id); continue; } struct input *input = NULL; for (size_t i = 0; i < m->num_inputs; i++) { struct input *maybe_input = &m->inputs[i]; if (strcmp(maybe_input->identifier, id) == 0 && !maybe_input->exists) { input = maybe_input; LOG_DBG("adding: %s", id); mtx_lock(&mod->lock); input->exists = true; m->num_existing_inputs++; mtx_unlock(&mod->lock); break; } } if (input == NULL) { LOG_DBG("ignoring xkb_layout change for input '%s'", id); continue; } /* Get current/active layout */ struct json_object *layout; if (!json_object_object_get_ex(obj, "xkb_active_layout_name", &layout)) return false; const char *new_layout_str = json_object_get_string(layout); mtx_lock(&mod->lock); assert(input != NULL); free(input->layout); input->layout = strdup(new_layout_str); m->dirty = true; mtx_unlock(&mod->lock); } return true; } static bool handle_input_event(int sock, int type, const struct json_object *json, void *_mod) { struct module *mod = _mod; struct private *m = mod->private; struct json_object *change; if (!json_object_object_get_ex(json, "change", &change)) return false; const char *change_str = json_object_get_string(change); bool is_layout = strcmp(change_str, "xkb_layout") == 0; bool is_removed = strcmp(change_str, "removed") == 0; bool is_added = strcmp(change_str, "added") == 0; if (!is_layout && !is_removed && !is_added) return true; struct json_object *obj; if (!json_object_object_get_ex(json, "input", &obj)) return false; struct json_object *identifier; if (!json_object_object_get_ex(obj, "identifier", &identifier)) return false; const char *id = json_object_get_string(identifier); struct json_object *input_type; if (!json_object_object_get_ex(obj, "type", &input_type)) return false; if (strcmp(json_object_get_string(input_type), "keyboard") != 0) { LOG_DBG("ignoring non-keyboard input '%s'", id); return true; } struct input *input = NULL; for (size_t i = 0; i < m->num_inputs; i++) { struct input *maybe_input = &m->inputs[i]; if (strcmp(maybe_input->identifier, id) == 0) { input = maybe_input; break; } } if (input == NULL) { LOG_DBG("ignoring xkb_layout change for input '%s'", id); return true; } if (is_removed) { if (input->exists) { LOG_DBG("removing: %s", id); mtx_lock(&mod->lock); input->exists = false; m->num_existing_inputs--; m->dirty = true; mtx_unlock(&mod->lock); } return true; } if (is_added) { if (!input->exists) { LOG_DBG("adding: %s", id); mtx_lock(&mod->lock); input->exists = true; m->num_existing_inputs++; m->dirty = true; mtx_unlock(&mod->lock); } /* “fallthrough”, to query current/active layout */ } /* Get current/active layout */ struct json_object *layout; if (!json_object_object_get_ex(obj, "xkb_active_layout_name", &layout)) return false; const char *new_layout_str = json_object_get_string(layout); mtx_lock(&mod->lock); assert(input != NULL); free(input->layout); input->layout = strdup(new_layout_str); m->dirty = true; mtx_unlock(&mod->lock); return true; } static void burst_done(void *_mod) { struct module *mod = _mod; struct private *m = mod->private; if (m->dirty) { m->dirty = false; mod->bar->refresh(mod->bar); } } static int run(struct module *mod) { if (getenv("SWAYSOCK") == NULL) { LOG_ERR("sway does not appear to be running"); return 1; } struct sockaddr_un addr; if (!i3_get_socket_address(&addr)) return 1; int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sock == -1) { LOG_ERRNO("failed to create UNIX socket"); return 1; } int r = connect(sock, (const struct sockaddr *)&addr, sizeof(addr)); if (r == -1) { LOG_ERRNO("failed to connect to i3 socket"); close(sock); return 1; } i3_send_pkg(sock, 100 /* IPC_GET_INPUTS */, NULL); i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"input\"]"); static const struct i3_ipc_callbacks callbacks = { .burst_done = &burst_done, .reply_inputs = &handle_input_reply, .event_input = &handle_input_event, }; bool ret = i3_receive_loop(mod->abort_fd, sock, &callbacks, mod); close(sock); return ret ? 0 : 1; } static struct module * sway_xkb_new(struct particle *template, const char *identifiers[], size_t num_identifiers, int left_spacing, int right_spacing) { struct private *m = calloc(1, sizeof(*m)); m->template = template; m->left_spacing = left_spacing; m->right_spacing = right_spacing; m->num_inputs = num_identifiers; m->inputs = calloc(num_identifiers, sizeof(m->inputs[0])); for (size_t i = 0; i < num_identifiers; i++) { m->inputs[i].identifier = strdup(identifiers[i]); m->inputs[i].layout = strdup(""); } struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *spacing = yml_get_value(node, "spacing"); const struct yml_node *left_spacing = yml_get_value(node, "left-spacing"); const struct yml_node *right_spacing = yml_get_value(node, "right-spacing"); int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0; int right = spacing != NULL ? yml_value_as_int(spacing) : right_spacing != NULL ? yml_value_as_int(right_spacing) : 0; const struct yml_node *ids = yml_get_value(node, "identifiers"); const size_t num_ids = yml_list_length(ids); const char *identifiers[num_ids]; size_t i = 0; for (struct yml_list_iter it = yml_list_iter(ids); it.node != NULL; yml_list_next(&it), i++) { identifiers[i] = yml_value_as_string(it.node); } return sway_xkb_new(conf_to_particle(c, inherited), identifiers, num_ids, left, right); } static bool verify_identifiers(keychain_t *chain, const struct yml_node *node) { if (!yml_is_list(node)) { LOG_ERR("%s: identifiers must be a list of strings", conf_err_prefix(chain, node)); return false; } for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) { if (!conf_verify_string(chain, it.node)) return false; } return true; } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, {"identifiers", true, &verify_identifiers}, MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_sway_xkb_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_sway_xkb_iface"))); #endif yambar-1.11.0/modules/xkb.c000066400000000000000000000454141460770427600154720ustar00rootroot00000000000000#include #include #include #include #include #include #include #define LOG_MODULE "xkb" #define LOG_ENABLE_DBG 0 #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" #include "../xcb.h" struct layout { char *name; char *symbol; }; struct layouts { size_t count; struct layout *layouts; }; struct indicators { size_t count; char **names; }; struct private { struct particle *label; struct indicators indicators; struct layouts layouts; size_t current; bool caps_lock; bool num_lock; bool scroll_lock; }; static void free_layouts(struct layouts layouts) { for (size_t i = 0; i < layouts.count; i++) { free(layouts.layouts[i].name); free(layouts.layouts[i].symbol); } free(layouts.layouts); } static void free_indicators(struct indicators indicators) { for (size_t i = 0; i < indicators.count; i++) free(indicators.names[i]); free(indicators.names); } static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free_layouts(m->layouts); free_indicators(m->indicators); free(m); module_default_destroy(mod); } static const char * description(const struct module *mod) { return "xkb"; } static struct exposable * content(struct module *mod) { const struct private *m = mod->private; mtx_lock(&mod->lock); const char *name = ""; const char *symbol = ""; if (m->current < m->layouts.count) { name = m->layouts.layouts[m->current].name; symbol = m->layouts.layouts[m->current].symbol; } struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "name", name), tag_new_string(mod, "symbol", symbol), tag_new_bool(mod, "caps_lock", m->caps_lock), tag_new_bool(mod, "num_lock", m->num_lock), tag_new_bool(mod, "scroll_lock", m->scroll_lock), }, .count = 5, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static bool xkb_enable(xcb_connection_t *conn) { xcb_generic_error_t *err; xcb_xkb_use_extension_cookie_t cookie = xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); xcb_xkb_use_extension_reply_t *reply = xcb_xkb_use_extension_reply(conn, cookie, &err); if (err != NULL) { LOG_ERR("failed to query for XKB extension: %s", xcb_error(err)); free(err); free(reply); return false; } if (!reply->supported) { LOG_ERR("XKB extension is not supported"); free(reply); return false; } free(reply); return true; } static int get_xkb_event_base(xcb_connection_t *conn) { const struct xcb_query_extension_reply_t *reply = xcb_get_extension_data(conn, &xcb_xkb_id); if (reply == NULL) { LOG_ERR("failed to get XKB extension data"); return -1; } if (!reply->present) { LOG_ERR("XKB not present"); return -1; } return reply->first_event; } static bool get_layouts_and_indicators(xcb_connection_t *conn, struct layouts *layouts, struct indicators *indicators) { xcb_generic_error_t *err; xcb_xkb_get_names_cookie_t cookie = xcb_xkb_get_names(conn, XCB_XKB_ID_USE_CORE_KBD, XCB_XKB_NAME_DETAIL_GROUP_NAMES | XCB_XKB_NAME_DETAIL_SYMBOLS | XCB_XKB_NAME_DETAIL_INDICATOR_NAMES); xcb_xkb_get_names_reply_t *reply = xcb_xkb_get_names_reply(conn, cookie, &err); if (err != NULL) { LOG_ERR("failed to get layouts and indicators: %s", xcb_error(err)); free(err); return false; } xcb_xkb_get_names_value_list_t vlist; void *buf = xcb_xkb_get_names_value_list(reply); xcb_xkb_get_names_value_list_unpack(buf, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames, reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &vlist); /* Number of groups (aka layouts) */ layouts->count = xcb_xkb_get_names_value_list_groups_length(reply, &vlist); layouts->layouts = calloc(layouts->count, sizeof(layouts->layouts[0])); /* Number of indicators */ indicators->count = xcb_xkb_get_names_value_list_indicator_names_length(reply, &vlist); indicators->names = calloc(indicators->count, sizeof(indicators->names[0])); xcb_get_atom_name_cookie_t symbols_name_cookie = xcb_get_atom_name(conn, vlist.symbolsName); xcb_get_atom_name_cookie_t group_name_cookies[layouts->count]; for (size_t i = 0; i < layouts->count; i++) group_name_cookies[i] = xcb_get_atom_name(conn, vlist.groups[i]); xcb_get_atom_name_cookie_t indicator_cookies[indicators->count]; for (size_t i = 0; i < indicators->count; i++) indicator_cookies[i] = xcb_get_atom_name(conn, vlist.indicatorNames[i]); char *symbols = NULL; /* Get layout short names (e.g. "us") */ xcb_get_atom_name_reply_t *atom_name = xcb_get_atom_name_reply(conn, symbols_name_cookie, &err); if (err != NULL) { LOG_ERR("failed to get 'symbols' atom name: %s", xcb_error(err)); free(err); goto err; } symbols = strndup(xcb_get_atom_name_name(atom_name), xcb_get_atom_name_name_length(atom_name)); LOG_DBG("symbols: %s", symbols); free(atom_name); /* Get layout long names (e.g. "English (US)") */ for (size_t i = 0; i < layouts->count; i++) { atom_name = xcb_get_atom_name_reply(conn, group_name_cookies[i], &err); if (err != NULL) { LOG_ERR("failed to get 'group' atom name: %s", xcb_error(err)); free(err); goto err; } layouts->layouts[i].name = strndup(xcb_get_atom_name_name(atom_name), xcb_get_atom_name_name_length(atom_name)); LOG_DBG("layout #%zd: long name: %s", i, layouts->layouts[i].name); free(atom_name); } /* Indicator names e.g. "Caps Lock", "Num Lock" */ for (size_t i = 0; i < indicators->count; i++) { atom_name = xcb_get_atom_name_reply(conn, indicator_cookies[i], &err); if (err != NULL) { LOG_ERR("failed to get 'indicator' atom name: %s", xcb_error(err)); free(err); goto err; } indicators->names[i] = strndup(xcb_get_atom_name_name(atom_name), xcb_get_atom_name_name_length(atom_name)); LOG_DBG("indicator #%zd: %s", i, indicators->names[i]); free(atom_name); } /* e.g. pc+us+inet(evdev)+group(..) */ size_t layout_idx = 0; for (char *tok_ctx = NULL, *tok = strtok_r(symbols, "+", &tok_ctx); tok != NULL; tok = strtok_r(NULL, "+", &tok_ctx)) { char *fname = strtok(tok, "()"); char *section __attribute__((unused)) = strtok(NULL, "()"); /* Not sure why some layouts have a ":n" suffix (where * 'n' is a number, e.g. "us:2") */ fname = strtok(fname, ":"); /* Assume all language layouts are two-letters */ if (strlen(fname) != 2) continue; /* But make sure to ignore "pc" :) */ if (strcmp(tok, "pc") == 0) continue; if (layout_idx >= layouts->count) { LOG_ERR("layout vs group name count mismatch: %zd > %zd", layout_idx + 1, layouts->count); goto err; } char *sym = strdup(fname); layouts->layouts[layout_idx++].symbol = sym; LOG_DBG("layout #%zd: short name: %s", layout_idx - 1, sym); } if (layout_idx != layouts->count) { LOG_ERR("layout vs group name count mismatch: %zd != %zd", layout_idx, layouts->count); goto err; } free(symbols); free(reply); return true; err: free(symbols); free(reply); free_layouts(*layouts); free_indicators(*indicators); return false; } static int get_current_layout(xcb_connection_t *conn) { xcb_generic_error_t *err; xcb_xkb_get_state_cookie_t cookie = xcb_xkb_get_state(conn, XCB_XKB_ID_USE_CORE_KBD); xcb_xkb_get_state_reply_t *reply = xcb_xkb_get_state_reply(conn, cookie, &err); if (err != NULL) { LOG_ERR("failed to get XKB state: %s", xcb_error(err)); return -1; } int ret = reply->group; free(reply); return ret; } static uint32_t get_indicator_state(xcb_connection_t *conn) { xcb_generic_error_t *err; xcb_xkb_get_indicator_state_cookie_t cookie = xcb_xkb_get_indicator_state(conn, XCB_XKB_ID_USE_CORE_KBD); xcb_xkb_get_indicator_state_reply_t *reply = xcb_xkb_get_indicator_state_reply(conn, cookie, &err); if (err != NULL) { LOG_ERR("failed to get indicator state: %s", xcb_error(err)); free(err); return (uint32_t)-1; } uint32_t state = reply->state; LOG_DBG("indicator state: 0x%08x", state); free(reply); return state; } static bool register_for_events(xcb_connection_t *conn) { xcb_void_cookie_t cookie = xcb_xkb_select_events_checked(conn, XCB_XKB_ID_USE_CORE_KBD, (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | XCB_XKB_EVENT_TYPE_STATE_NOTIFY | XCB_XKB_EVENT_TYPE_MAP_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_MAP_NOTIFY | XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY), 0, 0, NULL); xcb_generic_error_t *err = xcb_request_check(conn, cookie); if (err != NULL) { LOG_ERR("failed to register for events: %s", xcb_error(err)); return false; } return true; } static bool event_loop(struct module *mod, xcb_connection_t *conn, int xkb_event_base) { const struct bar *bar = mod->bar; struct private *m = mod->private; bool ret = false; bool has_error = false; const int xcb_fd = xcb_get_file_descriptor(conn); assert(xcb_fd >= 0); while (!has_error) { struct pollfd pfds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = xcb_fd, .events = POLLIN | POLLHUP}}; /* Use poll() since xcb_wait_for_events() doesn't return on signals */ if (poll(pfds, sizeof(pfds) / sizeof(pfds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (pfds[0].revents & POLLIN) { ret = true; break; } if (pfds[1].revents & POLLHUP) { LOG_WARN("I/O error, server disconnect?"); break; } assert(pfds[1].revents & POLLIN && "POLLIN not set"); /* * Note: poll() might have returned *before* the entire event * has been received, and thus we might block here. Hopefully * not for long though... */ for (xcb_generic_event_t *_evt = xcb_wait_for_event(conn); _evt != NULL; _evt = xcb_poll_for_event(conn)) { if (_evt->response_type != xkb_event_base) { LOG_WARN("non-XKB event ignored: %d", _evt->response_type); free(_evt); continue; } switch (_evt->pad0) { default: LOG_WARN("unimplemented XKB event: %d", _evt->pad0); break; case XCB_XKB_NEW_KEYBOARD_NOTIFY: { int current = get_current_layout(conn); if (current == -1) { has_error = true; break; } struct layouts layouts; struct indicators indicators; if (!get_layouts_and_indicators(conn, &layouts, &indicators)) { has_error = true; break; } if (current < layouts.count) { mtx_lock(&mod->lock); free_layouts(m->layouts); m->current = current; m->layouts = layouts; m->indicators = indicators; mtx_unlock(&mod->lock); bar->refresh(bar); } else { /* Can happen while transitioning to a new map */ free_layouts(layouts); free_indicators(indicators); } break; } case XCB_XKB_STATE_NOTIFY: { const xcb_xkb_state_notify_event_t *evt = (const xcb_xkb_state_notify_event_t *)_evt; if (evt->changed & XCB_XKB_STATE_PART_GROUP_STATE) { mtx_lock(&mod->lock); m->current = evt->group; mtx_unlock(&mod->lock); bar->refresh(bar); } break; } case XCB_XKB_MAP_NOTIFY: LOG_WARN("map event unimplemented"); break; case XCB_XKB_INDICATOR_STATE_NOTIFY: { const xcb_xkb_indicator_state_notify_event_t *evt = (const xcb_xkb_indicator_state_notify_event_t *)_evt; #if 0 size_t idx = __builtin_ctz(evt->stateChanged); LOG_ERR("%zu", idx); if (idx < m->indicators.count) LOG_ERR("%s", m->indicators.names[idx]); #endif /* TODO: bit count evt->stateChanged instead */ bool need_refresh = false; for (size_t i = 0; i < m->indicators.count; i++) { bool changed = (evt->stateChanged >> i) & 1; if (!changed) continue; bool enabled = (evt->state >> i) & 1; LOG_DBG("%s: %s", m->indicators.names[i], enabled ? "enabled" : "disabled"); const char *name = m->indicators.names[i]; bool is_caps = strcasecmp(name, "caps lock") == 0; bool is_num = strcasecmp(name, "num lock") == 0; bool is_scroll = strcasecmp(name, "scroll lock") == 0; if (is_caps || is_num || is_scroll) { mtx_lock(&mod->lock); if (is_caps) m->caps_lock = enabled; else if (is_num) m->num_lock = enabled; else if (is_scroll) m->scroll_lock = enabled; mtx_unlock(&mod->lock); need_refresh = true; } } if (need_refresh) bar->refresh(bar); break; } } free(_evt); } } return ret; } static bool talk_to_xkb(struct module *mod, xcb_connection_t *conn) { struct private *m = mod->private; if (!xkb_enable(conn)) return false; if (!register_for_events(conn)) return false; int xkb_event_base = get_xkb_event_base(conn); if (xkb_event_base == -1) return false; int current = get_current_layout(conn); if (current == -1) return false; /* Bitmask, one bit for every indicator available */ uint32_t indicator_state = get_indicator_state(conn); if (indicator_state == (uint32_t)-1) return false; struct layouts layouts; struct indicators indicators; if (!get_layouts_and_indicators(conn, &layouts, &indicators)) return false; if (current >= layouts.count) { LOG_ERR("current layout index: %d >= %zd", current, layouts.count); free_layouts(layouts); free_indicators(indicators); return false; } bool caps_lock = false, num_lock = false, scroll_lock = false; /* Lookup initial state of caps-, num- and scroll lock */ for (size_t i = 0; i < indicators.count; i++) { const char *name = indicators.names[i]; bool enabled = (indicator_state >> i) & 1; bool is_caps = strcasecmp(name, "caps lock") == 0; bool is_num = strcasecmp(name, "num lock") == 0; bool is_scroll = strcasecmp(name, "scroll lock") == 0; if (!(is_caps || is_num || is_scroll)) continue; if (is_caps) caps_lock = enabled; else if (is_num) num_lock = enabled; else if (is_scroll) scroll_lock = enabled; LOG_DBG("%s: %s", name, enabled ? "enabled" : "disabled"); } { char buf[512]; size_t idx = 0; for (size_t i = 0; i < layouts.count; i++) { idx += snprintf(&buf[idx], sizeof(buf) - idx, "%s%s (%s)%s", i == m->current ? "*" : "", layouts.layouts[i].name, layouts.layouts[i].symbol, i + 1 < layouts.count ? ", " : ""); } LOG_INFO("layouts: %s, caps-lock:%s, num-lock:%s, scroll-lock:%s", buf, caps_lock ? "on" : "off", num_lock ? "on" : "off", scroll_lock ? "on" : "off"); } mtx_lock(&mod->lock); m->layouts = layouts; m->current = current; m->indicators = indicators; m->caps_lock = caps_lock; m->num_lock = num_lock; m->scroll_lock = scroll_lock; mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); return event_loop(mod, conn, xkb_event_base); } static int run(struct module *mod) { xcb_connection_t *conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(conn) > 0) { LOG_ERR("failed to connect to X server"); xcb_disconnect(conn); return EXIT_FAILURE; } int ret = talk_to_xkb(mod, conn) ? EXIT_SUCCESS : EXIT_FAILURE; xcb_disconnect(conn); return ret; } static struct module * xkb_new(struct particle *label) { struct private *m = calloc(1, sizeof(*m)); m->label = label; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); return xkb_new(conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_xkb_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_xkb_iface"))); #endif yambar-1.11.0/modules/xwindow.c000066400000000000000000000234161460770427600164030ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "xwindow" #include "../bar/bar.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../plugin.h" #include "../xcb.h" struct private { /* Accessed from bar thread only */ struct particle *label; /* Accessed from both our thread, and the bar thread */ char *application; char *title; /* Accessed from our thread only */ xcb_connection_t *conn; xcb_window_t root_win; xcb_window_t monitor_win; xcb_window_t active_win; }; static const char * description(const struct module *mod) { return "xwindow"; } static void update_active_window(struct private *m) { if (m->active_win != 0) { xcb_void_cookie_t c = xcb_change_window_attributes_checked(m->conn, m->active_win, XCB_CW_EVENT_MASK, (const uint32_t[]){XCB_EVENT_MASK_NO_EVENT}); xcb_generic_error_t *e = xcb_request_check(m->conn, c); if (e != NULL) { LOG_DBG("failed to de-register events on previous active window: %s", xcb_error(e)); free(e); } m->active_win = 0; } xcb_get_property_cookie_t c = xcb_get_property(m->conn, 0, m->root_win, _NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 0, 32); xcb_generic_error_t *e; xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e); if (e != NULL) { LOG_ERR("failed to get active window ID: %s", xcb_error(e)); free(e); free(r); return; } if (xcb_get_property_value_length(r) != sizeof(m->active_win)) { free(r); return; } assert(sizeof(m->active_win) == xcb_get_property_value_length(r)); memcpy(&m->active_win, xcb_get_property_value(r), sizeof(m->active_win)); free(r); if (m->active_win != 0) { xcb_change_window_attributes(m->conn, m->active_win, XCB_CW_EVENT_MASK, (const uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE}); } } static void update_application(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); free(m->application); m->application = NULL; mtx_unlock(&mod->lock); if (m->active_win == 0) return; xcb_get_property_cookie_t c = xcb_get_property(m->conn, 0, m->active_win, _NET_WM_PID, XCB_ATOM_CARDINAL, 0, 32); xcb_generic_error_t *e; xcb_get_property_reply_t *r = xcb_get_property_reply(m->conn, c, &e); if (e != NULL) { LOG_ERR("failed to get _NET_WM_PID: %s", xcb_error(e)); free(e); free(r); return; } if (xcb_get_property_value_length(r) == 0) { free(r); return; } uint32_t pid; if (xcb_get_property_value_length(r) != sizeof(pid)) { free(r); return; } memcpy(&pid, xcb_get_property_value(r), sizeof(pid)); free(r); char path[1024]; snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); int fd = open(path, O_RDONLY); if (fd == -1) return; char cmd[1024] = {0}; ssize_t bytes = read(fd, cmd, sizeof(cmd) - 1); close(fd); if (bytes == -1) return; mtx_lock(&mod->lock); m->application = strdup(basename(cmd)); mtx_unlock(&mod->lock); } static void update_title(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); free(m->title); m->title = NULL; mtx_unlock(&mod->lock); if (m->active_win == 0) return; xcb_get_property_cookie_t c1 = xcb_get_property(m->conn, 0, m->active_win, _NET_WM_VISIBLE_NAME, UTF8_STRING, 0, 1000); xcb_get_property_cookie_t c2 = xcb_get_property(m->conn, 0, m->active_win, _NET_WM_NAME, UTF8_STRING, 0, 1000); xcb_get_property_cookie_t c3 = xcb_get_property(m->conn, 0, m->active_win, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1000); xcb_generic_error_t *e1, *e2, *e3; xcb_get_property_reply_t *r1 = xcb_get_property_reply(m->conn, c1, &e1); xcb_get_property_reply_t *r2 = xcb_get_property_reply(m->conn, c2, &e2); xcb_get_property_reply_t *r3 = xcb_get_property_reply(m->conn, c3, &e3); const char *title; int title_len; if (e1 == NULL && xcb_get_property_value_length(r1) > 0) { title = xcb_get_property_value(r1); title_len = xcb_get_property_value_length(r1); } else if (e2 == NULL && xcb_get_property_value_length(r2) > 0) { title = xcb_get_property_value(r2); title_len = xcb_get_property_value_length(r2); } else if (e3 == NULL && xcb_get_property_value_length(r3) > 0) { title = xcb_get_property_value(r3); title_len = xcb_get_property_value_length(r3); } else { title = NULL; title_len = 0; } if (title_len > 0) { mtx_lock(&mod->lock); m->title = malloc(title_len + 1); memcpy(m->title, title, title_len); m->title[title_len] = '\0'; mtx_unlock(&mod->lock); } free(e1); free(e2); free(e3); free(r1); free(r2); free(r3); } static int run(struct module *mod) { struct private *m = mod->private; int default_screen; m->conn = xcb_connect(NULL, &default_screen); if (xcb_connection_has_error(m->conn) > 0) { LOG_ERR("failed to connect to X"); xcb_disconnect(m->conn); return 1; } xcb_screen_t *screen = xcb_aux_get_screen(m->conn, default_screen); m->root_win = screen->root; /* Need a window(?) to be able to process events */ m->monitor_win = xcb_generate_id(m->conn); xcb_create_window(m->conn, screen->root_depth, m->monitor_win, screen->root, -1, -1, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, XCB_CW_OVERRIDE_REDIRECT, (const uint32_t[]){1}); xcb_map_window(m->conn, m->monitor_win); /* Register for property changes on root window. This allows us to * catch e.g. window switches etc */ xcb_change_window_attributes(m->conn, screen->root, XCB_CW_EVENT_MASK, (const uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE}); xcb_flush(m->conn); update_active_window(m); update_application(mod); update_title(mod); mod->bar->refresh(mod->bar); int ret = 1; int xcb_fd = xcb_get_file_descriptor(m->conn); while (true) { struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}, {.fd = xcb_fd, .events = POLLIN}}; if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) < 0) { if (errno == EINTR) continue; LOG_ERRNO("failed to poll"); break; } if (fds[0].revents & POLLIN) { ret = 0; break; } for (xcb_generic_event_t *_e = xcb_wait_for_event(m->conn); _e != NULL; _e = xcb_poll_for_event(m->conn)) { switch (XCB_EVENT_RESPONSE_TYPE(_e)) { case 0: LOG_ERR("XCB: %s", xcb_error((const xcb_generic_error_t *)_e)); break; case XCB_PROPERTY_NOTIFY: { xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)_e; if (e->atom == _NET_ACTIVE_WINDOW || e->atom == _NET_CURRENT_DESKTOP) { /* Active desktop and/or window changed */ update_active_window(m); update_application(mod); update_title(mod); mod->bar->refresh(mod->bar); } else if (e->atom == _NET_WM_VISIBLE_NAME || e->atom == _NET_WM_NAME || e->atom == XCB_ATOM_WM_NAME) { assert(e->window == m->active_win); update_title(mod); mod->bar->refresh(mod->bar); } break; } } free(_e); } } xcb_destroy_window(m->conn, m->monitor_win); xcb_disconnect(m->conn); return ret; } static struct exposable * content(struct module *mod) { struct private *m = mod->private; mtx_lock(&mod->lock); struct tag_set tags = { .tags = (struct tag *[]){ tag_new_string(mod, "application", m->application), tag_new_string(mod, "title", m->title), }, .count = 2, }; mtx_unlock(&mod->lock); struct exposable *exposable = m->label->instantiate(m->label, &tags); tag_set_destroy(&tags); return exposable; } static void destroy(struct module *mod) { struct private *m = mod->private; m->label->destroy(m->label); free(m->application); free(m->title); free(m); module_default_destroy(mod); } static struct module * xwindow_new(struct particle *label) { struct private *m = calloc(1, sizeof(*m)); m->label = label; struct module *mod = module_common_new(); mod->private = m; mod->run = &run; mod->destroy = &destroy; mod->content = &content; mod->description = &description; return mod; } static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *c = yml_get_value(node, "content"); return xwindow_new(conf_to_particle(c, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { MODULE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct module_iface module_xwindow_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct module_iface iface __attribute__((weak, alias("module_xwindow_iface"))); #endif yambar-1.11.0/particle.c000066400000000000000000000222601460770427600150330ustar00rootroot00000000000000#include "particle.h" #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE "particle" #define LOG_ENABLE_DBG 0 #include "bar/bar.h" #include "log.h" void particle_default_destroy(struct particle *particle) { if (particle->deco != NULL) particle->deco->destroy(particle->deco); fcft_destroy(particle->font); for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) free(particle->on_click_templates[i]); free(particle); } struct particle * particle_common_new(int left_margin, int right_margin, char **on_click_templates, struct fcft_font *font, enum font_shaping font_shaping, pixman_color_t foreground, struct deco *deco) { struct particle *p = calloc(1, sizeof(*p)); p->left_margin = left_margin; p->right_margin = right_margin; p->foreground = foreground; p->font = font; p->font_shaping = font_shaping; p->deco = deco; if (on_click_templates != NULL) { for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { if (on_click_templates[i] != NULL) { p->have_on_click_template = true; p->on_click_templates[i] = on_click_templates[i]; } } } return p; } void exposable_default_destroy(struct exposable *exposable) { for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) free(exposable->on_click[i]); free(exposable); } void exposable_render_deco(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { const struct deco *deco = exposable->particle->deco; if (deco != NULL) deco->expose(deco, pix, x, y, exposable->width, height); } static bool push_argv(char ***argv, size_t *size, char *arg, size_t *argc) { if (arg != NULL && arg[0] == '%') return true; if (*argc >= *size) { size_t new_size = *size > 0 ? 2 * *size : 10; char **new_argv = realloc(*argv, new_size * sizeof(new_argv[0])); if (new_argv == NULL) return false; *argv = new_argv; *size = new_size; } (*argv)[(*argc)++] = arg; return true; } static bool tokenize_cmdline(char *cmdline, char ***argv) { *argv = NULL; size_t argv_size = 0; bool first_token_is_quoted = cmdline[0] == '"' || cmdline[0] == '\''; char delim = first_token_is_quoted ? cmdline[0] : ' '; char *p = first_token_is_quoted ? &cmdline[1] : &cmdline[0]; size_t idx = 0; while (*p != '\0') { char *end = strchr(p, delim); if (end == NULL) { if (delim != ' ') { LOG_ERR("unterminated %s quote\n", delim == '"' ? "double" : "single"); free(*argv); return false; } if (!push_argv(argv, &argv_size, p, &idx) || !push_argv(argv, &argv_size, NULL, &idx)) { goto err; } else return true; } *end = '\0'; if (!push_argv(argv, &argv_size, p, &idx)) goto err; p = end + 1; while (*p == delim) p++; while (*p == ' ') p++; if (*p == '"' || *p == '\'') { delim = *p; p++; } else delim = ' '; } if (!push_argv(argv, &argv_size, NULL, &idx)) goto err; return true; err: free(*argv); return false; } void exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG static const char *button_name[] = { [MOUSE_BTN_NONE] = "none", [MOUSE_BTN_LEFT] = "left", [MOUSE_BTN_MIDDLE] = "middle", [MOUSE_BTN_RIGHT] = "right", [MOUSE_BTN_COUNT] = "count", [MOUSE_BTN_WHEEL_UP] = "wheel-up", [MOUSE_BTN_WHEEL_DOWN] = "wheel-down", [MOUSE_BTN_PREVIOUS] = "previous", [MOUSE_BTN_NEXT] = "next", }; LOG_DBG("on_mouse: exposable=%p, event=%s, btn=%s, x=%d, y=%d (on-click=%s)", exposable, event == ON_MOUSE_MOTION ? "motion" : "click", button_name[btn], x, y, exposable->on_click[btn]); #endif /* If we have a handler, change cursor to a hand */ const char *cursor = (exposable->particle != NULL && exposable->particle->have_on_click_template) ? "hand2" : "left_ptr"; bar->set_cursor(bar, cursor); /* If this is a mouse click, and we have a handler, execute it */ if (exposable->on_click[btn] != NULL && event == ON_MOUSE_CLICK) { /* Need a writeable copy, whose scope *we* control */ char *cmd = strdup(exposable->on_click[btn]); LOG_DBG("cmd = \"%s\"", exposable->on_click[btn]); char **argv; if (!tokenize_cmdline(cmd, &argv)) { free(cmd); return; } pid_t pid = fork(); if (pid == -1) LOG_ERRNO("failed to run on_click handler (fork)"); else if (pid > 0) { /* Parent */ free(cmd); free(argv); int wstatus; if (waitpid(pid, &wstatus, 0) == -1) LOG_ERRNO("%s: failed to wait for on_click handler", exposable->on_click[btn]); if (WIFEXITED(wstatus)) { if (WEXITSTATUS(wstatus) != 0) LOG_ERRNO_P(WEXITSTATUS(wstatus), "%s: failed to execute", exposable->on_click[btn]); } else LOG_ERR("%s: did not exit normally", exposable->on_click[btn]); LOG_DBG("%s: launched", exposable->on_click[btn]); } else { /* * Use a pipe with O_CLOEXEC to communicate exec() failure * to parent process. * * If the child succeeds with exec(), the pipe is simply * closed. If it fails, we write the fail reason (errno) * to the pipe. The parent reads the pipe; if it receives * data, the child failed, else it succeeded. */ int pipe_fds[2]; if (pipe2(pipe_fds, O_CLOEXEC) == -1) { LOG_ERRNO("%s: failed to create pipe", cmd); free(cmd); return; } LOG_DBG("ARGV:"); for (size_t i = 0; argv[i] != NULL; i++) LOG_DBG(" #%zu: \"%s\" ", i, argv[i]); LOG_DBG("double forking"); switch (fork()) { case -1: close(pipe_fds[0]); close(pipe_fds[1]); LOG_ERRNO("failed to double fork"); _exit(errno); break; case 0: /* Child */ close(pipe_fds[0]); /* Close read end */ LOG_DBG("executing on-click handler: %s", cmd); sigset_t mask; sigemptyset(&mask); const struct sigaction sa = {.sa_handler = SIG_DFL}; if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0 || sigaction(SIGCHLD, &sa, NULL) < 0 || sigprocmask(SIG_SETMASK, &mask, NULL) < 0) { goto fail; } /* Redirect stdin/stdout/stderr to /dev/null */ int dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC); int dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC); if (dev_null_r == -1 || dev_null_w == -1) { LOG_ERRNO("/dev/null: failed to open"); goto fail; } if (dup2(dev_null_r, STDIN_FILENO) == -1 || dup2(dev_null_w, STDOUT_FILENO) == -1 || dup2(dev_null_w, STDERR_FILENO) == -1) { LOG_ERRNO("failed to redirect stdin/stdout/stderr"); goto fail; } execvp(argv[0], argv); fail: /* Signal failure to parent process */ (void)!write(pipe_fds[1], &errno, sizeof(errno)); close(pipe_fds[1]); _exit(errno); break; default: /* Parent */ close(pipe_fds[1]); /* Close write end */ int _errno = 0; ssize_t ret = read(pipe_fds[0], &_errno, sizeof(_errno)); close(pipe_fds[0]); if (ret == 0) { /* Pipe was closed - child succeeded with exec() */ _exit(0); } LOG_DBG("second pipe failed: %s (%d)", strerror(_errno), _errno); _exit(_errno); break; } } } } struct exposable * exposable_common_new(const struct particle *particle, const struct tag_set *tags) { struct exposable *exposable = calloc(1, sizeof(*exposable)); exposable->particle = particle; if (particle != NULL && particle->have_on_click_template) { tags_expand_templates(exposable->on_click, (const char **)particle->on_click_templates, MOUSE_BTN_COUNT, tags); } exposable->destroy = &exposable_default_destroy; exposable->on_mouse = &exposable_default_on_mouse; return exposable; } yambar-1.11.0/particle.h000066400000000000000000000060301460770427600150350ustar00rootroot00000000000000#pragma once #include #include #include "color.h" #include "decoration.h" #include "font-shaping.h" #include "tag.h" enum mouse_event { ON_MOUSE_MOTION, ON_MOUSE_CLICK, }; enum mouse_button { MOUSE_BTN_NONE, MOUSE_BTN_LEFT, MOUSE_BTN_MIDDLE, MOUSE_BTN_RIGHT, MOUSE_BTN_WHEEL_UP, MOUSE_BTN_WHEEL_DOWN, MOUSE_BTN_PREVIOUS, MOUSE_BTN_NEXT, MOUSE_BTN_COUNT, }; struct bar; struct particle { void *private; int left_margin, right_margin; bool have_on_click_template; char *on_click_templates[MOUSE_BTN_COUNT]; pixman_color_t foreground; struct fcft_font *font; enum font_shaping font_shaping; struct deco *deco; void (*destroy)(struct particle *particle); struct exposable *(*instantiate)(const struct particle *particle, const struct tag_set *tags); }; struct exposable { const struct particle *particle; void *private; int width; /* Should be set by begin_expose(), at latest */ char *on_click[MOUSE_BTN_COUNT]; void (*destroy)(struct exposable *exposable); int (*begin_expose)(struct exposable *exposable); void (*expose)(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height); void (*on_mouse)(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y); }; struct particle *particle_common_new(int left_margin, int right_margin, char *on_click_templates[], struct fcft_font *font, enum font_shaping font_shaping, pixman_color_t foreground, struct deco *deco); void particle_default_destroy(struct particle *particle); struct exposable *exposable_common_new(const struct particle *particle, const struct tag_set *tags); void exposable_default_destroy(struct exposable *exposable); void exposable_render_deco(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height); void exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y); /* List of attributes *all* particles implement */ #define PARTICLE_COMMON_ATTRS \ {"margin", false, &conf_verify_unsigned}, {"left-margin", false, &conf_verify_unsigned}, \ {"right-margin", false, &conf_verify_unsigned}, {"on-click", false, &conf_verify_on_click}, \ {"font", false, &conf_verify_font}, {"font-shaping", false, &conf_verify_font_shaping}, \ {"foreground", false, &conf_verify_color}, {"deco", false, &conf_verify_decoration}, \ { \ NULL, false, NULL \ } yambar-1.11.0/particles/000077500000000000000000000000001460770427600150505ustar00rootroot00000000000000yambar-1.11.0/particles/dynlist.c000066400000000000000000000066021460770427600167060ustar00rootroot00000000000000#include "dynlist.h" #include #include #define LOG_MODULE "dynlist" #include "../log.h" #include "../particle.h" struct private { int left_spacing; int right_spacing; struct exposable **exposables; size_t count; int *widths; }; static void dynlist_destroy(struct exposable *exposable) { struct private *e = exposable->private; for (size_t i = 0; i < e->count; i++) { struct exposable *ee = e->exposables[i]; ee->destroy(ee); } free(e->exposables); free(e->widths); free(e); free(exposable); } static int dynlist_begin_expose(struct exposable *exposable) { const struct private *e = exposable->private; exposable->width = 0; bool have_at_least_one = false; for (size_t i = 0; i < e->count; i++) { struct exposable *ee = e->exposables[i]; e->widths[i] = ee->begin_expose(ee); assert(e->widths[i] >= 0); if (e->widths[i] > 0) { exposable->width += e->left_spacing + e->widths[i] + e->right_spacing; have_at_least_one = true; } } if (have_at_least_one) exposable->width -= e->left_spacing + e->right_spacing; else assert(exposable->width == 0); return exposable->width; } static void dynlist_expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { const struct private *e = exposable->private; int left_spacing = e->left_spacing; int right_spacing = e->right_spacing; x -= left_spacing; for (size_t i = 0; i < e->count; i++) { const struct exposable *ee = e->exposables[i]; ee->expose(ee, pix, x + left_spacing, y, height); if (e->widths[i] > 0) x += left_spacing + e->widths[i] + right_spacing; } } static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { const struct private *e = exposable->private; if (exposable->on_click[btn] != NULL) { exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = /*p->left_margin;*/ 0; for (size_t i = 0; i < e->count; i++) { if (x >= px && x < px + e->exposables[i]->width) { if (e->exposables[i]->on_mouse != NULL) { e->exposables[i]->on_mouse(e->exposables[i], bar, event, btn, x - px, y); } return; } if (e->exposables[i]->width > 0) px += e->left_spacing + e->exposables[i]->width + e->right_spacing; } LOG_DBG("on_mouse missed all sub-particles"); exposable_default_on_mouse(exposable, bar, event, btn, x, y); } struct exposable * dynlist_exposable_new(struct exposable **exposables, size_t count, int left_spacing, int right_spacing) { struct private *e = calloc(1, sizeof(*e)); e->count = count; e->exposables = malloc(count * sizeof(e->exposables[0])); e->widths = calloc(count, sizeof(e->widths[0])); e->left_spacing = left_spacing; e->right_spacing = right_spacing; for (size_t i = 0; i < count; i++) e->exposables[i] = exposables[i]; struct exposable *exposable = exposable_common_new(NULL, NULL); exposable->private = e; exposable->destroy = &dynlist_destroy; exposable->begin_expose = &dynlist_begin_expose; exposable->expose = &dynlist_expose; exposable->on_mouse = &on_mouse; return exposable; } yambar-1.11.0/particles/dynlist.h000066400000000000000000000003271460770427600167110ustar00rootroot00000000000000#pragma once #include struct particle; struct exposable *dynlist_exposable_new(struct exposable **exposables, size_t count, int left_spacing, int right_spacing); yambar-1.11.0/particles/empty.c000066400000000000000000000027551460770427600163630ustar00rootroot00000000000000#include #include "../config-verify.h" #include "../config.h" #include "../particle.h" #include "../plugin.h" static int begin_expose(struct exposable *exposable) { exposable->width = exposable->particle->left_margin + exposable->particle->right_margin; return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { exposable_render_deco(exposable, pix, x, y, height); } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { struct exposable *exposable = exposable_common_new(particle, tags); exposable->begin_expose = &begin_expose; exposable->expose = &expose; return exposable; } static struct particle * empty_new(struct particle *common) { common->destroy = &particle_default_destroy; common->instantiate = &instantiate; return common; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { return empty_new(common); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_empty_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_empty_iface"))); #endif yambar-1.11.0/particles/list.c000066400000000000000000000154721460770427600162000ustar00rootroot00000000000000#include #define LOG_MODULE "list" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particle.h" #include "../plugin.h" struct private { struct particle **particles; size_t count; int left_spacing, right_spacing; }; struct eprivate { struct exposable **exposables; int *widths; size_t count; int left_spacing, right_spacing; }; static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; for (size_t i = 0; i < e->count; i++) e->exposables[i]->destroy(e->exposables[i]); free(e->exposables); free(e->widths); free(e); exposable_default_destroy(exposable); } static int begin_expose(struct exposable *exposable) { const struct eprivate *e = exposable->private; bool have_at_least_one = false; exposable->width = 0; for (size_t i = 0; i < e->count; i++) { struct exposable *ee = e->exposables[i]; e->widths[i] = ee->begin_expose(ee); assert(e->widths[i] >= 0); if (e->widths[i] > 0) { exposable->width += e->left_spacing + e->widths[i] + e->right_spacing; have_at_least_one = true; } } if (have_at_least_one) { exposable->width -= e->left_spacing + e->right_spacing; exposable->width += exposable->particle->left_margin; exposable->width += exposable->particle->right_margin; } else assert(exposable->width == 0); return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { const struct eprivate *e = exposable->private; exposable_render_deco(exposable, pix, x, y, height); int left_margin = exposable->particle->left_margin; int left_spacing = e->left_spacing; int right_spacing = e->right_spacing; x += left_margin - left_spacing; for (size_t i = 0; i < e->count; i++) { const struct exposable *ee = e->exposables[i]; ee->expose(ee, pix, x + left_spacing, y, height); if (e->widths[i] > 0) x += left_spacing + e->widths[i] + right_spacing; } } static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; if ((event == ON_MOUSE_MOTION && exposable->particle->have_on_click_template) || exposable->on_click[btn] != NULL) { /* We have our own handler */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = p->left_margin; for (size_t i = 0; i < e->count; i++) { if (x >= px && x < px + e->exposables[i]->width) { if (e->exposables[i]->on_mouse != NULL) { e->exposables[i]->on_mouse(e->exposables[i], bar, event, btn, x - px, y); } return; } if (e->exposables[i]->width > 0) px += e->left_spacing + e->exposables[i]->width + e->right_spacing; } /* We're between sub-particles (or in the left/right margin) */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; struct eprivate *e = calloc(1, sizeof(*e)); e->exposables = malloc(p->count * sizeof(*e->exposables)); e->widths = calloc(p->count, sizeof(*e->widths)); e->count = p->count; e->left_spacing = p->left_spacing; e->right_spacing = p->right_spacing; for (size_t i = 0; i < p->count; i++) { const struct particle *pp = p->particles[i]; e->exposables[i] = pp->instantiate(pp, tags); assert(e->exposables[i] != NULL); } struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; return exposable; } static void particle_destroy(struct particle *particle) { struct private *p = particle->private; for (size_t i = 0; i < p->count; i++) p->particles[i]->destroy(p->particles[i]); free(p->particles); free(p); particle_default_destroy(particle); } struct particle * particle_list_new(struct particle *common, struct particle *particles[], size_t count, int left_spacing, int right_spacing) { struct private *p = calloc(1, sizeof(*p)); p->particles = malloc(count * sizeof(p->particles[0])); p->count = count; p->left_spacing = left_spacing; p->right_spacing = right_spacing; for (size_t i = 0; i < count; i++) p->particles[i] = particles[i]; common->private = p; common->destroy = &particle_destroy; common->instantiate = &instantiate; return common; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { const struct yml_node *items = yml_get_value(node, "items"); const struct yml_node *spacing = yml_get_value(node, "spacing"); const struct yml_node *_left_spacing = yml_get_value(node, "left-spacing"); const struct yml_node *_right_spacing = yml_get_value(node, "right-spacing"); int left_spacing = spacing != NULL ? yml_value_as_int(spacing) : _left_spacing != NULL ? yml_value_as_int(_left_spacing) : 0; int right_spacing = spacing != NULL ? yml_value_as_int(spacing) : _right_spacing != NULL ? yml_value_as_int(_right_spacing) : 2; size_t count = yml_list_length(items); struct particle *parts[count]; size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); } return particle_list_new(common, parts, count, left_spacing, right_spacing); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"items", true, &conf_verify_particle_list_items}, {"spacing", false, &conf_verify_unsigned}, {"left-spacing", false, &conf_verify_unsigned}, {"right-spacing", false, &conf_verify_unsigned}, PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_list_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_list_iface"))); #endif yambar-1.11.0/particles/map.c000066400000000000000000000270171460770427600160000ustar00rootroot00000000000000#include #include #include #include #define LOG_MODULE "map" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particle.h" #include "../plugin.h" #include "dynlist.h" #include "map.h" static bool int_condition(const long tag_value, const long cond_value, enum map_op op) { switch (op) { case MAP_OP_EQ: return tag_value == cond_value; case MAP_OP_NE: return tag_value != cond_value; case MAP_OP_LE: return tag_value <= cond_value; case MAP_OP_LT: return tag_value < cond_value; case MAP_OP_GE: return tag_value >= cond_value; case MAP_OP_GT: return tag_value > cond_value; case MAP_OP_SELF: LOG_WARN("using int tag as bool"); default: return false; } } static bool float_condition(const double tag_value, const double cond_value, enum map_op op) { switch (op) { case MAP_OP_EQ: return tag_value == cond_value; case MAP_OP_NE: return tag_value != cond_value; case MAP_OP_LE: return tag_value <= cond_value; case MAP_OP_LT: return tag_value < cond_value; case MAP_OP_GE: return tag_value >= cond_value; case MAP_OP_GT: return tag_value > cond_value; case MAP_OP_SELF: LOG_WARN("using float tag as bool"); default: return false; } } static bool str_condition(const char *tag_value, const char *cond_value, enum map_op op) { switch (op) { case MAP_OP_EQ: return strcmp(tag_value, cond_value) == 0; case MAP_OP_NE: return strcmp(tag_value, cond_value) != 0; case MAP_OP_LE: return strcmp(tag_value, cond_value) <= 0; case MAP_OP_LT: return strcmp(tag_value, cond_value) < 0; case MAP_OP_GE: return strcmp(tag_value, cond_value) >= 0; case MAP_OP_GT: return strcmp(tag_value, cond_value) > 0; case MAP_OP_SELF: LOG_WARN("using String tag as bool"); default: return false; } } static bool eval_comparison(const struct map_condition *map_cond, const struct tag_set *tags) { const struct tag *tag = tag_for_name(tags, map_cond->tag); if (tag == NULL) { LOG_WARN("tag %s not found", map_cond->tag); return false; } switch (tag->type(tag)) { case TAG_TYPE_INT: { errno = 0; char *end; const long cond_value = strtol(map_cond->value, &end, 0); if (errno == ERANGE) { LOG_WARN("value %s is too large", map_cond->value); return false; } else if (*end != '\0') { LOG_WARN("failed to parse %s into int", map_cond->value); return false; } const long tag_value = tag->as_int(tag); return int_condition(tag_value, cond_value, map_cond->op); } case TAG_TYPE_FLOAT: { errno = 0; char *end; const double cond_value = strtod(map_cond->value, &end); if (errno == ERANGE) { LOG_WARN("value %s is too large", map_cond->value); return false; } else if (*end != '\0') { LOG_WARN("failed to parse %s into float", map_cond->value); return false; } const double tag_value = tag->as_float(tag); return float_condition(tag_value, cond_value, map_cond->op); } case TAG_TYPE_BOOL: if (map_cond->op == MAP_OP_SELF) return tag->as_bool(tag); else { LOG_WARN("boolean tag '%s' should be used directly", map_cond->tag); return false; } case TAG_TYPE_STRING: { const char *tag_value = tag->as_string(tag); return str_condition(tag_value, map_cond->value, map_cond->op); } } return false; } static bool eval_map_condition(const struct map_condition *map_cond, const struct tag_set *tags) { switch (map_cond->op) { case MAP_OP_NOT: return !eval_map_condition(map_cond->cond1, tags); case MAP_OP_AND: return eval_map_condition(map_cond->cond1, tags) && eval_map_condition(map_cond->cond2, tags); case MAP_OP_OR: return eval_map_condition(map_cond->cond1, tags) || eval_map_condition(map_cond->cond2, tags); default: return eval_comparison(map_cond, tags); } } void free_map_condition(struct map_condition *c) { switch (c->op) { case MAP_OP_EQ: case MAP_OP_NE: case MAP_OP_LE: case MAP_OP_LT: case MAP_OP_GE: case MAP_OP_GT: free(c->value); /* FALLTHROUGH */ case MAP_OP_SELF: free(c->tag); break; case MAP_OP_AND: case MAP_OP_OR: free_map_condition(c->cond2); /* FALLTHROUGH */ case MAP_OP_NOT: free_map_condition(c->cond1); break; } free(c); } struct particle_map { struct map_condition *condition; struct particle *particle; }; struct private { struct particle *default_particle; struct particle_map *map; size_t count; }; struct eprivate { struct exposable *exposable; }; static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; e->exposable->destroy(e->exposable); free(e); exposable_default_destroy(exposable); } static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; int width = e->exposable->begin_expose(e->exposable); assert(width >= 0); if (width > 0) width += exposable->particle->left_margin + exposable->particle->right_margin; exposable->width = width; return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { struct eprivate *e = exposable->private; exposable_render_deco(exposable, pix, x, y, height); e->exposable->expose(e->exposable, pix, x + exposable->particle->left_margin, y, height); } static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; if ((event == ON_MOUSE_MOTION && exposable->particle->have_on_click_template) || exposable->on_click[btn] != NULL) { /* We have our own handler */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = p->left_margin; if (x >= px && x < px + e->exposable->width) { if (e->exposable->on_mouse != NULL) e->exposable->on_mouse(e->exposable, bar, event, btn, x - px, y); return; } /* In the left- or right margin */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; struct particle *pp = NULL; for (size_t i = 0; i < p->count; i++) { const struct particle_map *e = &p->map[i]; if (!eval_map_condition(e->condition, tags)) continue; pp = e->particle; break; } struct eprivate *e = calloc(1, sizeof(*e)); if (pp != NULL) e->exposable = pp->instantiate(pp, tags); else if (p->default_particle != NULL) e->exposable = p->default_particle->instantiate(p->default_particle, tags); else e->exposable = dynlist_exposable_new(NULL, 0, 0, 0); assert(e->exposable != NULL); struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; return exposable; } static void particle_destroy(struct particle *particle) { struct private *p = particle->private; if (p->default_particle != NULL) p->default_particle->destroy(p->default_particle); for (size_t i = 0; i < p->count; i++) { struct particle *pp = p->map[i].particle; pp->destroy(pp); free_map_condition(p->map[i].condition); } free(p->map); free(p); particle_default_destroy(particle); } static struct particle * map_new(struct particle *common, const struct particle_map particle_map[], size_t count, struct particle *default_particle) { struct private *priv = calloc(1, sizeof(*priv)); priv->default_particle = default_particle; priv->count = count; priv->map = malloc(count * sizeof(priv->map[0])); for (size_t i = 0; i < count; i++) { priv->map[i].condition = particle_map[i].condition; priv->map[i].particle = particle_map[i].particle; } common->private = priv; common->destroy = &particle_destroy; common->instantiate = &instantiate; return common; } static bool verify_map_conditions(keychain_t *chain, const struct yml_node *node) { if (!yml_is_dict(node)) { LOG_ERR("%s: must be a dictionary of workspace-name: particle mappings", conf_err_prefix(chain, node)); return false; } bool result = true; for (struct yml_dict_iter it = yml_dict_iter(node); it.key != NULL; yml_dict_next(&it)) { const char *key = yml_value_as_string(it.key); if (key == NULL) { LOG_ERR("%s: key must be a string", conf_err_prefix(chain, it.key)); return false; } char *key_clone = strdup(key); YY_BUFFER_STATE buffer = yy_scan_string(key_clone); if (yyparse() != 0) { LOG_ERR("%s: %s", conf_err_prefix(chain, it.key), MAP_PARSER_ERROR_MSG); free(MAP_PARSER_ERROR_MSG); result = false; } else free_map_condition(MAP_CONDITION_PARSE_RESULT); yy_delete_buffer(buffer); free(key_clone); if (!conf_verify_particle(chain_push(chain, key), it.value)) return false; chain_pop(chain); } return result; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { const struct yml_node *conditions = yml_get_value(node, "conditions"); const struct yml_node *def = yml_get_value(node, "default"); struct particle_map particle_map[yml_dict_length(conditions)]; struct conf_inherit inherited = {.font = common->font, .font_shaping = common->font_shaping, .foreground = common->foreground}; size_t idx = 0; for (struct yml_dict_iter it = yml_dict_iter(conditions); it.key != NULL; yml_dict_next(&it), idx++) { /* Note we can skip the error checking here */ char *key_clone = strdup(yml_value_as_string(it.key)); YY_BUFFER_STATE buffer = yy_scan_string(key_clone); yyparse(); particle_map[idx].condition = MAP_CONDITION_PARSE_RESULT; yy_delete_buffer(buffer); free(key_clone); particle_map[idx].particle = conf_to_particle(it.value, inherited); } struct particle *default_particle = def != NULL ? conf_to_particle(def, inherited) : NULL; return map_new(common, particle_map, yml_dict_length(conditions), default_particle); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"conditions", true, &verify_map_conditions}, {"default", false, &conf_verify_particle}, PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_map_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_map_iface"))); #endif yambar-1.11.0/particles/map.h000066400000000000000000000012621460770427600157770ustar00rootroot00000000000000#pragma once enum map_op { MAP_OP_EQ, MAP_OP_NE, MAP_OP_LE, MAP_OP_LT, MAP_OP_GE, MAP_OP_GT, MAP_OP_SELF, MAP_OP_NOT, MAP_OP_AND, MAP_OP_OR, }; struct map_condition { union { char *tag; struct map_condition *cond1; }; enum map_op op; union { char *value; struct map_condition *cond2; }; }; void free_map_condition(struct map_condition *c); typedef struct yy_buffer_state *YY_BUFFER_STATE; YY_BUFFER_STATE yy_scan_string(const char *str); int yyparse(); void yy_delete_buffer(YY_BUFFER_STATE buffer); extern struct map_condition *MAP_CONDITION_PARSE_RESULT; extern char *MAP_PARSER_ERROR_MSG; yambar-1.11.0/particles/map.l000066400000000000000000000036041460770427600160050ustar00rootroot00000000000000%{ #include #include "map.h" #include "map.tab.h" void yyerror(const char *s); %} %option warn nodefault nounput noinput noyywrap char *quoted = NULL; size_t quote_len = 0; %x QUOTE %% [[:alnum:]_-]+ yylval.str = strdup(yytext); return WORD; \" { BEGIN(QUOTE); quoted = calloc(1, sizeof(quoted[0])); } [^\\\"]* { /* printf("CAT: %s\n", yytext); */ const size_t yy_length = strlen(yytext); quoted = realloc(quoted, quote_len + yy_length + 1); strcat(quoted, yytext); quote_len += yy_length; } \\\" { /* printf("escaped quote\n"); */ quoted = realloc(quoted, quote_len + 1 + 1); strcat(quoted, "\""); quote_len++; } \\. { /* printf("CAT: %s\n", yytext); */ const size_t yy_length = strlen(yytext); quoted = realloc(quoted, quote_len + yy_length + 1); strcat(quoted, yytext); quote_len += yy_length; } \\ { /* quoted string that ends with a backslash: "string\ */ quoted = realloc(quoted, quote_len + 1 + 1); strcat(quoted, "\\"); quote_len++; } \" { /* printf("QUOTED=%s\n", quoted); */ yylval.str = strdup(quoted); free(quoted); quoted = NULL; quote_len = 0; BEGIN(INITIAL); return STRING; } == yylval.op = MAP_OP_EQ; return CMP_OP; != yylval.op = MAP_OP_NE; return CMP_OP; \<= yylval.op = MAP_OP_LE; return CMP_OP; \< yylval.op = MAP_OP_LT; return CMP_OP; >= yylval.op = MAP_OP_GE; return CMP_OP; > yylval.op = MAP_OP_GT; return CMP_OP; && yylval.op = MAP_OP_AND; return BOOL_OP; \|\| yylval.op = MAP_OP_OR; return BOOL_OP; ~ return NOT; \( return L_PAR; \) return R_PAR; [ \t\n] ; . yylval.str = strdup(yytext); return STRING; %% yambar-1.11.0/particles/map.y000066400000000000000000000063241460770427600160240ustar00rootroot00000000000000%{ #include #include #include "map.h" struct map_condition *MAP_CONDITION_PARSE_RESULT; char *MAP_PARSER_ERROR_MSG; static const int NUM_TOKENS = 7; int yylex(); void yyerror(const char *str); %} %define parse.lac full %define parse.error custom %union { char *str; struct map_condition *condition; enum map_op op; } %token WORD STRING CMP_OP L_PAR R_PAR %left BOOL_OP %precedence NOT %destructor { free_map_condition($$); } condition %destructor { free($$); } WORD %destructor { free($$); } STRING %% result: condition { MAP_CONDITION_PARSE_RESULT = $1; }; condition: WORD { $$ = malloc(sizeof(struct map_condition)); $$->tag = $1; $$->op = MAP_OP_SELF; } | WORD CMP_OP WORD { $$ = malloc(sizeof(struct map_condition)); $$->tag = $1; $$->op = $2; $$->value = $3; } | WORD CMP_OP STRING { $$ = malloc(sizeof(struct map_condition)); $$->tag = $1; $$->op = $2; $$->value = $3; } | L_PAR condition R_PAR { $$ = $2; } | NOT condition { $$ = malloc(sizeof(struct map_condition)); $$->cond1 = $2; $$->op = MAP_OP_NOT; } | condition BOOL_OP condition { $$ = malloc(sizeof(struct map_condition)); $$->cond1 = $1; $$->op = $2; $$->cond2 = $3; } ; %% void yyerror(const char *str) { fprintf(stderr, "error: %s\n", str); } static char const* token_to_str(yysymbol_kind_t tkn) { switch (tkn) { case YYSYMBOL_CMP_OP: return "==, !=, <=, <, >=, >"; case YYSYMBOL_BOOL_OP: return "||, &&"; case YYSYMBOL_L_PAR: return "("; case YYSYMBOL_R_PAR: return ")"; case YYSYMBOL_NOT: return "~"; default: return yysymbol_name(tkn); } } static int yyreport_syntax_error (const yypcontext_t *ctx) { int res = 0; char *errmsg = malloc(1024); errmsg[0] = '\0'; // Report the tokens expected at this point. yysymbol_kind_t expected[NUM_TOKENS]; int n = yypcontext_expected_tokens(ctx, expected, NUM_TOKENS); if (n < 0) res = n; // Forward errors to yyparse. else { for (int i = 0; i < n; ++i) { strcat(errmsg, i == 0 ? "expected [" : ", "); strcat(errmsg, token_to_str(expected[i])); } strcat(errmsg, "]"); } // Report the unexpected token. yysymbol_kind_t lookahead = yypcontext_token(ctx); if (lookahead != YYSYMBOL_YYEMPTY) { strcat(errmsg, ", found "); if (!(lookahead == YYSYMBOL_STRING || lookahead == YYSYMBOL_WORD)) strcat(errmsg, yysymbol_name(lookahead)); else if (yylval.str != NULL) strcat(errmsg, yylval.str); else strcat(errmsg, "nothing"); } MAP_PARSER_ERROR_MSG = errmsg; return res; } yambar-1.11.0/particles/meson.build000066400000000000000000000032711460770427600172150ustar00rootroot00000000000000flex = find_program('flex', required: true) bison = find_program('bison', required: true) lgen = generator( flex, output : '@BASENAME@.yy.c', arguments : ['-o', '@OUTPUT@', '@INPUT@'] ) lfiles = lgen.process('map.l') pgen = generator( bison, output : ['@BASENAME@.tab.c', '@BASENAME@.tab.h'], arguments : ['-Wall', '-Wcounterexamples', '--defines=@OUTPUT1@', '--output=@OUTPUT0@', '@INPUT@'] ) pfiles = pgen.process('map.y') map_parser = declare_dependency(sources: [pfiles, lfiles], include_directories: '.') particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) dynlist_lib = build_target( 'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk, target_type: plugs_as_libs ? 'shared_library' : 'static_library', override_options : ['b_lundef=false'], install: plugs_as_libs, install_dir: get_option('libdir') + '/yambar', ) dynlist = declare_dependency(link_with: dynlist_lib) # Particle name -> dep-list deps = { 'empty': [], 'list': [], 'map': [dynlist, map_parser], 'progress-bar': [], 'ramp': [], 'string': [], } particles = [] foreach particle, particle_deps : deps if plugs_as_libs shared_module('@0@'.format(particle), '@0@.c'.format(particle), dependencies: [particle_sdk] + particle_deps, name_prefix: 'particle_', install: true, install_dir: join_paths(get_option('libdir'), 'yambar')) else particles += [declare_dependency( sources: '@0@.c'.format(particle), dependencies: [particle_sdk] + particle_deps, compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))] endif endforeach yambar-1.11.0/particles/progress-bar.c000066400000000000000000000253611460770427600176310ustar00rootroot00000000000000#include #include #include #define LOG_MODULE "progress_bar" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particle.h" #include "../plugin.h" struct private { char *tag; int width; struct particle *start_marker; struct particle *end_marker; struct particle *fill; struct particle *empty; struct particle *indicator; }; struct eprivate { size_t count; struct exposable **exposables; }; static void particle_destroy(struct particle *particle) { struct private *p = particle->private; p->start_marker->destroy(p->start_marker); p->end_marker->destroy(p->end_marker); p->fill->destroy(p->fill); p->empty->destroy(p->empty); p->indicator->destroy(p->indicator); free(p->tag); free(p); particle_default_destroy(particle); } static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; for (size_t i = 0; i < e->count; i++) e->exposables[i]->destroy(e->exposables[i]); free(e->exposables); free(e); exposable_default_destroy(exposable); } static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; bool have_at_least_one = false; exposable->width = 0; /* Sub-exposables */ for (size_t i = 0; i < e->count; i++) { int width = e->exposables[i]->begin_expose(e->exposables[i]); assert(width >= 0); if (width >= 0) { exposable->width += width; have_at_least_one = true; } } /* Margins */ if (have_at_least_one) { exposable->width += exposable->particle->left_margin + exposable->particle->right_margin; } else assert(exposable->width == 0); return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { const struct eprivate *e = exposable->private; exposable_render_deco(exposable, pix, x, y, height); x += exposable->particle->left_margin; for (size_t i = 0; i < e->count; i++) { e->exposables[i]->expose(e->exposables[i], pix, x, y, height); x += e->exposables[i]->width; } } static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; /* Start of empty/fill cells */ int x_offset = p->left_margin + e->exposables[0]->width; /* Mouse is *before* progress-bar? */ if (x < x_offset) { if (x >= p->left_margin) { /* Mouse is over the start-marker */ struct exposable *start = e->exposables[0]; if (start->on_mouse != NULL) start->on_mouse(start, bar, event, btn, x - p->left_margin, y); } else { /* Mouse if over left margin */ bar->set_cursor(bar, "left_ptr"); } return; } /* Size of the clickable area (the empty/fill cells) */ int clickable_width = 0; for (size_t i = 1; i < e->count - 1; i++) clickable_width += e->exposables[i]->width; /* Mouse is *after* progress-bar? */ if (x - x_offset > clickable_width) { if (x - x_offset - clickable_width < e->exposables[e->count - 1]->width) { /* Mouse is over the end-marker */ struct exposable *end = e->exposables[e->count - 1]; if (end->on_mouse != NULL) end->on_mouse(end, bar, event, btn, x - x_offset - clickable_width, y); } else { /* Mouse is over the right margin */ bar->set_cursor(bar, "left_ptr"); } return; } /* * Hack-warning! * * In order to pass the *clicked* position to the on_click * handler, we expand the handler *again* (first time would be * when the particle instantiated us). * * We pass a single tag, "where", which is a percentage value. * * Keep a reference to the un-expanded string, to be able to * reset it after executing the handler. * * Note that we only consider the actual progress bar to be * clickable. This means we ignore the start and end markers. */ /* Remember the original handler, so that we can restore it */ char *original[MOUSE_BTN_COUNT]; for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) original[i] = exposable->on_click[i]; if (event == ON_MOUSE_CLICK) { long where = clickable_width > 0 ? 100 * (x - x_offset) / clickable_width : 0; struct tag_set tags = { .tags = (struct tag *[]){tag_new_int(NULL, "where", where)}, .count = 1, }; tags_expand_templates(exposable->on_click, (const char **)exposable->on_click, MOUSE_BTN_COUNT, &tags); tag_set_destroy(&tags); } /* Call default implementation, which will execute our handler */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); if (event == ON_MOUSE_CLICK) { /* Reset handler string */ for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { free(exposable->on_click[i]); exposable->on_click[i] = original[i]; } } } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; const struct tag *tag = tag_for_name(tags, p->tag); long value = tag != NULL ? tag->as_int(tag) : 0; long min = tag != NULL ? tag->min(tag) : 0; long max = tag != NULL ? tag->max(tag) : 0; LOG_DBG("%s: value=%ld, min=%ld, max=%ld", tag != NULL ? tag->name(tag) : "", value, min, max); long fill_count = max == min ? 0 : p->width * value / (max - min); long empty_count = p->width - fill_count; struct eprivate *epriv = calloc(1, sizeof(*epriv)); epriv->count = (1 + /* Start marker */ fill_count + /* Before current position */ 1 + /* Current position indicator */ empty_count + /* After current position */ 1); /* End marker */ epriv->exposables = malloc(epriv->count * sizeof(epriv->exposables[0])); size_t idx = 0; epriv->exposables[idx++] = p->start_marker->instantiate(p->start_marker, tags); for (size_t i = 0; i < fill_count; i++) epriv->exposables[idx++] = p->fill->instantiate(p->fill, tags); epriv->exposables[idx++] = p->indicator->instantiate(p->indicator, tags); for (size_t i = 0; i < empty_count; i++) epriv->exposables[idx++] = p->empty->instantiate(p->empty, tags); epriv->exposables[idx++] = p->end_marker->instantiate(p->end_marker, tags); assert(idx == epriv->count); for (size_t i = 0; i < epriv->count; i++) assert(epriv->exposables[i] != NULL); struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = epriv; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; if (tag == NULL) return exposable; enum tag_realtime_unit rt = tag->realtime(tag); if (rt == TAG_REALTIME_NONE) return exposable; #if 0 long units_per_segment = (max - min) / p->width; long units_filled = fill_count * (max - min) / p->width; long units_til_next_segment = units_per_segment - (value - units_filled); LOG_DBG("tag: %s, value: %ld, " "units-per-segment: %ld, units-filled: %ld, units-til-next: %ld", tag->name(tag), value, units_per_segment, units_filled, units_til_next_segment); #else double units_per_segment = (double)(max - min) / p->width; double units_filled = fill_count * units_per_segment; double units_til_next_segment = units_per_segment - ((double)value - units_filled); LOG_DBG("tag: %s, value: %ld, " "units-per-segment: %f, units-filled: %f, units-til-next: %f", tag->name(tag), value, units_per_segment, units_filled, units_til_next_segment); #endif if (!tag->refresh_in(tag, units_til_next_segment)) LOG_WARN("failed to schedule update of tag"); return exposable; } static struct particle * progress_bar_new(struct particle *common, const char *tag, int width, struct particle *start_marker, struct particle *end_marker, struct particle *fill, struct particle *empty, struct particle *indicator) { struct private *priv = calloc(1, sizeof(*priv)); priv->tag = strdup(tag); priv->width = width; priv->start_marker = start_marker; priv->end_marker = end_marker; priv->fill = fill; priv->empty = empty; priv->indicator = indicator; common->private = priv; common->destroy = &particle_destroy; common->instantiate = &instantiate; return common; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { const struct yml_node *tag = yml_get_value(node, "tag"); const struct yml_node *length = yml_get_value(node, "length"); const struct yml_node *start = yml_get_value(node, "start"); const struct yml_node *end = yml_get_value(node, "end"); const struct yml_node *fill = yml_get_value(node, "fill"); const struct yml_node *empty = yml_get_value(node, "empty"); const struct yml_node *indicator = yml_get_value(node, "indicator"); struct conf_inherit inherited = { .font = common->font, .font_shaping = common->font_shaping, .foreground = common->foreground, }; return progress_bar_new(common, yml_value_as_string(tag), yml_value_as_int(length), conf_to_particle(start, inherited), conf_to_particle(end, inherited), conf_to_particle(fill, inherited), conf_to_particle(empty, inherited), conf_to_particle(indicator, inherited)); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"tag", true, &conf_verify_string}, {"length", true, &conf_verify_unsigned}, /* TODO: make these optional? Default to empty */ {"start", true, &conf_verify_particle}, {"end", true, &conf_verify_particle}, {"fill", true, &conf_verify_particle}, {"empty", true, &conf_verify_particle}, {"indicator", true, &conf_verify_particle}, PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_progress_bar_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_progress_bar_iface"))); #endif yambar-1.11.0/particles/ramp.c000066400000000000000000000150171460770427600161570ustar00rootroot00000000000000#include #include #include #include #define LOG_MODULE "ramp" #define LOG_ENABLE_DBG 0 #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particle.h" #include "../plugin.h" struct private { char *tag; bool use_custom_min; long min; bool use_custom_max; long max; struct particle **particles; size_t count; }; struct eprivate { struct exposable *exposable; }; static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; e->exposable->destroy(e->exposable); free(e); exposable_default_destroy(exposable); } static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; int width = e->exposable->begin_expose(e->exposable); assert(width >= 0); if (width > 0) width += exposable->particle->left_margin + exposable->particle->right_margin; exposable->width = width; return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { struct eprivate *e = exposable->private; exposable_render_deco(exposable, pix, x, y, height); e->exposable->expose(e->exposable, pix, x + exposable->particle->left_margin, y, height); } static void on_mouse(struct exposable *exposable, struct bar *bar, enum mouse_event event, enum mouse_button btn, int x, int y) { const struct particle *p = exposable->particle; const struct eprivate *e = exposable->private; if ((event == ON_MOUSE_MOTION && exposable->particle->have_on_click_template) || exposable->on_click[btn] != NULL) { /* We have our own handler */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); return; } int px = p->left_margin; if (x >= px && x < px + e->exposable->width) { if (e->exposable->on_mouse != NULL) e->exposable->on_mouse(e->exposable, bar, event, btn, x - px, y); return; } /* In the left- or right margin */ exposable_default_on_mouse(exposable, bar, event, btn, x, y); } static void particle_destroy(struct particle *particle) { struct private *p = particle->private; for (size_t i = 0; i < p->count; i++) p->particles[i]->destroy(p->particles[i]); free(p->tag); free(p->particles); free(p); particle_default_destroy(particle); } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; const struct tag *tag = tag_for_name(tags, p->tag); assert(p->count > 0); long value = tag != NULL ? tag->as_int(tag) : 0; long min = tag != NULL ? tag->min(tag) : 0; long max = tag != NULL ? tag->max(tag) : 0; min = p->use_custom_min ? p->min : min; max = p->use_custom_max ? p->max : max; if (min > max) { LOG_WARN("tag's minimum value is greater than its maximum: " "tag=\"%s\", min=%ld, max=%ld", p->tag, min, max); min = max; } if (value < min) { LOG_WARN("tag's value is less than its minimum value: " "tag=\"%s\", min=%ld, value=%ld", p->tag, min, value); value = min; } if (value > max) { LOG_WARN("tag's value is greater than its maximum value: " "tag=\"%s\", max=%ld, value=%ld", p->tag, max, value); value = max; } assert(value >= min && value <= max); assert(max >= min); size_t idx = 0; if (max - min > 0) idx = p->count * (value - min) / (max - min); if (idx == p->count) idx--; /* * printf("ramp: value: %lu, min: %lu, max: %lu, progress: %f, idx: %zu\n", * value, min, max, progress, idx); */ assert(idx >= 0 && idx < p->count); struct particle *pp = p->particles[idx]; struct eprivate *e = calloc(1, sizeof(*e)); e->exposable = pp->instantiate(pp, tags); assert(e->exposable != NULL); struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; exposable->on_mouse = &on_mouse; return exposable; } static struct particle * ramp_new(struct particle *common, const char *tag, struct particle *particles[], size_t count, bool use_custom_min, long min, bool use_custom_max, long max) { struct private *priv = calloc(1, sizeof(*priv)); priv->tag = strdup(tag); priv->particles = malloc(count * sizeof(priv->particles[0])); priv->count = count; priv->use_custom_max = use_custom_max; priv->max = max; priv->use_custom_min = use_custom_min; priv->min = min; for (size_t i = 0; i < count; i++) priv->particles[i] = particles[i]; common->private = priv; common->destroy = &particle_destroy; common->instantiate = &instantiate; return common; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { const struct yml_node *tag = yml_get_value(node, "tag"); const struct yml_node *items = yml_get_value(node, "items"); const struct yml_node *min = yml_get_value(node, "min"); const struct yml_node *max = yml_get_value(node, "max"); size_t count = yml_list_length(items); struct particle *parts[count]; size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); } long min_v = min != NULL ? yml_value_as_int(min) : 0; long max_v = max != NULL ? yml_value_as_int(max) : 0; return ramp_new(common, yml_value_as_string(tag), parts, count, min != NULL, min_v, max != NULL, max_v); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"tag", true, &conf_verify_string}, {"items", true, &conf_verify_particle_list_items}, {"min", false, &conf_verify_int}, {"max", false, &conf_verify_int}, PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_ramp_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_ramp_iface"))); #endif yambar-1.11.0/particles/string.c000066400000000000000000000220141460770427600165210ustar00rootroot00000000000000#include #include #include #define LOG_MODULE "string" #define LOG_ENABLE_DBG 0 #include "../char32.h" #include "../config-verify.h" #include "../config.h" #include "../log.h" #include "../particle.h" #include "../plugin.h" struct text_run_cache { uint64_t hash; struct fcft_text_run *run; int width; bool in_use; }; struct private { char *text; size_t max_len; size_t cache_size; struct text_run_cache *cache; }; struct eprivate { ssize_t cache_idx; const struct fcft_glyph **glyphs; const struct fcft_glyph **allocated_glyphs; long *kern_x; int num_glyphs; }; static void exposable_destroy(struct exposable *exposable) { struct eprivate *e = exposable->private; free(e->allocated_glyphs); free(e->kern_x); free(e); exposable_default_destroy(exposable); } static int begin_expose(struct exposable *exposable) { struct eprivate *e = exposable->private; struct private *p = exposable->particle->private; exposable->width = exposable->particle->left_margin + exposable->particle->right_margin; if (e->cache_idx >= 0) { exposable->width += p->cache[e->cache_idx].width; } else { /* Calculate the size we need to render the glyphs */ for (int i = 0; i < e->num_glyphs; i++) exposable->width += e->kern_x[i] + e->glyphs[i]->advance.x; } return exposable->width; } static void expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) { exposable_render_deco(exposable, pix, x, y, height); const struct eprivate *e = exposable->private; const struct fcft_font *font = exposable->particle->font; if (e->cache_idx >= 0) { struct private *priv = exposable->particle->private; priv->cache[e->cache_idx].in_use = false; } if (e->num_glyphs == 0) return; /* * This tries to center the font around the bar center, by using * the font's ascent+descent as total height, and then removing * its descent. This way, the part of the font *above* the * baseline is centered. * * "EEEE" will typically be dead center, with the middle of each character being in the bar's center. * "eee" will be slightly below the center. * "jjj" will be even further below the center. * * Finally, if the font's descent is negative, ignore it (except * for the height calculation). This is unfortunately not based on * any real facts, but works very well with e.g. the "Awesome 6" * font family. */ const double baseline = (double)y + (double)(height + font->ascent + font->descent) / 2.0 - (font->descent > 0 ? font->descent : 0); x += exposable->particle->left_margin; /* Loop glyphs and render them, one by one */ for (int i = 0; i < e->num_glyphs; i++) { const struct fcft_glyph *glyph = e->glyphs[i]; assert(glyph != NULL); x += e->kern_x[i]; if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) { /* Glyph surface is a pre-rendered image (typically a color emoji...) */ pixman_image_composite32(PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0, x + glyph->x, baseline - glyph->y, glyph->width, glyph->height); } else { /* Glyph surface is an alpha mask */ pixman_image_t *src = pixman_image_create_solid_fill(&exposable->particle->foreground); pixman_image_composite32(PIXMAN_OP_OVER, src, glyph->pix, pix, 0, 0, 0, 0, x + glyph->x, baseline - glyph->y, glyph->width, glyph->height); pixman_image_unref(src); } x += glyph->advance.x; } } static uint64_t sdbm_hash(const char *s) { uint64_t hash = 0; for (; *s != '\0'; s++) { int c = *s; hash = c + (hash << 6) + (hash << 16) - hash; } return hash; } static struct exposable * instantiate(const struct particle *particle, const struct tag_set *tags) { struct private *p = (struct private *)particle->private; struct eprivate *e = calloc(1, sizeof(*e)); struct fcft_font *font = particle->font; char32_t *wtext = NULL; char *text = tags_expand_template(p->text, tags); e->glyphs = e->allocated_glyphs = NULL; e->num_glyphs = 0; e->kern_x = NULL; e->cache_idx = -1; uint64_t hash = sdbm_hash(text); /* First, check if we have this string cached */ for (size_t i = 0; i < p->cache_size; i++) { if (p->cache[i].hash == hash) { assert(p->cache[i].run != NULL); p->cache[i].in_use = true; e->cache_idx = i; e->glyphs = p->cache[i].run->glyphs; e->num_glyphs = p->cache[i].run->count; e->kern_x = calloc(p->cache[i].run->count, sizeof(e->kern_x[0])); goto done; } } /* Not in cache - we need to rasterize it. First, convert to char32_t */ wtext = ambstoc32(text); size_t chars = wtext != NULL ? c32len(wtext) : 0; /* Truncate, if necessary */ if (p->max_len > 0 && chars > p->max_len) { chars = p->max_len; if (p->max_len > 3) wtext[p->max_len - 1] = U'…'; wtext[p->max_len] = U'\0'; } e->kern_x = calloc(chars, sizeof(e->kern_x[0])); if (particle->font_shaping == FONT_SHAPE_FULL && fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) { struct fcft_text_run *run = fcft_rasterize_text_run_utf32(font, chars, wtext, FCFT_SUBPIXEL_NONE); if (run != NULL) { int w = 0; for (size_t i = 0; i < run->count; i++) w += run->glyphs[i]->advance.x; ssize_t cache_idx = -1; for (size_t i = 0; i < p->cache_size; i++) { if (p->cache[i].run == NULL || !p->cache[i].in_use) { fcft_text_run_destroy(p->cache[i].run); cache_idx = i; break; } } if (cache_idx < 0) { size_t new_size = p->cache_size + 1; struct text_run_cache *new_cache = realloc(p->cache, new_size * sizeof(new_cache[0])); p->cache_size = new_size; p->cache = new_cache; cache_idx = new_size - 1; } assert(cache_idx >= 0 && cache_idx < p->cache_size); p->cache[cache_idx].hash = hash; p->cache[cache_idx].run = run; p->cache[cache_idx].width = w; p->cache[cache_idx].in_use = true; e->cache_idx = cache_idx; e->num_glyphs = run->count; e->glyphs = run->glyphs; } } if (e->glyphs == NULL) { e->allocated_glyphs = malloc(chars * sizeof(e->glyphs[0])); /* Convert text to glyph masks/images. */ for (size_t i = 0; i < chars; i++) { const struct fcft_glyph *glyph = fcft_rasterize_char_utf32(font, wtext[i], FCFT_SUBPIXEL_NONE); if (glyph == NULL) continue; e->allocated_glyphs[e->num_glyphs++] = glyph; if (i == 0) continue; fcft_kerning(font, wtext[i - 1], wtext[i], &e->kern_x[i], NULL); } e->glyphs = e->allocated_glyphs; } done: free(wtext); free(text); struct exposable *exposable = exposable_common_new(particle, tags); exposable->private = e; exposable->destroy = &exposable_destroy; exposable->begin_expose = &begin_expose; exposable->expose = &expose; return exposable; } static void particle_destroy(struct particle *particle) { struct private *p = particle->private; for (size_t i = 0; i < p->cache_size; i++) fcft_text_run_destroy(p->cache[i].run); free(p->cache); free(p->text); free(p); particle_default_destroy(particle); } static struct particle * string_new(struct particle *common, const char *text, size_t max_len) { struct private *p = calloc(1, sizeof(*p)); p->text = strdup(text); p->max_len = max_len; p->cache_size = 0; p->cache = NULL; common->private = p; common->destroy = &particle_destroy; common->instantiate = &instantiate; return common; } static struct particle * from_conf(const struct yml_node *node, struct particle *common) { const struct yml_node *text = yml_get_value(node, "text"); const struct yml_node *max = yml_get_value(node, "max"); return string_new(common, yml_value_as_string(text), max != NULL ? yml_value_as_int(max) : 0); } static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"text", true, &conf_verify_string}, {"max", false, &conf_verify_unsigned}, PARTICLE_COMMON_ATTRS, }; return conf_verify_dict(chain, node, attrs); } const struct particle_iface particle_string_iface = { .verify_conf = &verify_conf, .from_conf = &from_conf, }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) extern const struct particle_iface iface __attribute__((weak, alias("particle_string_iface"))); #endif yambar-1.11.0/plugin.c000066400000000000000000000244251460770427600145330ustar00rootroot00000000000000#include "plugin.h" #include #include #include #define LOG_MODULE "plugin" #define LOG_ENABLE_DBG 0 #include "config.h" #include "log.h" #if !defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) #define EXTERN_MODULE(plug_name) \ extern const struct module_iface module_##plug_name##_iface; \ extern bool plug_name##_verify_conf(keychain_t *chain, const struct yml_node *node); \ extern struct module *plug_name##_from_conf(const struct yml_node *node, struct conf_inherit inherited); #define EXTERN_PARTICLE(plug_name) \ extern const struct particle_iface particle_##plug_name##_iface; \ extern bool plug_name##_verify_conf(keychain_t *chain, const struct yml_node *node); \ extern struct particle *plug_name##_from_conf(const struct yml_node *node, struct particle *common); #define EXTERN_DECORATION(plug_name) \ extern const struct deco_iface deco_##plug_name##_iface; \ extern bool plug_name##_verify_conf(keychain_t *chain, const struct yml_node *node); \ extern struct deco *plug_name##_from_conf(const struct yml_node *node); #if defined(HAVE_PLUGIN_alsa) EXTERN_MODULE(alsa); #endif #if defined(HAVE_PLUGIN_backlight) EXTERN_MODULE(backlight); #endif #if defined(HAVE_PLUGIN_battery) EXTERN_MODULE(battery); #endif #if defined(HAVE_PLUGIN_clock) EXTERN_MODULE(clock); #endif #if defined(HAVE_PLUGIN_cpu) EXTERN_MODULE(cpu); #endif #if defined(HAVE_PLUGIN_disk_io) EXTERN_MODULE(disk_io); #endif #if defined(HAVE_PLUGIN_dwl) EXTERN_MODULE(dwl); #endif #if defined(HAVE_PLUGIN_foreign_toplevel) EXTERN_MODULE(foreign_toplevel); #endif #if defined(HAVE_PLUGIN_mem) EXTERN_MODULE(mem); #endif #if defined(HAVE_PLUGIN_mpd) EXTERN_MODULE(mpd); #endif #if defined(HAVE_PLUGIN_i3) EXTERN_MODULE(i3); #endif #if defined(HAVE_PLUGIN_label) EXTERN_MODULE(label); #endif #if defined(HAVE_PLUGIN_network) EXTERN_MODULE(network); #endif #if defined(HAVE_PLUGIN_pipewire) EXTERN_MODULE(pipewire); #endif #if defined(HAVE_PLUGIN_pulse) EXTERN_MODULE(pulse); #endif #if defined(HAVE_PLUGIN_removables) EXTERN_MODULE(removables); #endif #if defined(HAVE_PLUGIN_river) EXTERN_MODULE(river); #endif #if defined(HAVE_PLUGIN_script) EXTERN_MODULE(script); #endif #if defined(HAVE_PLUGIN_sway_xkb) EXTERN_MODULE(sway_xkb); #endif #if defined(HAVE_PLUGIN_xkb) EXTERN_MODULE(xkb); #endif #if defined(HAVE_PLUGIN_xwindow) EXTERN_MODULE(xwindow); #endif EXTERN_PARTICLE(empty); EXTERN_PARTICLE(list); EXTERN_PARTICLE(map); EXTERN_PARTICLE(progress_bar); EXTERN_PARTICLE(ramp); EXTERN_PARTICLE(string); EXTERN_DECORATION(background); EXTERN_DECORATION(border); EXTERN_DECORATION(stack); EXTERN_DECORATION(underline); EXTERN_DECORATION(overline); #undef EXTERN_DECORATION #undef EXTERN_PARTICLE #undef EXTERN_MODULE #endif static tll(struct plugin) plugins = tll_init(); static const char * type2str(enum plugin_type type) { switch (type) { case PLUGIN_MODULE: return "module"; case PLUGIN_PARTICLE: return "particle"; case PLUGIN_DECORATION: return "decoration"; } assert(false && "invalid type"); return ""; } static void __attribute__((constructor)) init(void) { #if !defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) #define REGISTER_CORE_PLUGIN(plug_name, func_prefix, plug_type) \ do { \ tll_push_back(plugins, ((struct plugin){ \ .name = strdup(#plug_name), \ .type = (plug_type), \ .lib = RTLD_DEFAULT, \ })); \ } while (0) #define REGISTER_CORE_MODULE(plug_name, func_prefix) \ do { \ REGISTER_CORE_PLUGIN(plug_name, func_prefix, PLUGIN_MODULE); \ tll_back(plugins).module = &module_##func_prefix##_iface; \ } while (0) #define REGISTER_CORE_PARTICLE(plug_name, func_prefix) \ do { \ REGISTER_CORE_PLUGIN(plug_name, func_prefix, PLUGIN_PARTICLE); \ tll_back(plugins).particle = &particle_##func_prefix##_iface; \ } while (0) #define REGISTER_CORE_DECORATION(plug_name, func_prefix) \ do { \ REGISTER_CORE_PLUGIN(plug_name, func_prefix, PLUGIN_DECORATION); \ tll_back(plugins).decoration = &deco_##func_prefix##_iface; \ } while (0) #if defined(HAVE_PLUGIN_alsa) REGISTER_CORE_MODULE(alsa, alsa); #endif #if defined(HAVE_PLUGIN_backlight) REGISTER_CORE_MODULE(backlight, backlight); #endif #if defined(HAVE_PLUGIN_battery) REGISTER_CORE_MODULE(battery, battery); #endif #if defined(HAVE_PLUGIN_clock) REGISTER_CORE_MODULE(clock, clock); #endif #if defined(HAVE_PLUGIN_cpu) REGISTER_CORE_MODULE(cpu, cpu); #endif #if defined(HAVE_PLUGIN_disk_io) REGISTER_CORE_MODULE(disk-io, disk_io); #endif #if defined(HAVE_PLUGIN_dwl) REGISTER_CORE_MODULE(dwl, dwl); #endif #if defined(HAVE_PLUGIN_foreign_toplevel) REGISTER_CORE_MODULE(foreign-toplevel, foreign_toplevel); #endif #if defined(HAVE_PLUGIN_mem) REGISTER_CORE_MODULE(mem, mem); #endif #if defined(HAVE_PLUGIN_mpd) REGISTER_CORE_MODULE(mpd, mpd); #endif #if defined(HAVE_PLUGIN_i3) REGISTER_CORE_MODULE(i3, i3); #endif #if defined(HAVE_PLUGIN_label) REGISTER_CORE_MODULE(label, label); #endif #if defined(HAVE_PLUGIN_network) REGISTER_CORE_MODULE(network, network); #endif #if defined(HAVE_PLUGIN_pipewire) REGISTER_CORE_MODULE(pipewire, pipewire); #endif #if defined(HAVE_PLUGIN_pulse) REGISTER_CORE_MODULE(pulse, pulse); #endif #if defined(HAVE_PLUGIN_removables) REGISTER_CORE_MODULE(removables, removables); #endif #if defined(HAVE_PLUGIN_river) REGISTER_CORE_MODULE(river, river); #endif #if defined(HAVE_PLUGIN_script) REGISTER_CORE_MODULE(script, script); #endif #if defined(HAVE_PLUGIN_sway_xkb) REGISTER_CORE_MODULE(sway-xkb, sway_xkb); #endif #if defined(HAVE_PLUGIN_xkb) REGISTER_CORE_MODULE(xkb, xkb); #endif #if defined(HAVE_PLUGIN_xwindow) REGISTER_CORE_MODULE(xwindow, xwindow); #endif REGISTER_CORE_PARTICLE(empty, empty); REGISTER_CORE_PARTICLE(list, list); REGISTER_CORE_PARTICLE(map, map); REGISTER_CORE_PARTICLE(progress-bar, progress_bar); REGISTER_CORE_PARTICLE(ramp, ramp); REGISTER_CORE_PARTICLE(string, string); REGISTER_CORE_DECORATION(background, background); REGISTER_CORE_DECORATION(border, border); REGISTER_CORE_DECORATION(stack, stack); REGISTER_CORE_DECORATION(underline, underline); REGISTER_CORE_DECORATION(overline, overline); #undef REGISTER_CORE_DECORATION #undef REGISTER_CORE_PARTICLE #undef REGISTER_CORE_PLUGIN #endif /* !CORE_PLUGINS_AS_SHARED_LIBRARIES */ } static void free_plugin(struct plugin plug) { dlerror(); if (plug.lib != NULL) dlclose(plug.lib); const char *dl_error = dlerror(); if (dl_error != NULL) LOG_ERR("%s: %s: dlclose(): %s", type2str(plug.type), plug.name, dl_error); free(plug.name); } static void __attribute__((destructor)) fini(void) { tll_free_and_free(plugins, free_plugin); } const struct plugin * plugin_load(const char *name, enum plugin_type type) { tll_foreach(plugins, plug) { if (plug->item.type == type && strcmp(plug->item.name, name) == 0) { LOG_DBG("%s: %s already loaded: %p", type2str(type), name, plug->item.lib); assert(plug->item.dummy != NULL); return &plug->item; } } char path[128]; snprintf(path, sizeof(path), "%s_%s.so", type2str(type), name); /* Not loaded - do it now */ void *lib = dlopen(path, RTLD_LOCAL | RTLD_NOW); LOG_DBG("%s: %s: dlopened to %p", type2str(type), name, lib); if (lib == NULL) { LOG_ERR("%s: %s: dlopen: %s", type2str(type), name, dlerror()); return NULL; } tll_push_back(plugins, ((struct plugin){strdup(name), type, lib, {NULL}})); struct plugin *plug = &tll_back(plugins); dlerror(); /* Clear previous error */ plug->dummy = dlsym(lib, "iface"); const char *dl_error = dlerror(); if (dl_error != NULL) { LOG_ERR("%s: %s: dlsym: %s", type2str(type), name, dl_error); return NULL; } return plug; } const struct module_iface * plugin_load_module(const char *name) { const struct plugin *plug = plugin_load(name, PLUGIN_MODULE); return plug != NULL ? plug->module : NULL; } const struct particle_iface * plugin_load_particle(const char *name) { const struct plugin *plug = plugin_load(name, PLUGIN_PARTICLE); return plug != NULL ? plug->particle : NULL; } const struct deco_iface * plugin_load_deco(const char *name) { const struct plugin *plug = plugin_load(name, PLUGIN_DECORATION); return plug != NULL ? plug->decoration : NULL; } yambar-1.11.0/plugin.h000066400000000000000000000026041460770427600145330ustar00rootroot00000000000000#pragma once #include "config-verify.h" #include "config.h" #include "module.h" #include "particle.h" typedef bool (*verify_func_t)(keychain_t *chain, const struct yml_node *node); struct module_iface { verify_func_t verify_conf; struct module *(*from_conf)(const struct yml_node *node, struct conf_inherit inherited); }; struct particle_iface { verify_func_t verify_conf; struct particle *(*from_conf)(const struct yml_node *node, struct particle *common); }; struct deco_iface { verify_func_t verify_conf; struct deco *(*from_conf)(const struct yml_node *node); }; const struct module_iface *plugin_load_module(const char *name); const struct particle_iface *plugin_load_particle(const char *name); const struct deco_iface *plugin_load_deco(const char *name); enum plugin_type { PLUGIN_MODULE, PLUGIN_PARTICLE, PLUGIN_DECORATION }; struct plugin { char *name; enum plugin_type type; void *lib; union { const struct module_iface *module; const struct particle_iface *particle; const struct deco_iface *decoration; const void *dummy; #if 0 struct { void *sym1; void *sym2; } dummy; struct module_iface module; struct particle_iface particle; struct deco_iface decoration; #endif }; }; const struct plugin *plugin_load(const char *name, enum plugin_type type); yambar-1.11.0/screenshot.png000066400000000000000000000314651460770427600157560ustar00rootroot00000000000000PNG  IHDRvbKGD IDATx]y}Lϱ, ., ȡ6QQ3^1j1FE!h41(1D0ke!\ ;Lgv|z^z]ׯ ׃@$Y )Gcч3ljeOϜ5JGr}b-M1Qd[˞YєqTr…  I b*~G"6L\r#bD` FHHsM |ٲ?'aѨeƄl<O$8Xyڠ3H%(('鏌pf $JeMpDjR̘G$hHkvHgtw"U0\PA'!tAv{jsĒLq.Dykw# 6&oK7E8 6Gp .\p"sAx)eEQHh4cKbZzqp8`G5Pk2 :UQSXFP3}̈JFő2:6#FHUH$ }>ΌHE2;3nEQ͕ d!V "0nPoBBU!^~1B?,; 3^T8su ~NJIHw .kI\,\ـK1B\(9!X^q|d2c~آ&eFeYF,$IZqLV#H$dHY^o.]bEEE{E^H`<BZt@QiD"Fg刜-Q -!BGnI;xfl ݓM-fdM<v?p)eK f6X#[M.\p XZY4pq)~ZYe&Ryв+MN3sk_8A[0$eZ񆚦7k̀'yә9p<$I4pf~qdYFԨR@K$d nL֎ݺux<8;fmHTUNk ҪXrj<9Rp[]8 GSe#Q?(H3x"8™'dp!~]tpFtʁKsʱ9RdlfkUr…8, )J+zw:OHe V'/SSǟNPBG D`mk*}GQ\sMxE7\1ٞf R,`$oC|>\S8\C6o1YWȎH$ E"]UUz>×ٗMò`:[x<"cPER$IBj}ӏPj/'IY4׋:9aܬ Lۚ왩,%z{ 1ƺ,S閏?!~ AzROC1gZTն6qВnQd9v㌃?-r=fZ$jD1{$)4ͅN$J8.tU/:5c<(~?GzkWnV~o'[ٖ#pTz/G~>s]ߎԩ#?\VWyW}ݲ1e)C.Oza7߬}aR1Z7`iUkYIC{-~{^I  1u)Ro_λd(Sէ I[MBa~`8)_UUχe@I dIѨi c @Ci nK]gDF2"ˉD"H WQD#A'gRnň_$ W=)T)Rdt]è~D+Jz1Sry1+BȠHՉPit#rXجfqhpLVvI'BW`V#.zz -Ɨ &6pbtH !R\30݌0QȺt]|^6HΜ /Wݧ ^.=)'Ҵj~8*nXtsUYzc͵u`꼓GIYff\2\tX]l+n8oP]F_7m8\{Ώui?^YܡJwU]pJtK2oտv$:P;a8}|shU]Nj~I?yakY$]Wv+eySQK&\rNa+ ǜu/]֢Ԓ᧎(ڵuO-Nو>ۺzݶzɚ8qQ9sA):H42w.Lld|d2 Rb6mL&eeDL@II~{n|ܳgOmbglWRRRbLonn.--%Sg;w@CCC>}c8:s<WS@,555T u}֭'xލ7qeHzTYEQ mmE>}>_kk+D" s;SJRPhǃBd!HsNfhÑk6L9P-Ypiִ砿m3B5ž˅ɚ6m>5kriӦfNՕeTAl0wݘ&NEf,8':|7zp.AV θ?}7& @J!ӕ)gi#"Lp)=\J?ʜ?=_;z,oOs晴|޲n:)ߜy}9"M6&۵nu*vpߘ3#.PQgxC9كzA%={gM5]<.-u:s6NaYQR>miZ8>p$IAA]ьA!2$IB5.qgRE!gr죳셈Kgc?:c9Η{=⳩+K08rO*b ̒Asy_'z@F9} 7>m#tv]@їvޫ{?toGX~0޻[.^Y *5K9hHIۂ缇bl_sWuS]uMWX:G;w 7Bҥn&\7خL#4B}x`%[I=ۍc/CH1?@8OL {1{ fSĀR]:]d|('#`_~[pY5'_A=~Yآ:T6A9yEځ~n|ƣ^^?xϞe_ux)He_mܟ౧ /#.Tz@E_KC897c(ө"f)ib[&IR_pccccc]vڅwi&D;vرQ(@s8BpP`bD:`~EEE@`C~ݺuj*ծXbŊ555̭b׎㵵$ŌLE"bc>ZO@Yh0+PAS3u9,A_goeT@PqKDsb-uۀx"ȇD ӲFݲg^ҋi)PũD\iYfQřك<\Ƴdj6Le㘯xafX D-M$PTGX-Kmmm#qVN-`"T6fYG_׀LQwx?>gw-T6?;M:B_|\Weߙt땣{y#.Dqׯ敛sEs4ΩǍl g^B /~:w//N~&ĭDq}HN3?Ȧ^VʯG_6Ss"2Wr~~~ϢfjR7-:y..߽r3 6+锱G;:o[=hibάb?wBz>ox5֭MnjqMsґg? Imb5gԩȋ:H45 mP;E6 %Fg%mޒґ;2$555$zۡF=ݝ(ΐ$ ;#`D7@("1yۚQF)_5 UWSSs 'TWW\O$C A.̛7o߿eem*++u]߲eː!CI( 逌/q.Cd2 &@$uoEQ^iBX,FFbg԰}gm #aDJ666bZā^ ƹ31SXɦc[az_sLQ/◙gs1lذvȵy7H$UUUhGRTd "e\c~mۆf>o4( %I4-Fx<7QDgRL w )]{.#Ӽ-YrHrk_=FF6*,,s*Ŭ"z9S*3]e푼\v@\H5L9kB: rfBn59%x,^EY\.S\=rʿlxuեOo&< U3zt-Yc-E?b\ħ>7]r%]ki3&t֛jչjb'sv3Q*~iLz{t+x p.;L̦ j-/G"rv6v٬kG1 [Otę:qGʠ/Ś~dɒ%Kt>Ⱓ[)ݰ#PQHNۚ,{H [i+nf6)]{T`붭f9m^GN-zHhmg?%tmZ6M:h7C@$I*싃$I=GREf1d4?'HF ,9644 0EA!ğ%JA)R R8^4)wf$lg1***0+Q#W /V>Qb,<B찞qu(hou? 4F^$I2aG ¸pB׊c(g!CC ޥCQ+{2}wr7'rZqS|JX7G_}}oW: a~ŒUCny7hV^m wB݆;MA< _̞ELC䦿N~re@ed g 1(7@; Xۉ=p~w)+/hW`0DCO&vߟј($GH!r Ry)IilxJvY;)^ee`[(PSUľ _~no&5~v*S#肋<`D3/h*i,W1d'j@q(xū!H]L*Ru1E Ef{E`>N90.ILe7XW pξY hf\ &8KE8" 0(H 4WF:r9ж&og|h7M3g}'- Q6S|պC,ٲ%ߍۏzc9x" +PT9|@qdcpణ/mbh{>rGw BZl?̓ `IDAT>b_6m1_&r "0NS_$8dɕz>--0Av+ݟH;׋xK>_UKm l,uK Ukґ.tUkr8Ru鄣`&1121"\ׯNCQnx.oadvowCqG{^r<`"Nbq2@Ԭql#L3/@2{3f 5 SɸiIsr]ױ/*3[A+'UIqPf&Oј iq}}}߾}۷o&ͨ>$Lz"SYb@8D}̂&;/]|zZ"ofϯp nF01& *I ؀N!Um$~`1M7S8\pZHU|ho-46GJyU?)"o,us6P玻7\~k_cR؁Hl{AZ=6߾ܭ]OZ'f>y. (=Fs؊Xv;/oÍ&m_|t8絇d~'-=1=y W/L& ,{wјx۾~'Zwcƫ]~+K+>[|@;gV{Z6h8H$IcO m] Ch82H+F(t@=4Ξn೽gM5Hlٲ_~/ `YQT#": TqcYX$wqs1ɘMMM(̌Ew܉Rp3,IREEŎT}c\B$A:飱: tj66 !Q x<~_QH$BdP2qqfXo^p`^񵟥CaA<wɄ _`+1_ok٫ԨSmHd=[.ADe6$,ܳQ3aV#2͚1KPLkƳ0"Zg2)3MrB D:#2|ׅ=cny-ql3%7ײ}(PPS G*[tسQuaGF\mO?/OꝖ*Ui3ak}FmXW Wqǜ0坅7%ۃ:HE]{7syq/SSNs=O`duTWgQ*z-yg}Bc~wJ:Rܻ_1jHO $I V**75&}܌$C;:)YQUUqX,r4&@4ߏ{<M6ǃ̈7) '1#,W,uɲ*T2D]tkWUUǃGzX5l֙y$rYT~Qҝ;Zo|`}9f~L>$Dvܑ㐩(&DEGë9Ei=q:u8,ˁIsܟ-'Lxg`Me`7ۜuc=7MI3,? /ÿ}r;'}X`V7fk*(y|ÀD49]Q K&ž{o/|W')_>tO =⦘|b؁VZVVWܽQ`5ٳ-Srjͫ6HCxŜ,Ci$՟5s2R`c@lƆCJJ$ wZw5zȅj$Lq[dYbz' x<!&SxurEϒ>_v̺ I$˛̺Y"]ב4M4MCu" CdSGKirͷs d9Hq֙.H=YC\3w8'`r{A߂Eqf,zBR5~2dAĒgԤ-u!I-l?˨[ȱrO1{W(~-zY+#AWX8 YdC"s7?WxȎG0rnY⾻nⲊbd>~~]ǏF1s<-u[kɦEdDoY>_>*뭫3o[Vü'ڸ~՚fb;#D6EQ^,ˉDvd2X,G"@K3kԡ)t)H0das(&f9K8}~34GpNDڎt"U|Bu+w3lݒ\vG.B8R3m6䋺=~z`L2 fݺ+ ط'ԴyB(R!Poa7jP[G3 e0 \](D Gm z(V2 g5<>F:|3Hx<]0RXQăC*ߗE ƻ,"A~)Jd Hu]]q|=O $E"`0(f,"%3q6f]BD"taF5l4&GY܁ں.\ߑ./߄biFؘ`pᢐ͌be4a !#>*QYfT3Fr:=]Z{1x0}dY4MUU]Qt,kzx3Cıh4LHHh\tD:#kL%e- 44MC;7(z8G -E4j>>bN&)~D V#YLToD&]5drINTq5\pѱaYK"k/˙U-Sr5tEaBd8l`w֑ bdZ&k(L*OuT$/{OUUJHD! }9u= oh0ođ ,F&JdWDR2/r.A$1_L L[1Ӎc?\+.Dfҁ0{t83+oQP ~hn%0np"N~ Ѭ:`RƔ-%if\ECyT1 ]ą >~*'<2f$C-̘utH%J[ǣiTd, GQI0Tj#NEu\5w aVB |RgYf6=MO:P,ae$I `%.f5A< IQc ekt"KOY'*LfMZh+>G e(/A|(Lj`L"#Mt>J-uDGԼ _)[.:#)О>Ws̬W3%UőZVԳEؾuFY3EafNu#E0өM2Kf?)Q聏y^O&$a4T"gኖ|hPa@*((Aapud2kD\3Q8 >`"9Ȥ!uX "*QcLuz,$jIP$qM$I p"  B"^̔+!0K={:l`(APA>YY,9F]tA14̪\,4e92S;ŞcYQ, %KdY1: #I$ٍ-aN'O/X>Ԟb+g~{"2 a{30eL`:3SY>3qs_N goM(5 nov Ӹ^947+sagw|xq^.Wdn,S,\>[;|#|3rnVLM5TSQ28pgcB~ -57o3%.BeN |LWngce2:rQ p0TCohs[#c;Er… .\p… & yRIENDB`yambar-1.11.0/stride.h000066400000000000000000000003061460770427600145240ustar00rootroot00000000000000#pragma once #include static inline int stride_for_format_and_width(pixman_format_code_t format, int width) { return (((PIXMAN_FORMAT_BPP(format) * width + 7) / 8 + 4 - 1) & -4); } yambar-1.11.0/subprojects/000077500000000000000000000000001460770427600154255ustar00rootroot00000000000000yambar-1.11.0/subprojects/fcft.wrap000066400000000000000000000001061460770427600172370ustar00rootroot00000000000000[wrap-git] url = https://codeberg.org/dnkl/fcft.git revision = master yambar-1.11.0/subprojects/tllist.wrap000066400000000000000000000001101460770427600176230ustar00rootroot00000000000000[wrap-git] url = https://codeberg.org/dnkl/tllist.git revision = master yambar-1.11.0/tag.c000066400000000000000000000474161460770427600140150ustar00rootroot00000000000000#include "tag.h" #include #include #include #include #include #include #include #include #define LOG_MODULE "tag" #define LOG_ENABLE_DBG 1 #include "log.h" #include "module.h" struct private { char *name; union { struct { long value; long min; long max; enum tag_realtime_unit realtime_unit; } value_as_int; bool value_as_bool; double value_as_float; char *value_as_string; }; }; static const char * tag_name(const struct tag *tag) { const struct private *priv = tag->private; return priv->name; } static enum tag_type bool_type(const struct tag *tag) { return TAG_TYPE_BOOL; } static enum tag_type int_type(const struct tag *tag) { return TAG_TYPE_INT; } static enum tag_type float_type(const struct tag *tag) { return TAG_TYPE_FLOAT; } static enum tag_type string_type(const struct tag *tag) { return TAG_TYPE_STRING; } static long unimpl_min_max(const struct tag *tag) { return 0; } static enum tag_realtime_unit no_realtime(const struct tag *tag) { return TAG_REALTIME_NONE; } static bool unimpl_refresh_in(const struct tag *tag, long units) { return false; } static void destroy_int_and_float(struct tag *tag) { struct private *priv = tag->private; free(priv->name); free(priv); free(tag); } static void destroy_string(struct tag *tag) { struct private *priv = tag->private; free(priv->value_as_string); destroy_int_and_float(tag); } static long int_min(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.min; } static long int_max(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.max; } static enum tag_realtime_unit int_realtime(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.realtime_unit; } static const char * int_as_string(const struct tag *tag) { static char as_string[128]; const struct private *priv = tag->private; snprintf(as_string, sizeof(as_string), "%ld", priv->value_as_int.value); return as_string; } static long int_as_int(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.value; } static bool int_as_bool(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.value; } static double int_as_float(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_int.value; } static bool int_refresh_in(const struct tag *tag, long units) { const struct private *priv = tag->private; if (priv->value_as_int.realtime_unit == TAG_REALTIME_NONE) return false; if (tag->owner == NULL || tag->owner->refresh_in == NULL) return false; assert(priv->value_as_int.realtime_unit == TAG_REALTIME_SECS || priv->value_as_int.realtime_unit == TAG_REALTIME_MSECS); long milli_seconds = units; if (priv->value_as_int.realtime_unit == TAG_REALTIME_SECS) milli_seconds *= 1000; return tag->owner->refresh_in(tag->owner, milli_seconds); } static const char * bool_as_string(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_bool ? "true" : "false"; } static long bool_as_int(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_bool; } static bool bool_as_bool(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_bool; } static double bool_as_float(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_bool; } static const char * float_as_string(const struct tag *tag) { static char as_string[128]; const struct private *priv = tag->private; snprintf(as_string, sizeof(as_string), "%.2f", priv->value_as_float); return as_string; } static long float_as_int(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_float; } static bool float_as_bool(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_float; } static double float_as_float(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_float; } static const char * string_as_string(const struct tag *tag) { const struct private *priv = tag->private; return priv->value_as_string; } static long string_as_int(const struct tag *tag) { const struct private *priv = tag->private; long value; int matches = sscanf(priv->value_as_string, "%ld", &value); return matches == 1 ? value : 0; } static bool string_as_bool(const struct tag *tag) { const struct private *priv = tag->private; uint8_t value; int matches = sscanf(priv->value_as_string, "%hhu", &value); return matches == 1 ? value : 0; } static double string_as_float(const struct tag *tag) { const struct private *priv = tag->private; double value; int matches = sscanf(priv->value_as_string, "%lf", &value); return matches == 1 ? value : 0; } struct tag * tag_new_int(struct module *owner, const char *name, long value) { return tag_new_int_range(owner, name, value, value, value); } struct tag * tag_new_int_range(struct module *owner, const char *name, long value, long min, long max) { return tag_new_int_realtime(owner, name, value, min, max, TAG_REALTIME_NONE); } struct tag * tag_new_int_realtime(struct module *owner, const char *name, long value, long min, long max, enum tag_realtime_unit unit) { struct private *priv = malloc(sizeof(*priv)); priv->name = strdup(name); priv->value_as_int.value = value; priv->value_as_int.min = min; priv->value_as_int.max = max; priv->value_as_int.realtime_unit = unit; struct tag *tag = malloc(sizeof(*tag)); tag->private = priv; tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; tag->type = &int_type; tag->min = &int_min; tag->max = &int_max; tag->realtime = &int_realtime; tag->refresh_in = &int_refresh_in; tag->as_string = &int_as_string; tag->as_int = &int_as_int; tag->as_bool = &int_as_bool; tag->as_float = &int_as_float; return tag; } struct tag * tag_new_bool(struct module *owner, const char *name, bool value) { struct private *priv = malloc(sizeof(*priv)); priv->name = strdup(name); priv->value_as_bool = value; struct tag *tag = malloc(sizeof(*tag)); tag->private = priv; tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; tag->type = &bool_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; tag->refresh_in = &unimpl_refresh_in; tag->as_string = &bool_as_string; tag->as_int = &bool_as_int; tag->as_bool = &bool_as_bool; tag->as_float = &bool_as_float; return tag; } struct tag * tag_new_float(struct module *owner, const char *name, double value) { struct private *priv = malloc(sizeof(*priv)); priv->name = strdup(name); priv->value_as_float = value; struct tag *tag = malloc(sizeof(*tag)); tag->private = priv; tag->owner = owner; tag->destroy = &destroy_int_and_float; tag->name = &tag_name; tag->type = &float_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; tag->refresh_in = &unimpl_refresh_in; tag->as_string = &float_as_string; tag->as_int = &float_as_int; tag->as_bool = &float_as_bool; tag->as_float = &float_as_float; return tag; } struct tag * tag_new_string(struct module *owner, const char *name, const char *value) { struct private *priv = malloc(sizeof(*priv)); priv->name = strdup(name); priv->value_as_string = value != NULL ? strdup(value) : strdup(""); struct tag *tag = malloc(sizeof(*tag)); tag->private = priv; tag->owner = owner; tag->destroy = &destroy_string; tag->name = &tag_name; tag->type = &string_type; tag->min = &unimpl_min_max; tag->max = &unimpl_min_max; tag->realtime = &no_realtime; tag->refresh_in = &unimpl_refresh_in; tag->as_string = &string_as_string; tag->as_int = &string_as_int; tag->as_bool = &string_as_bool; tag->as_float = &string_as_float; return tag; } const struct tag * tag_for_name(const struct tag_set *set, const char *name) { if (set == NULL) return NULL; for (size_t i = 0; i < set->count; i++) { const struct tag *tag = set->tags[i]; if (strcmp(tag->name(tag), name) == 0) return tag; } return NULL; } void tag_set_destroy(struct tag_set *set) { for (size_t i = 0; i < set->count; i++) set->tags[i]->destroy(set->tags[i]); set->tags = NULL; set->count = 0; } struct sbuf { char *s; size_t size; size_t len; }; static void sbuf_append_at_most(struct sbuf *s1, const char *s2, size_t n) { if (s1->len + n >= s1->size) { size_t required_size = s1->len + n + 1; s1->size = 2 * required_size; s1->s = realloc(s1->s, s1->size); // s1->s[s1->len] = '\0'; } memcpy(&s1->s[s1->len], s2, n); s1->len += n; s1->s[s1->len] = '\0'; } static void sbuf_append(struct sbuf *s1, const char *s2) { sbuf_append_at_most(s1, s2, strlen(s2)); } // stores the number in "*value" on success static bool is_number(const char *str, int *value) { errno = 0; char *end; int v = strtol(str, &end, 10); if (errno != 0 || *end != '\0') return false; *value = v; return true; } char * tags_expand_template(const char *template, const struct tag_set *tags) { if (template == NULL) return NULL; struct sbuf formatted = {0}; while (true) { /* Find next tag opening '{' */ const char *begin = strchr(template, '{'); if (begin == NULL) { /* No more tags, copy remaining characters */ sbuf_append(&formatted, template); break; } /* Find closing '}' */ const char *end = strchr(begin, '}'); if (end == NULL) { /* Wasn't actually a tag, copy as-is instead */ sbuf_append_at_most(&formatted, template, begin - template + 1); template = begin + 1; continue; } /* Extract tag name + argument*/ char tag_name_and_arg[end - begin]; strncpy(tag_name_and_arg, begin + 1, end - begin - 1); tag_name_and_arg[end - begin - 1] = '\0'; static const size_t MAX_TAG_ARGS = 4; const char *tag_name = NULL; const char *tag_args[MAX_TAG_ARGS]; memset(tag_args, 0, sizeof(tag_args)); { char *saveptr; tag_name = strtok_r(tag_name_and_arg, ":", &saveptr); for (size_t i = 0; i < MAX_TAG_ARGS; i++) { const char *arg = strtok_r(NULL, ":", &saveptr); if (arg == NULL) break; tag_args[i] = arg; } } /* Lookup tag */ const struct tag *tag = NULL; if (tag_name == NULL || (tag = tag_for_name(tags, tag_name)) == NULL) { /* No such tag, copy as-is instead */ sbuf_append_at_most(&formatted, template, begin - template + 1); template = begin + 1; continue; } /* Copy characters preceding the tag (name) */ sbuf_append_at_most(&formatted, template, begin - template); /* Parse arguments */ enum { FMT_DEFAULT, FMT_HEX, FMT_OCT, FMT_PERCENT, FMT_KBYTE, FMT_MBYTE, FMT_GBYTE, FMT_KIBYTE, FMT_MIBYTE, FMT_GIBYTE, } format = FMT_DEFAULT; enum { VALUE_VALUE, VALUE_MIN, VALUE_MAX, VALUE_UNIT, } kind = VALUE_VALUE; int digits = 0; int decimals = 2; bool zero_pad = false; char *point = NULL; for (size_t i = 0; i < MAX_TAG_ARGS; i++) { if (tag_args[i] == NULL) break; else if (strcmp(tag_args[i], "hex") == 0) format = FMT_HEX; else if (strcmp(tag_args[i], "oct") == 0) format = FMT_OCT; else if (strcmp(tag_args[i], "%") == 0) format = FMT_PERCENT; else if (strcmp(tag_args[i], "kb") == 0) format = FMT_KBYTE; else if (strcmp(tag_args[i], "mb") == 0) format = FMT_MBYTE; else if (strcmp(tag_args[i], "gb") == 0) format = FMT_GBYTE; else if (strcmp(tag_args[i], "kib") == 0) format = FMT_KIBYTE; else if (strcmp(tag_args[i], "mib") == 0) format = FMT_MIBYTE; else if (strcmp(tag_args[i], "gib") == 0) format = FMT_GIBYTE; else if (strcmp(tag_args[i], "min") == 0) kind = VALUE_MIN; else if (strcmp(tag_args[i], "max") == 0) kind = VALUE_MAX; else if (strcmp(tag_args[i], "unit") == 0) kind = VALUE_UNIT; else if (is_number(tag_args[i], &digits)) // i.e.: "{tag:3}" zero_pad = tag_args[i][0] == '0'; else if ((point = strchr(tag_args[i], '.')) != NULL) { *point = '\0'; const char *digits_str = tag_args[i]; const char *decimals_str = point + 1; if (digits_str[0] != '\0') { // guards against i.e. "{tag:.3}" if (!is_number(digits_str, &digits)) { LOG_WARN("tag `%s`: invalid field width formatter. Ignoring...", tag_name); } } if (decimals_str[0] != '\0') { // guards against i.e. "{tag:3.}" if (!is_number(decimals_str, &decimals)) { LOG_WARN("tag `%s`: invalid decimals formatter. Ignoring...", tag_name); } } zero_pad = digits_str[0] == '0'; } else LOG_WARN("invalid tag formatter: %s", tag_args[i]); } /* Copy tag value */ switch (kind) { case VALUE_VALUE: switch (format) { case FMT_DEFAULT: { switch (tag->type(tag)) { case TAG_TYPE_FLOAT: { const char *fmt = zero_pad ? "%0*.*f" : "%*.*f"; char str[24]; snprintf(str, sizeof(str), fmt, digits, decimals, tag->as_float(tag)); sbuf_append(&formatted, str); break; } case TAG_TYPE_INT: { const char *fmt = zero_pad ? "%0*ld" : "%*ld"; char str[24]; snprintf(str, sizeof(str), fmt, digits, tag->as_int(tag)); sbuf_append(&formatted, str); break; } default: sbuf_append(&formatted, tag->as_string(tag)); break; } break; } case FMT_HEX: case FMT_OCT: { const char *fmt = format == FMT_HEX ? zero_pad ? "%0*lx" : "%*lx" : zero_pad ? "%0*lo" : "%*lo"; char str[24]; snprintf(str, sizeof(str), fmt, digits, tag->as_int(tag)); sbuf_append(&formatted, str); break; } case FMT_PERCENT: { const long min = tag->min(tag); const long max = tag->max(tag); const long cur = tag->as_int(tag); const char *fmt = zero_pad ? "%0*lu" : "%*lu"; char str[4]; snprintf(str, sizeof(str), fmt, digits, (cur - min) * 100 / (max - min)); sbuf_append(&formatted, str); break; } case FMT_KBYTE: case FMT_MBYTE: case FMT_GBYTE: case FMT_KIBYTE: case FMT_MIBYTE: case FMT_GIBYTE: { const long divider = format == FMT_KBYTE ? 1000 : format == FMT_MBYTE ? 1000 * 1000 : format == FMT_GBYTE ? 1000 * 1000 * 1000 : format == FMT_KIBYTE ? 1024 : format == FMT_MIBYTE ? 1024 * 1024 : format == FMT_GIBYTE ? 1024 * 1024 * 1024 : 1; char str[24]; if (tag->type(tag) == TAG_TYPE_FLOAT) { const char *fmt = zero_pad ? "%0*.*f" : "%*.*f"; snprintf(str, sizeof(str), fmt, digits, decimals, tag->as_float(tag) / (double)divider); } else { const char *fmt = zero_pad ? "%0*lu" : "%*lu"; snprintf(str, sizeof(str), fmt, digits, tag->as_int(tag) / divider); } sbuf_append(&formatted, str); break; } } break; case VALUE_MIN: case VALUE_MAX: { const long min = tag->min(tag); const long max = tag->max(tag); long value = kind == VALUE_MIN ? min : max; const char *fmt = NULL; switch (format) { case FMT_DEFAULT: fmt = zero_pad ? "%0*ld" : "%*ld"; break; case FMT_HEX: fmt = zero_pad ? "%0*lx" : "%*lx"; break; case FMT_OCT: fmt = zero_pad ? "%0*lo" : "%*lo"; break; case FMT_PERCENT: value = (value - min) * 100 / (max - min); fmt = zero_pad ? "%0*lu" : "%*lu"; break; case FMT_KBYTE: case FMT_MBYTE: case FMT_GBYTE: case FMT_KIBYTE: case FMT_MIBYTE: case FMT_GIBYTE: { const long divider = format == FMT_KBYTE ? 1024 : format == FMT_MBYTE ? 1024 * 1024 : format == FMT_GBYTE ? 1024 * 1024 * 1024 : format == FMT_KIBYTE ? 1000 : format == FMT_MIBYTE ? 1000 * 1000 : format == FMT_GIBYTE ? 1000 * 1000 * 1000 : 1; value /= divider; fmt = zero_pad ? "%0*lu" : "%*lu"; break; } } assert(fmt != NULL); char str[24]; snprintf(str, sizeof(str), fmt, digits, value); sbuf_append(&formatted, str); break; } case VALUE_UNIT: { const char *value = NULL; switch (tag->realtime(tag)) { case TAG_REALTIME_NONE: value = ""; break; case TAG_REALTIME_SECS: value = "s"; break; case TAG_REALTIME_MSECS: value = "ms"; break; } sbuf_append(&formatted, value); break; } } /* Skip past tag name + closing '}' */ template = end + 1; } return formatted.s; } void tags_expand_templates(char *expanded[], const char *template[], size_t nmemb, const struct tag_set *tags) { for (size_t i = 0; i < nmemb; i++) expanded[i] = tags_expand_template(template[i], tags); } yambar-1.11.0/tag.h000066400000000000000000000034731460770427600140150ustar00rootroot00000000000000#pragma once #include #include enum tag_type { TAG_TYPE_BOOL, TAG_TYPE_INT, TAG_TYPE_FLOAT, TAG_TYPE_STRING, }; enum tag_realtime_unit { TAG_REALTIME_NONE, TAG_REALTIME_SECS, TAG_REALTIME_MSECS, }; struct module; struct tag { void *private; struct module *owner; void (*destroy)(struct tag *tag); const char *(*name)(const struct tag *tag); enum tag_type (*type)(const struct tag *tag); const char *(*as_string)(const struct tag *tag); long (*as_int)(const struct tag *tag); bool (*as_bool)(const struct tag *tag); double (*as_float)(const struct tag *tag); long (*min)(const struct tag *tag); long (*max)(const struct tag *tag); enum tag_realtime_unit (*realtime)(const struct tag *tag); bool (*refresh_in)(const struct tag *tag, long units); }; struct tag_set { struct tag **tags; size_t count; }; struct tag *tag_new_int(struct module *owner, const char *name, long value); struct tag *tag_new_int_range(struct module *owner, const char *name, long value, long min, long max); struct tag *tag_new_int_realtime(struct module *owner, const char *name, long value, long min, long max, enum tag_realtime_unit unit); struct tag *tag_new_bool(struct module *owner, const char *name, bool value); struct tag *tag_new_float(struct module *owner, const char *name, double value); struct tag *tag_new_string(struct module *owner, const char *name, const char *value); const struct tag *tag_for_name(const struct tag_set *set, const char *name); void tag_set_destroy(struct tag_set *set); /* Utility functions */ char *tags_expand_template(const char *template, const struct tag_set *tags); void tags_expand_templates(char *expanded[], const char *template[], size_t nmemb, const struct tag_set *tags); yambar-1.11.0/test/000077500000000000000000000000001460770427600140415ustar00rootroot00000000000000yambar-1.11.0/test/full-conf-good.yml000066400000000000000000000052351460770427600174040ustar00rootroot00000000000000bar: height: 10 location: bottom spacing: 3 margin: 2 monitor: LVDS-1 font: monospace foreground: ffffffff background: 000000ff border: width: 1 color: 77777777 margin: 7 # left: focus on modules left: - alsa: card: Default mixer: Master content: {string: {text: "{volume}"}} - backlight: name: intel_backlight content: {string: {text: "{percent}"}} - battery: name: BAT0 content: {string: {text: "{capacity}"}} - clock: content: {string: {text: "{date} {time}"}} - i3: content: "": {string: {text: "{name}"}} ws: {string: {text: WS}} - label: content: {string: {text: hello}} - mpd: host: 127.0.0.1 content: {string: {text: "{state}"}} - network: content: map: default: string: {text: "{name}: {state} ({ipv4})"} conditions: ipv4 == "": string: {text: "{name}: {state}"} - removables: content: {string: {text: "{label}"}} # - xkb: # content: {string: {text: "{name}"}} # - xwindow: # content: {string: {text: "{application}: {title}"}} # center: focus on particles center: - clock: {content: {empty: {}}} - clock: content: [string: {text: hello}, string: {text: world}] - clock: content: list: items: - string: {text: hello} - string: {text: world} - clock: content: map: default: {string: {text: default value}} conditions: date == 1234: {string: {text: specific value}} - clock: content: progress-bar: tag: date length: 20 start: {string: {text: START}} end: {string: {text: START}} fill: {string: {text: FILL}} empty: {string: {text: EMPTY}} indicator: {string: {text: INDICATOR}} - clock: content: ramp: tag: date # date isn't a range tag... items: [string: {text: value}] # right: focus on decorations right: - clock: content: string: text: background deco deco: {background: {color: ffffffff}} - clock: content: string: text: underline deco deco: {underline: {size: 2, color: ffffffff}} - clock: content: string: text: stacked deco deco: stack: - underline: {size: 2, color: ffffffff} - background: {color: ffffffff} yambar-1.11.0/test/meson.build000066400000000000000000000007171460770427600162100ustar00rootroot00000000000000test('is-executable', yambar, args: ['--version']) pwd = meson.current_source_dir() # Configuration validation tests test('no-config', yambar, args: ['-C', '-c', 'xyz'], should_fail: true) test('config-isnt-file', yambar, args: ['-C', '-c', '.'], should_fail: true) test('config-no-bar', yambar, args: ['-C', '-c', join_paths(pwd, 'no-bar.yml')], should_fail: true) test('full-conf-good', yambar, args: ['-C', '-c', join_paths(pwd, 'full-conf-good.yml')]) yambar-1.11.0/test/no-bar.yml000066400000000000000000000000461460770427600157420ustar00rootroot00000000000000top-level-isnt-bar: hello: world yambar-1.11.0/xcb.c000066400000000000000000000142141460770427600140040ustar00rootroot00000000000000#include "xcb.h" #include #include #include #include #include #include #include #if defined(HAVE_XCB_ERRORS) #include #endif #define LOG_MODULE "xcb" #define LOG_ENABLE_DBG 0 #include "log.h" xcb_atom_t UTF8_STRING; xcb_atom_t _NET_WM_PID; xcb_atom_t _NET_WM_WINDOW_TYPE; xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK; xcb_atom_t _NET_WM_STATE; xcb_atom_t _NET_WM_STATE_ABOVE; xcb_atom_t _NET_WM_STATE_STICKY; xcb_atom_t _NET_WM_DESKTOP; xcb_atom_t _NET_WM_STRUT; xcb_atom_t _NET_WM_STRUT_PARTIAL; xcb_atom_t _NET_ACTIVE_WINDOW; xcb_atom_t _NET_CURRENT_DESKTOP; xcb_atom_t _NET_WM_VISIBLE_NAME; xcb_atom_t _NET_WM_NAME; #if defined(HAVE_XCB_ERRORS) static xcb_errors_context_t *err_context; #endif static void __attribute__((destructor)) fini(void) { #if defined(HAVE_XCB_ERRORS) xcb_errors_context_free(err_context); #endif } bool xcb_init(void) { xcb_connection_t *conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(conn) > 0) { LOG_ERR("failed to connect to X"); xcb_disconnect(conn); return false; } #if defined(HAVE_XCB_ERRORS) xcb_errors_context_new(conn, &err_context); #endif #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG const xcb_setup_t *setup = xcb_get_setup(conn); /* Vendor release number */ unsigned release = setup->release_number; unsigned major = release / 10000000; release %= 10000000; unsigned minor = release / 100000; release %= 100000; unsigned patch = release / 1000; #endif LOG_DBG("%.*s %u.%u.%u (protocol: %u.%u)", xcb_setup_vendor_length(setup), xcb_setup_vendor(setup), major, minor, patch, setup->protocol_major_version, setup->protocol_minor_version); const xcb_query_extension_reply_t *randr = xcb_get_extension_data(conn, &xcb_randr_id); if (randr == NULL || !randr->present) { LOG_ERR("RANDR extension not present"); xcb_disconnect(conn); return false; } const xcb_query_extension_reply_t *render = xcb_get_extension_data(conn, &xcb_render_id); if (render == NULL || !render->present) { LOG_ERR("RENDER extension not present"); xcb_disconnect(conn); return false; } xcb_randr_query_version_cookie_t randr_cookie = xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION); xcb_render_query_version_cookie_t render_cookie = xcb_render_query_version(conn, XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION); xcb_flush(conn); xcb_generic_error_t *e; xcb_randr_query_version_reply_t *randr_version = xcb_randr_query_version_reply(conn, randr_cookie, &e); if (e != NULL) { LOG_ERR("failed to query RANDR version: %s", xcb_error(e)); free(e); xcb_disconnect(conn); return false; } xcb_render_query_version_reply_t *render_version = xcb_render_query_version_reply(conn, render_cookie, &e); if (e != NULL) { LOG_ERR("failed to query RENDER version: %s", xcb_error(e)); free(e); xcb_disconnect(conn); return false; } LOG_DBG("RANDR: %u.%u", randr_version->major_version, randr_version->minor_version); LOG_DBG("RENDER: %u.%u", render_version->major_version, render_version->minor_version); free(randr_version); free(render_version); /* Cache atoms */ UTF8_STRING = get_atom(conn, "UTF8_STRING"); _NET_WM_PID = get_atom(conn, "_NET_WM_PID"); _NET_WM_WINDOW_TYPE = get_atom(conn, "_NET_WM_WINDOW_TYPE"); _NET_WM_WINDOW_TYPE_DOCK = get_atom(conn, "_NET_WM_WINDOW_TYPE_DOCK"); _NET_WM_STATE = get_atom(conn, "_NET_WM_STATE"); _NET_WM_STATE_ABOVE = get_atom(conn, "_NET_WM_STATE_ABOVE"); _NET_WM_STATE_STICKY = get_atom(conn, "_NET_WM_STATE_STICKY"); _NET_WM_DESKTOP = get_atom(conn, "_NET_WM_DESKTOP"); _NET_WM_STRUT = get_atom(conn, "_NET_WM_STRUT"); _NET_WM_STRUT_PARTIAL = get_atom(conn, "_NET_WM_STRUT_PARTIAL"); _NET_ACTIVE_WINDOW = get_atom(conn, "_NET_ACTIVE_WINDOW"); _NET_CURRENT_DESKTOP = get_atom(conn, "_NET_CURRENT_DESKTOP"); _NET_WM_VISIBLE_NAME = get_atom(conn, "_NET_WM_VISIBLE_NAME"); _NET_WM_NAME = get_atom(conn, "_NET_WM_NAME"); _NET_WM_PID = get_atom(conn, "_NET_WM_PID"); xcb_disconnect(conn); return true; } xcb_atom_t get_atom(xcb_connection_t *conn, const char *name) { xcb_generic_error_t *e; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen(name), name), &e); if (e != NULL) { LOG_ERR("%s: failed to get atom for %s", name, xcb_error(e)); free(e); free(reply); return (xcb_atom_t){0}; } xcb_atom_t ret = reply->atom; LOG_DBG("atom %s = 0x%08x", name, ret); if (ret == XCB_ATOM_NONE) LOG_ERR("%s: no such atom", name); assert(ret != XCB_ATOM_NONE); free(reply); return ret; } char * get_atom_name(xcb_connection_t *conn, xcb_atom_t atom) { xcb_generic_error_t *e; xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(conn, xcb_get_atom_name(conn, atom), &e); if (e != NULL) { LOG_ERR("failed to get atom name: %s", xcb_error(e)); free(e); free(reply); return NULL; } char *name = strndup(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)); LOG_DBG("atom name: %s", name); free(reply); return name; } const char * xcb_error(const xcb_generic_error_t *error) { static char msg[1024]; #if defined(HAVE_XCB_ERRORS) const char *major = xcb_errors_get_name_for_major_code(err_context, error->major_code); const char *minor = xcb_errors_get_name_for_minor_code(err_context, error->major_code, error->minor_code); const char *extension; const char *name = xcb_errors_get_name_for_error(err_context, error->error_code, &extension); snprintf(msg, sizeof(msg), "major=%s, minor=%s), code=%s, extension=%s, sequence=%u", major, minor, name, extension, error->sequence); #else snprintf(msg, sizeof(msg), "op %hhu:%hu, code %hhu, sequence %hu", error->major_code, error->minor_code, error->error_code, error->sequence); #endif return msg; } yambar-1.11.0/xcb.h000066400000000000000000000014361460770427600140130ustar00rootroot00000000000000#pragma once #include #include bool xcb_init(void); xcb_atom_t get_atom(xcb_connection_t *conn, const char *name); char *get_atom_name(xcb_connection_t *conn, xcb_atom_t atom); const char *xcb_error(const xcb_generic_error_t *error); /* Cached atoms */ extern xcb_atom_t UTF8_STRING; extern xcb_atom_t _NET_WM_PID; extern xcb_atom_t _NET_WM_WINDOW_TYPE; extern xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK; extern xcb_atom_t _NET_WM_STATE; extern xcb_atom_t _NET_WM_STATE_ABOVE; extern xcb_atom_t _NET_WM_STATE_STICKY; extern xcb_atom_t _NET_WM_DESKTOP; extern xcb_atom_t _NET_WM_STRUT; extern xcb_atom_t _NET_WM_STRUT_PARTIAL; extern xcb_atom_t _NET_ACTIVE_WINDOW; extern xcb_atom_t _NET_CURRENT_DESKTOP; extern xcb_atom_t _NET_WM_VISIBLE_NAME; extern xcb_atom_t _NET_WM_NAME; yambar-1.11.0/yambar.desktop000066400000000000000000000003211460770427600157240ustar00rootroot00000000000000[Desktop Entry] Type=Application Exec=yambar Icon=panel Terminal=false Categories=System;Monitor;Core Name=Yambar GenericName=Panel Comment=Configurable statusbar to show time, battery capacity and much more yambar-1.11.0/yml.c000066400000000000000000000556321460770427600140420ustar00rootroot00000000000000#include "yml.h" #include #include #include #include #include #include #include #define UNUSED __attribute__((unused)) enum yml_error { YML_ERR_NONE, YML_ERR_DUPLICATE_KEY, YML_ERR_INVALID_ANCHOR, YML_ERR_UNKNOWN, }; enum node_type { ROOT, SCALAR, DICT, LIST, }; struct yml_node; struct dict_pair { struct yml_node *key; struct yml_node *value; }; struct anchor_map { char *anchor; const struct yml_node *node; }; struct yml_node { enum node_type type; union { struct { struct yml_node *root; struct anchor_map anchors[100]; /* TODO: dynamic resize */ size_t anchor_count; } root; struct { char *value; } scalar; struct { tll(struct dict_pair) pairs; bool next_is_value; } dict; struct { tll(struct yml_node *) values; } list; }; size_t line; size_t column; struct yml_node *parent; }; static struct yml_node * clone_node(struct yml_node *parent, const struct yml_node *node) { struct yml_node *clone = calloc(1, sizeof(*clone)); clone->type = node->type; clone->line = node->line; clone->column = node->column; clone->parent = parent; switch (node->type) { case SCALAR: clone->scalar.value = strdup(node->scalar.value); break; case DICT: tll_foreach(node->dict.pairs, it) { struct dict_pair p = { .key = clone_node(clone, it->item.key), .value = clone_node(clone, it->item.value), }; tll_push_back(clone->dict.pairs, p); } break; case LIST: tll_foreach(node->list.values, it) tll_push_back(clone->list.values, clone_node(clone, it->item)); break; case ROOT: assert(false); break; } return clone; } static bool node_equal(const struct yml_node *a, const struct yml_node *b) { if (a->type != b->type) return false; if (a->type != SCALAR) { /* TODO... */ return false; } return strcmp(a->scalar.value, b->scalar.value) == 0; } static bool dict_has_key(const struct yml_node *node, const struct yml_node *key) { assert(node->type == DICT); tll_foreach(node->dict.pairs, pair) { if (node_equal(pair->item.key, key)) return true; } return false; } static enum yml_error add_node(struct yml_node *parent, struct yml_node *new_node, yaml_mark_t loc) { new_node->line = loc.line + 1; /* yaml uses 0-based line numbers */ new_node->column = loc.column; switch (parent->type) { case ROOT: assert(parent->root.root == NULL); parent->root.root = new_node; new_node->parent = parent; break; case DICT: if (!parent->dict.next_is_value) { if (dict_has_key(parent, new_node)) return YML_ERR_DUPLICATE_KEY; tll_push_back(parent->dict.pairs, (struct dict_pair){.key = new_node}); parent->dict.next_is_value = true; } else { tll_back(parent->dict.pairs).value = new_node; parent->dict.next_is_value = false; } new_node->parent = parent; break; case LIST: tll_push_back(parent->list.values, new_node); new_node->parent = parent; break; case SCALAR: assert(false); return YML_ERR_UNKNOWN; } return YML_ERR_NONE; } static void add_anchor(struct yml_node *root, const char *anchor, const struct yml_node *node) { assert(root->type == ROOT); struct anchor_map *map = &root->root.anchors[root->root.anchor_count]; map->anchor = strdup(anchor); map->node = node; root->root.anchor_count++; } static bool post_process(struct yml_node *node, char **error) { switch (node->type) { case ROOT: if (node->root.root != NULL) if (!post_process(node->root.root, error)) return false; break; case SCALAR: // assert(strcmp(node->scalar.value, "<<") != 0); break; case LIST: tll_foreach(node->list.values, it) if (!post_process(it->item, error)) return false; break; case DICT: tll_foreach(node->dict.pairs, it) { if (!post_process(it->item.key, error) || !post_process(it->item.value, error)) { return false; } } tll_foreach(node->dict.pairs, it) { if (it->item.key->type != SCALAR) continue; if (strcmp(it->item.key->scalar.value, "<<") != 0) continue; if (it->item.value->type == LIST) { /* * Merge value is a list (of dictionaries) * e.g. <<: [*foo, *bar] */ tll_foreach(it->item.value->list.values, v_it) { if (v_it->item->type != DICT) { int cnt = snprintf(NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor", v_it->item->line, v_it->item->column); *error = malloc(cnt + 1); snprintf(*error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor", v_it->item->line, v_it->item->column); return false; } tll_foreach(v_it->item->dict.pairs, vv_it) { struct dict_pair p = { .key = vv_it->item.key, .value = vv_it->item.value, }; if (dict_has_key(node, vv_it->item.key)) { /* Prefer value in target dictionary, over the * value from the anchor */ yml_destroy(vv_it->item.key); yml_destroy(vv_it->item.value); } else { tll_push_back(node->dict.pairs, p); } } /* Destroy list, but don't free (since its nodes * have been moved to this node), *before* * destroying the key/value nodes. This ensures * the dict nodes aren't free:d in the * yml_destroy() below). */ tll_free(v_it->item->dict.pairs); } } else { /* * Merge value is a dictionary only * e.g. <<: *foo */ if (it->item.value->type != DICT) { int cnt = snprintf(NULL, 0, "%zu:%zu: cannot merge non-dictionary anchor", it->item.value->line, it->item.value->column); *error = malloc(cnt + 1); snprintf(*error, cnt + 1, "%zu:%zu: cannot merge non-dictionary anchor", it->item.value->line, it->item.value->column); return false; } tll_foreach(it->item.value->dict.pairs, v_it) { struct dict_pair p = { .key = v_it->item.key, .value = v_it->item.value, }; if (dict_has_key(node, v_it->item.key)) { /* Prefer value in target dictionary, over the * value from the anchor */ yml_destroy(v_it->item.key); yml_destroy(v_it->item.value); } else { tll_push_back(node->dict.pairs, p); } } /* Destroy list here, *without* freeing nodes (since * nodes have been moved to this node), *before* * destroying the key/value nodes. This ensures the * dict nodes aren't free:d in the yml_destroy() * below */ tll_free(it->item.value->dict.pairs); } yml_destroy(it->item.key); yml_destroy(it->item.value); tll_remove(node->dict.pairs, it); } break; } return true; } static const char * format_error(enum yml_error err, const struct yml_node *parent, const struct yml_node *node, const char *anchor) { static char err_str[512]; switch (err) { case YML_ERR_NONE: assert(false); break; case YML_ERR_DUPLICATE_KEY: { /* Find parent's key (i.e its name) */ if (parent->parent != NULL && parent->parent->type == DICT && node->type == SCALAR) { tll_foreach(parent->parent->dict.pairs, pair) { if (pair->item.value != parent) continue; if (pair->item.key->type != SCALAR) break; assert(pair->item.key->type == SCALAR); assert(node->type == SCALAR); snprintf(err_str, sizeof(err_str), "%s: duplicate key: '%s'", pair->item.key->scalar.value, node->scalar.value); return err_str; } } if (node->type == SCALAR) { snprintf(err_str, sizeof(err_str), "duplicate key: %s", node->scalar.value); } else snprintf(err_str, sizeof(err_str), "duplicate key"); break; } case YML_ERR_INVALID_ANCHOR: if (parent->parent != NULL && parent->parent->type == DICT) { tll_foreach(parent->parent->dict.pairs, pair) { if (pair->item.value != parent) continue; if (pair->item.key->type != SCALAR) break; snprintf(err_str, sizeof(err_str), "%s: invalid anchor: %s", pair->item.key->scalar.value, anchor != NULL ? anchor : ""); return err_str; } } snprintf(err_str, sizeof(err_str), "invalid anchor: %s", anchor != NULL ? anchor : ""); break; case YML_ERR_UNKNOWN: snprintf(err_str, sizeof(err_str), "unknown error"); break; } return err_str; } struct yml_node * yml_load(FILE *yml, char **error) { yaml_parser_t yaml; yaml_parser_initialize(&yaml); yaml_parser_set_input_file(&yaml, yml); bool done = false; int indent UNUSED = 0; struct yml_node *root = malloc(sizeof(*root)); root->type = ROOT; root->root.root = NULL; root->root.anchor_count = 0; struct yml_node *n = root; const char *error_str = NULL; while (!done) { yaml_event_t event; if (!yaml_parser_parse(&yaml, &event)) { if (error != NULL) { int cnt = snprintf(NULL, 0, "%zu:%zu: %s %s", yaml.problem_mark.line + 1, yaml.problem_mark.column, yaml.problem, yaml.context != NULL ? yaml.context : ""); *error = malloc(cnt + 1); snprintf(*error, cnt + 1, "%zu:%zu: %s %s", yaml.problem_mark.line + 1, yaml.problem_mark.column, yaml.problem, yaml.context != NULL ? yaml.context : ""); } goto err_no_error_formatting; } switch (event.type) { case YAML_NO_EVENT: break; case YAML_STREAM_START_EVENT: indent += 2; break; case YAML_STREAM_END_EVENT: indent -= 2; done = true; break; case YAML_DOCUMENT_START_EVENT: indent += 2; break; case YAML_DOCUMENT_END_EVENT: indent -= 2; break; case YAML_ALIAS_EVENT: { bool got_match = false; for (size_t i = 0; i < root->root.anchor_count; i++) { const struct anchor_map *map = &root->root.anchors[i]; if (strcmp(map->anchor, (const char *)event.data.alias.anchor) != 0) continue; struct yml_node *clone = clone_node(NULL, map->node); assert(clone != NULL); enum yml_error err = add_node(n, clone, event.start_mark); if (err != YML_ERR_NONE) { error_str = format_error(err, n, clone, NULL); yml_destroy(clone); yaml_event_delete(&event); goto err; } got_match = true; break; } if (!got_match) { error_str = format_error(YML_ERR_INVALID_ANCHOR, n, NULL, (const char *)event.data.alias.anchor); yaml_event_delete(&event); goto err; } break; } case YAML_SCALAR_EVENT: { struct yml_node *new_scalar = calloc(1, sizeof(*new_scalar)); new_scalar->type = SCALAR; new_scalar->scalar.value = strndup((const char *)event.data.scalar.value, event.data.scalar.length); enum yml_error err = add_node(n, new_scalar, event.start_mark); if (err != YML_ERR_NONE) { error_str = format_error(err, n, new_scalar, NULL); yml_destroy(new_scalar); yaml_event_delete(&event); goto err; } if (event.data.scalar.anchor != NULL) { const char *anchor = (const char *)event.data.scalar.anchor; add_anchor(root, anchor, new_scalar); } break; } case YAML_SEQUENCE_START_EVENT: { indent += 2; struct yml_node *new_list = calloc(1, sizeof(*new_list)); new_list->type = LIST; enum yml_error err = add_node(n, new_list, event.start_mark); if (err != YML_ERR_NONE) { error_str = format_error(err, n, new_list, NULL); yml_destroy(new_list); yaml_event_delete(&event); goto err; } n = new_list; if (event.data.sequence_start.anchor != NULL) { const char *anchor = (const char *)event.data.sequence_start.anchor; add_anchor(root, anchor, new_list); } break; } case YAML_SEQUENCE_END_EVENT: indent -= 2; assert(n->parent != NULL); n = n->parent; break; case YAML_MAPPING_START_EVENT: { indent += 2; struct yml_node *new_dict = calloc(1, sizeof(*new_dict)); new_dict->type = DICT; enum yml_error err = add_node(n, new_dict, event.start_mark); if (err != YML_ERR_NONE) { error_str = format_error(err, n, new_dict, NULL); yml_destroy(new_dict); yaml_event_delete(&event); goto err; } n = new_dict; if (event.data.mapping_start.anchor != NULL) { const char *anchor = (const char *)event.data.mapping_start.anchor; add_anchor(root, anchor, new_dict); } break; } case YAML_MAPPING_END_EVENT: assert(!n->dict.next_is_value); indent -= 2; assert(n->parent != NULL); n = n->parent; break; } yaml_event_delete(&event); } yaml_parser_delete(&yaml); if (!post_process(root, error)) { yml_destroy(root); return NULL; } return root; err: if (error_str != NULL) { int cnt = snprintf(NULL, 0, "%zu:%zu: %s", yaml.mark.line + 1, yaml.mark.column, error_str); *error = malloc(cnt + 1); snprintf(*error, cnt + 1, "%zu:%zu: %s", yaml.mark.line + 1, yaml.mark.column, error_str); } else { int cnt = snprintf(NULL, 0, "%zu:%zu: unknown error", yaml.mark.line + 1, yaml.mark.column); *error = malloc(cnt + 1); snprintf(*error, cnt + 1, "%zu:%zu: unknown error", yaml.mark.line + 1, yaml.mark.column); } err_no_error_formatting: yml_destroy(root); yaml_parser_delete(&yaml); return NULL; } void yml_destroy(struct yml_node *node) { if (node == NULL) return; switch (node->type) { case ROOT: yml_destroy(node->root.root); for (size_t i = 0; i < node->root.anchor_count; i++) free(node->root.anchors[i].anchor); break; case SCALAR: free(node->scalar.value); break; case LIST: tll_free_and_free(node->list.values, yml_destroy); break; case DICT: tll_foreach(node->dict.pairs, it) { yml_destroy(it->item.key); yml_destroy(it->item.value); } tll_free(node->dict.pairs); break; } free(node); } bool yml_is_scalar(const struct yml_node *node) { return node->type == SCALAR; } bool yml_is_dict(const struct yml_node *node) { return node->type == DICT; } bool yml_is_list(const struct yml_node *node) { return node->type == LIST; } static struct yml_node const * yml_get_(struct yml_node const *node, char const *_path, bool value) { /* value: true for value, false for key */ if (node != NULL && node->type == ROOT) node = node->root.root; if (node == NULL) return NULL; char *path = strdup(_path); for (const char *part = strtok(path, "."), *next_part = strtok(NULL, "."); part != NULL; part = next_part, next_part = strtok(NULL, ".")) { assert(yml_is_dict(node)); tll_foreach(node->dict.pairs, it) { assert(yml_is_scalar(it->item.key)); if (strcmp(it->item.key->scalar.value, part) == 0) { if (next_part == NULL) { free(path); if (value) return it->item.value; else return it->item.key; } node = it->item.value; break; } } } free(path); return NULL; } const struct yml_node * yml_get_value(const struct yml_node *node, const char *_path) { return yml_get_(node, _path, true); } struct yml_node const * yml_get_key(struct yml_node const *node, char const *_path) { return yml_get_(node, _path, false); } struct yml_list_iter yml_list_iter(const struct yml_node *list) { assert(yml_is_list(list)); tll_foreach(list->list.values, it) { return (struct yml_list_iter){ .node = it->item, .private = it, }; } return (struct yml_list_iter){ .node = NULL, .private = NULL, }; } void yml_list_next(struct yml_list_iter *iter) { if (iter->private == NULL) return; const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef; __typeof__(d->list.values.head) it = (__typeof__(d->list.values.head))iter->private; __typeof__(d->list.values.head) next = it->next; iter->node = next != NULL ? next->item : NULL; iter->private = next; } size_t yml_list_length(const struct yml_node *list) { assert(yml_is_list(list)); size_t length = 0; for (struct yml_list_iter it = yml_list_iter(list); it.node != NULL; yml_list_next(&it), length++) ; return length; } struct yml_dict_iter yml_dict_iter(const struct yml_node *dict) { assert(yml_is_dict(dict)); tll_foreach(dict->dict.pairs, it) { return (struct yml_dict_iter){ .key = it->item.key, .value = it->item.value, .private1 = it, }; } return (struct yml_dict_iter){ .key = NULL, .value = NULL, .private1 = NULL, }; } void yml_dict_next(struct yml_dict_iter *iter) { const struct yml_node *d = (const void *)(uintptr_t)0xdeadbeef; __typeof__(d->dict.pairs.head) it = (__typeof__(d->dict.pairs.head))iter->private1; if (it == NULL) return; __typeof__(d->dict.pairs.head) next = it->next; iter->key = next != NULL ? next->item.key : NULL; iter->value = next != NULL ? next->item.value : NULL; iter->private1 = next; } size_t yml_dict_length(const struct yml_node *dict) { assert(yml_is_dict(dict)); return tll_length(dict->dict.pairs); } const char * yml_value_as_string(const struct yml_node *value) { if (!yml_is_scalar(value)) return NULL; return value->scalar.value; } static bool _as_int(const struct yml_node *value, long *ret) { const char *s = yml_value_as_string(value); if (s == NULL) return false; int cnt; int res = sscanf(s, "%ld%n", ret, &cnt); return res == 1 && strlen(s) == (size_t)cnt; } bool yml_value_is_int(const struct yml_node *value) { long dummy; return _as_int(value, &dummy); } long yml_value_as_int(const struct yml_node *value) { long ret = -1; _as_int(value, &ret); return ret; } static bool _as_bool(const struct yml_node *value, bool *ret) { if (!yml_is_scalar(value)) return false; const char *v = yml_value_as_string(value); if (strcasecmp(v, "y") == 0 || strcasecmp(v, "yes") == 0 || strcasecmp(v, "true") == 0 || strcasecmp(v, "on") == 0) { *ret = true; return true; } else if (strcasecmp(v, "n") == 0 || strcasecmp(v, "no") == 0 || strcasecmp(v, "false") == 0 || strcasecmp(v, "off") == 0) { *ret = false; return true; } return false; } bool yml_value_is_bool(const struct yml_node *value) { bool dummy; return _as_bool(value, &dummy); } bool yml_value_as_bool(const struct yml_node *value) { bool ret = false; _as_bool(value, &ret); return ret; } size_t yml_source_line(const struct yml_node *node) { return node->line; } size_t yml_source_column(const struct yml_node *node) { return node->column; } static void _print_node(const struct yml_node *n, int indent) { if (n == NULL) return; switch (n->type) { case ROOT: _print_node(n->root.root, indent); break; case DICT: tll_foreach(n->dict.pairs, it) { _print_node(it->item.key, indent); printf(": "); if (it->item.value->type != SCALAR) { printf("\n"); _print_node(it->item.value, indent + 2); } else { _print_node(it->item.value, 0); printf("\n"); } } break; case LIST: tll_foreach(n->list.values, it) { printf("%*s- ", indent, ""); if (it->item->type != SCALAR) { printf("\n"); _print_node(it->item, indent + 2); } else { _print_node(it->item, 0); } } break; case SCALAR: printf("%*s%s", indent, "", n->scalar.value); break; } } void print_node(const struct yml_node *n) { _print_node(n, 0); } yambar-1.11.0/yml.h000066400000000000000000000027461460770427600140450ustar00rootroot00000000000000#pragma once #include #include struct yml_node; struct yml_node *yml_load(FILE *yml, char **error); void yml_destroy(struct yml_node *root); bool yml_is_scalar(const struct yml_node *node); bool yml_is_dict(const struct yml_node *node); bool yml_is_list(const struct yml_node *node); const struct yml_node *yml_get_value(const struct yml_node *node, const char *path); const struct yml_node *yml_get_key(struct yml_node const *node, char const *path); struct yml_list_iter { const struct yml_node *node; const void *private; }; struct yml_list_iter yml_list_iter(const struct yml_node *list); void yml_list_next(struct yml_list_iter *iter); size_t yml_list_length(const struct yml_node *list); struct yml_dict_iter { const struct yml_node *key; const struct yml_node *value; const void *private1; const void *private2; }; struct yml_dict_iter yml_dict_iter(const struct yml_node *dict); void yml_dict_next(struct yml_dict_iter *iter); size_t yml_dict_length(const struct yml_node *dict); bool yml_value_is_int(const struct yml_node *value); bool yml_value_is_bool(const struct yml_node *value); const char *yml_value_as_string(const struct yml_node *value); long yml_value_as_int(const struct yml_node *value); bool yml_value_as_bool(const struct yml_node *value); size_t yml_source_line(const struct yml_node *node); size_t yml_source_column(const struct yml_node *node); /* For debugging, prints on stdout */ void print_node(const struct yml_node *n);