pax_global_header00006660000000000000000000000064141667724650014534gustar00rootroot0000000000000052 comment=1540797cd93bd91517b7fb28b2a129cb8938381d waybar-0.9.9/000077500000000000000000000000001416677246500130405ustar00rootroot00000000000000waybar-0.9.9/.clang-format000066400000000000000000000001511416677246500154100ustar00rootroot00000000000000--- BasedOnStyle: Google AlignConsecutiveDeclarations: true BinPackArguments: false ColumnLimit: 100 ... waybar-0.9.9/.editorconfig000066400000000000000000000004701416677246500155160ustar00rootroot00000000000000# EditorConfig configuration for Waybar # http://EditorConfig.org # Top-most EditorConfig file root = true [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 [*.{build,css}] indent_style = space indent_size = 4 [*.{hpp,cpp}] indent_style = space indent_size = 2 waybar-0.9.9/.github/000077500000000000000000000000001416677246500144005ustar00rootroot00000000000000waybar-0.9.9/.github/FUNDING.yml000066400000000000000000000001441416677246500162140ustar00rootroot00000000000000# These are supported funding model platforms github: Alexays custom: https://paypal.me/ARouillard waybar-0.9.9/.github/workflows/000077500000000000000000000000001416677246500164355ustar00rootroot00000000000000waybar-0.9.9/.github/workflows/freebsd.yml000066400000000000000000000021061416677246500205710ustar00rootroot00000000000000name: freebsd on: [ push, pull_request ] jobs: clang: # Run actions in a FreeBSD vm on the macos-10.15 runner # https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners runs-on: macos-10.15 steps: - uses: actions/checkout@v2 - name: Test in FreeBSD VM uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 with: mem: 2048 usesh: true prepare: | export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf pkg install -y git # subprojects/date pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ pkgconf pulseaudio scdoc sndio spdlog run: | meson build -Dman-pages=enabled ninja -C build meson test -C build --no-rebuild --print-errorlogs --suite waybar waybar-0.9.9/.github/workflows/linux.yml000066400000000000000000000010471416677246500203210ustar00rootroot00000000000000name: linux on: [push, pull_request] jobs: build: strategy: matrix: distro: - alpine - archlinux - debian - fedora - opensuse runs-on: ubuntu-latest container: image: alexays/waybar:${{ matrix.distro }} steps: - uses: actions/checkout@v2 - name: configure run: meson -Dman-pages=enabled build - name: build run: ninja -C build - name: test run: meson test -C build --no-rebuild --print-errorlogs --suite waybar waybar-0.9.9/.gitignore000066400000000000000000000005601416677246500150310ustar00rootroot00000000000000.DS_Store *~ vgcore.* /.vscode *.swp packagecache /subprojects/**/ /build* /dist /meson.egg-info # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app waybar-0.9.9/.gitmodules000066400000000000000000000001521416677246500152130ustar00rootroot00000000000000[submodule "package/archlinux"] path = package/archlinux url = https://aur.archlinux.org/waybar-git.git waybar-0.9.9/Dockerfiles/000077500000000000000000000000001416677246500152725ustar00rootroot00000000000000waybar-0.9.9/Dockerfiles/alpine000066400000000000000000000004421416677246500164650ustar00rootroot00000000000000# vim: ft=Dockerfile FROM alpine:latest RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata waybar-0.9.9/Dockerfiles/archlinux000066400000000000000000000004441416677246500172140ustar00rootroot00000000000000# vim: ft=Dockerfile FROM archlinux:base-devel RUN pacman -Syu --noconfirm && \ pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon waybar-0.9.9/Dockerfiles/debian000066400000000000000000000010761416677246500164430ustar00rootroot00000000000000# vim: ft=Dockerfile FROM debian:sid RUN apt-get update && \ apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \ apt-get clean waybar-0.9.9/Dockerfiles/fedora000066400000000000000000000012411416677246500164530ustar00rootroot00000000000000# vim: ft=Dockerfile FROM fedora:latest RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ 'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ 'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ 'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ 'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \ dnf clean all -y waybar-0.9.9/Dockerfiles/opensuse000066400000000000000000000011221416677246500170520ustar00rootroot00000000000000# vim: ft=Dockerfile FROM opensuse/tumbleweed:latest RUN zypper -n up && \ zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \ zypper -n refresh && \ zypper -n install -t pattern devel_C_C++ && \ zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc waybar-0.9.9/LICENSE000066400000000000000000000020451416677246500140460ustar00rootroot00000000000000MIT License Copyright (c) 2018 Alex 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. waybar-0.9.9/Makefile000066400000000000000000000004721416677246500145030ustar00rootroot00000000000000.PHONY: build build-debug run clean default install default: build build: meson build ninja -C build build-debug: meson build --buildtype=debug ninja -C build install: build ninja -C build install run: build ./build/waybar debug-run: build-debug ./build/waybar --log-level debug clean: rm -rf build waybar-0.9.9/README.md000066400000000000000000000056651416677246500143330ustar00rootroot00000000000000# Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)
![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png) > Highly customizable Wayland bar for Sway and Wlroots based compositors.
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or [AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* #### Current features - Sway (Workspaces, Binding mode, Focused window name) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time - Battery - Network - Bluetooth - Pulseaudio - Disk - Memory - Cpu load average - Temperature - MPD - Custom scripts - Multiple output configuration - And much more customizations #### Configuration and Styling [See the wiki for more details](https://github.com/Alexays/Waybar/wiki). ### Installation Waybar is available from a number of Linux distributions: [![Packaging status](https://repology.org/badge/vertical-allrepos/waybar.svg)](https://repology.org/project/waybar/versions) An Ubuntu PPA with more recent versions is available [here](https://launchpad.net/~nschloe/+archive/ubuntu/waybar). #### Building from source ```bash $ git clone https://github.com/Alexays/Waybar $ cd Waybar $ meson build $ ninja -C build $ ./build/waybar # If you want to install it $ ninja -C build install $ waybar ``` **Dependencies** ``` gtkmm3 jsoncpp libsigc++ fmt wayland chrono-date spdlog libgtk-3-dev [gtk-layer-shell] gobject-introspection [gtk-layer-shell] libgirepository1.0-dev [gtk-layer-shell] libpulse [Pulseaudio module] libnl [Network module] libappindicator-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] libsndio [sndio module] libevdev [KeyboardState module] xkbregistry ``` **Build dependencies** ``` cmake meson scdoc wayland-protocols ``` On Ubuntu you can install all the relevant dependencies using this command (tested with 19.10 and 20.04): ``` sudo apt install \ clang-tidy \ gobject-introspection \ libdbusmenu-gtk3-dev \ libevdev-dev \ libfmt-dev \ libgirepository1.0-dev \ libgtk-3-dev \ libgtkmm-3.0-dev \ libinput-dev \ libjsoncpp-dev \ libmpdclient-dev \ libnl-3-dev \ libnl-genl-3-dev \ libpulse-dev \ libsigc++-2.0-dev \ libspdlog-dev \ libwayland-dev \ scdoc \ libxkbregistry-dev ``` Contributions welcome! - have fun :)
The style guidelines is [Google's](https://google.github.io/styleguide/cppguide.html) ## License Waybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/Alexays/Waybar/blob/master/LICENSE). waybar-0.9.9/include/000077500000000000000000000000001416677246500144635ustar00rootroot00000000000000waybar-0.9.9/include/ALabel.hpp000066400000000000000000000017701416677246500163210ustar00rootroot00000000000000#pragma once #include #include #include #include "AModule.hpp" namespace waybar { class ALabel : public AModule { public: ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format, uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false); virtual ~ALabel() = default; virtual auto update() -> void; virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0); virtual std::string getIcon(uint16_t, const std::vector &alts, uint16_t max = 0); protected: Gtk::Label label_; std::string format_; const std::chrono::seconds interval_; bool alt_ = false; std::string default_format_; virtual bool handleToggle(GdkEventButton *const &e); virtual std::string getState(uint8_t value, bool lesser = false); }; } // namespace waybar waybar-0.9.9/include/AModule.hpp000066400000000000000000000016701416677246500165260ustar00rootroot00000000000000#pragma once #include #include #include #include #include "IModule.hpp" namespace waybar { class AModule : public IModule { public: AModule(const Json::Value &, const std::string &, const std::string &, bool enable_click = false, bool enable_scroll = false); virtual ~AModule(); virtual auto update() -> void; virtual operator Gtk::Widget &(); Glib::Dispatcher dp; protected: enum SCROLL_DIR { NONE, UP, DOWN, LEFT, RIGHT }; SCROLL_DIR getScrollDir(GdkEventScroll *e); bool tooltipEnabled(); const std::string name_; const Json::Value &config_; Gtk::EventBox event_box_; virtual bool handleToggle(GdkEventButton *const &ev); virtual bool handleScroll(GdkEventScroll *); private: std::vector pid_; gdouble distance_scrolled_y_; gdouble distance_scrolled_x_; }; } // namespace waybar waybar-0.9.9/include/IModule.hpp000066400000000000000000000003531416677246500165330ustar00rootroot00000000000000#pragma once #include namespace waybar { class IModule { public: virtual ~IModule() = default; virtual auto update() -> void = 0; virtual operator Gtk::Widget &() = 0; }; } // namespace waybar waybar-0.9.9/include/bar.hpp000066400000000000000000000063351416677246500157470ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include "AModule.hpp" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar { class Factory; struct waybar_output { Glib::RefPtr monitor; std::string name; std::string identifier; std::unique_ptr xdg_output = { nullptr, &zxdg_output_v1_destroy}; }; enum class bar_layer : uint8_t { BOTTOM, TOP, OVERLAY, }; struct bar_margins { int top = 0; int right = 0; int bottom = 0; int left = 0; }; struct bar_mode { bar_layer layer; bool exclusive; bool passthrough; bool visible; }; #ifdef HAVE_SWAY namespace modules::sway { class BarIpcClient; } #endif // HAVE_SWAY class BarSurface { protected: BarSurface() = default; public: virtual void setExclusiveZone(bool enable) = 0; virtual void setLayer(bar_layer layer) = 0; virtual void setMargins(const struct bar_margins &margins) = 0; virtual void setPassThrough(bool enable) = 0; virtual void setPosition(const std::string_view &position) = 0; virtual void setSize(uint32_t width, uint32_t height) = 0; virtual void commit(){}; virtual ~BarSurface() = default; }; class Bar { public: using bar_mode_map = std::map; static const bar_mode_map PRESET_MODES; static const std::string_view MODE_DEFAULT; static const std::string_view MODE_INVISIBLE; Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; ~Bar(); void setMode(const std::string_view &); void setVisible(bool visible); void toggle(); void handleSignal(int); struct waybar_output *output; Json::Value config; struct wl_surface *surface; bool visible = true; bool vertical = false; Gtk::Window window; #ifdef HAVE_SWAY std::string bar_id; #endif private: void onMap(GdkEventAny *); auto setupWidgets() -> void; void getModules(const Factory &, const std::string &, Gtk::Box*); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); void setMode(const bar_mode &); /* Copy initial set of modes to allow customization */ bar_mode_map configured_modes = PRESET_MODES; std::string last_mode_{MODE_DEFAULT}; std::unique_ptr surface_impl_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; Gtk::Box box_; std::vector> modules_left_; std::vector> modules_center_; std::vector> modules_right_; #ifdef HAVE_SWAY using BarIpcClient = modules::sway::BarIpcClient; std::unique_ptr _ipc_client; #endif std::vector> modules_all_; }; } // namespace waybar waybar-0.9.9/include/client.hpp000066400000000000000000000042561416677246500164610ustar00rootroot00000000000000#pragma once #include #include #include #include #include "bar.hpp" #include "config.hpp" struct zwlr_layer_shell_v1; struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibit_manager_v1; namespace waybar { class Client { public: static Client *inst(); int main(int argc, char *argv[]); void reset(); Glib::RefPtr gtk_app; Glib::RefPtr gdk_display; struct wl_display * wl_display = nullptr; struct wl_registry * registry = nullptr; struct zwlr_layer_shell_v1 * layer_shell = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; std::vector> bars; Config config; std::string bar_id; private: Client() = default; const std::string getStyle(const std::string &style); void bindInterfaces(); void handleOutput(struct waybar_output &output); auto setupCss(const std::string &css_file) -> void; struct waybar_output & getOutput(void *); std::vector getOutputConfigs(struct waybar_output &output); static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); static void handleOutputDone(void *, struct zxdg_output_v1 *); static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *); void handleMonitorAdded(Glib::RefPtr monitor); void handleMonitorRemoved(Glib::RefPtr monitor); void handleDeferredMonitorRemoval(Glib::RefPtr monitor); Glib::RefPtr style_context_; Glib::RefPtr css_provider_; std::list outputs_; }; } // namespace waybar waybar-0.9.9/include/config.hpp000066400000000000000000000017221416677246500164430ustar00rootroot00000000000000#pragma once #include #include #include #ifndef SYSCONFDIR #define SYSCONFDIR "/etc" #endif namespace waybar { class Config { public: static const std::vector CONFIG_DIRS; /* Try to find any of provided names in the supported set of config directories */ static std::optional findConfigPath( const std::vector &names, const std::vector &dirs = CONFIG_DIRS); Config() = default; void load(const std::string &config); Json::Value &getConfig() { return config_; } std::vector getOutputConfigs(const std::string &name, const std::string &identifier); private: void setupConfig(Json::Value &dst, const std::string &config_file, int depth); void resolveConfigIncludes(Json::Value &config, int depth); void mergeConfig(Json::Value &a_config_, Json::Value &b_config_); std::string config_file_; Json::Value config_; }; } // namespace waybar waybar-0.9.9/include/factory.hpp000066400000000000000000000032641416677246500166500ustar00rootroot00000000000000#pragma once #include #ifdef HAVE_LIBDATE #include "modules/clock.hpp" #else #include "modules/simpleclock.hpp" #endif #ifdef HAVE_SWAY #include "modules/sway/mode.hpp" #include "modules/sway/window.hpp" #include "modules/sway/workspaces.hpp" #include "modules/sway/language.hpp" #endif #ifdef HAVE_WLR #include "modules/wlr/taskbar.hpp" #include "modules/wlr/workspace_manager.hpp" #endif #ifdef HAVE_RIVER #include "modules/river/tags.hpp" #endif #if defined(__linux__) && !defined(NO_FILESYSTEM) #include "modules/battery.hpp" #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" #endif #include "modules/idle_inhibitor.hpp" #if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD) #include "modules/memory.hpp" #endif #include "modules/disk.hpp" #ifdef HAVE_DBUSMENU #include "modules/sni/tray.hpp" #endif #ifdef HAVE_LIBNL #include "modules/network.hpp" #endif #ifdef HAVE_LIBUDEV #include "modules/backlight.hpp" #endif #ifdef HAVE_LIBEVDEV #include "modules/keyboard_state.hpp" #endif #ifdef HAVE_LIBPULSE #include "modules/pulseaudio.hpp" #endif #ifdef HAVE_LIBMPDCLIENT #include "modules/mpd/mpd.hpp" #endif #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" #endif #ifdef HAVE_GIO_UNIX #include "modules/inhibitor.hpp" #endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" #if defined(__linux__) # ifdef WANT_RFKILL # include "modules/bluetooth.hpp" # endif #endif namespace waybar { class Factory { public: Factory(const Bar& bar, const Json::Value& config); AModule* makeModule(const std::string& name) const; private: const Bar& bar_; const Json::Value& config_; }; } // namespace waybar waybar-0.9.9/include/group.hpp000066400000000000000000000006071416677246500163330ustar00rootroot00000000000000#pragma once #include #include #include #include "AModule.hpp" #include "bar.hpp" #include "factory.hpp" namespace waybar { class Group : public AModule { public: Group(const std::string&, const Bar&, const Json::Value&); ~Group() = default; auto update() -> void; operator Gtk::Widget &(); Gtk::Box box; }; } // namespace waybar waybar-0.9.9/include/modules/000077500000000000000000000000001416677246500161335ustar00rootroot00000000000000waybar-0.9.9/include/modules/backlight.hpp000066400000000000000000000034021416677246500205730ustar00rootroot00000000000000#pragma once #include #include #include #include #include "ALabel.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" struct udev; struct udev_device; namespace waybar::modules { class Backlight : public ALabel { class BacklightDev { public: BacklightDev() = default; BacklightDev(std::string name, int actual, int max); std::string_view name() const; int get_actual() const; void set_actual(int actual); int get_max() const; void set_max(int max); friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) { return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_; } private: std::string name_; int actual_ = 1; int max_ = 1; }; public: Backlight(const std::string &, const Json::Value &); ~Backlight(); auto update() -> void; private: template static const BacklightDev *best_device(ForwardIt first, ForwardIt last, std::string_view); template static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev); template static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev); const std::string preferred_device_; static constexpr int EPOLL_MAX_EVENTS = 16; std::optional previous_best_; std::string previous_format_; std::mutex udev_thread_mutex_; std::vector devices_; // thread must destruct before shared data util::SleeperThread udev_thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/battery.hpp000066400000000000000000000027341416677246500203240ustar00rootroot00000000000000#pragma once #ifdef FILESYSTEM_EXPERIMENTAL #include #else #include #endif #include #include #include #include #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { #ifdef FILESYSTEM_EXPERIMENTAL namespace fs = std::experimental::filesystem; #else namespace fs = std::filesystem; #endif class Battery : public ALabel { public: Battery(const std::string&, const Json::Value&); ~Battery(); auto update() -> void; private: static inline const fs::path data_dir_ = "/sys/class/power_supply/"; void refreshBatteries(); void worker(); const std::string getAdapterStatus(uint8_t capacity) const; const std::tuple getInfos(); const std::string formatTimeRemaining(float hoursRemaining); int global_watch; std::map batteries_; fs::path adapter_; int battery_watch_fd_; int global_watch_fd_; std::mutex battery_list_mutex_; std::string old_status_; util::SleeperThread thread_; util::SleeperThread thread_battery_update_; util::SleeperThread thread_timer_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/bluetooth.hpp000066400000000000000000000004671416677246500206600ustar00rootroot00000000000000#pragma once #include "ALabel.hpp" #include "util/rfkill.hpp" namespace waybar::modules { class Bluetooth : public ALabel { public: Bluetooth(const std::string&, const Json::Value&); ~Bluetooth() = default; auto update() -> void; private: util::Rfkill rfkill_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/clock.hpp000066400000000000000000000017241416677246500177430ustar00rootroot00000000000000#pragma once #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar { struct waybar_time; namespace modules { const std::string kCalendarPlaceholder = "calendar"; class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); ~Clock() = default; auto update() -> void; private: util::SleeperThread thread_; std::locale locale_; std::vector time_zones_; int current_time_zone_idx_; date::year_month_day cached_calendar_ymd_ = date::January/1/0; std::string cached_calendar_text_; bool is_calendar_in_tooltip_; bool handleScroll(GdkEventScroll* e); auto calendar_text(const waybar_time& wtime) -> std::string; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto first_day_of_week() -> date::weekday; const date::time_zone* current_timezone(); bool is_timezone_fixed(); }; } // namespace modules } // namespace waybar waybar-0.9.9/include/modules/cpu.hpp000066400000000000000000000015011416677246500174300ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Cpu : public ALabel { public: Cpu(const std::string&, const Json::Value&); ~Cpu() = default; auto update() -> void; private: double getCpuLoad(); std::tuple, std::string> getCpuUsage(); std::tuple getCpuFrequency(); std::vector> parseCpuinfo(); std::vector parseCpuFrequencies(); std::vector> prev_times_; util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/custom.hpp000066400000000000000000000020121416677246500201510ustar00rootroot00000000000000#pragma once #include #include #include #include "ALabel.hpp" #include "util/command.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Custom : public ALabel { public: Custom(const std::string&, const std::string&, const Json::Value&); ~Custom(); auto update() -> void; void refresh(int /*signal*/); private: void delayWorker(); void continuousWorker(); void parseOutputRaw(); void parseOutputJson(); void handleEvent(); bool handleScroll(GdkEventScroll* e); bool handleToggle(GdkEventButton* const& e); const std::string name_; std::string text_; std::string alt_; std::string tooltip_; std::vector class_; int percentage_; FILE* fp_; int pid_; util::command::res output_; util::JsonParser parser_; util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/disk.hpp000066400000000000000000000006531416677246500176020ustar00rootroot00000000000000#pragma once #include #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" #include "util/format.hpp" namespace waybar::modules { class Disk : public ALabel { public: Disk(const std::string&, const Json::Value&); ~Disk() = default; auto update() -> void; private: util::SleeperThread thread_; std::string path_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/idle_inhibitor.hpp000066400000000000000000000011431416677246500216270ustar00rootroot00000000000000#pragma once #include #include "ALabel.hpp" #include "bar.hpp" #include "client.hpp" namespace waybar::modules { class IdleInhibitor : public ALabel { public: IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; static std::list modules; static bool status; private: bool handleToggle(GdkEventButton* const& e); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; int pid_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/inhibitor.hpp000066400000000000000000000010541416677246500206330ustar00rootroot00000000000000#pragma once #include #include #include "ALabel.hpp" #include "bar.hpp" namespace waybar::modules { class Inhibitor : public ALabel { public: Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~Inhibitor() override; auto update() -> void; auto activated() -> bool; private: auto handleToggle(::GdkEventButton* const& e) -> bool; const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_; const std::string inhibitors_; int handle_ = -1; }; } // namespace waybar::modules waybar-0.9.9/include/modules/keyboard_state.hpp000066400000000000000000000017171416677246500216520ustar00rootroot00000000000000#pragma once #include #if FMT_VERSION < 60000 #include #else #include #endif #include "AModule.hpp" #include "bar.hpp" #include "util/sleeper_thread.hpp" #include extern "C" { #include } namespace waybar::modules { class KeyboardState : public AModule { public: KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&); ~KeyboardState(); auto update() -> void; private: static auto openDevice(const std::string&) -> std::pair; Gtk::Box box_; Gtk::Label numlock_label_; Gtk::Label capslock_label_; Gtk::Label scrolllock_label_; std::string numlock_format_; std::string capslock_format_; std::string scrolllock_format_; const std::chrono::seconds interval_; std::string icon_locked_; std::string icon_unlocked_; int fd_; libevdev* dev_; util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/memory.hpp000066400000000000000000000007601416677246500201570ustar00rootroot00000000000000#pragma once #include #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Memory : public ALabel { public: Memory(const std::string&, const Json::Value&); ~Memory() = default; auto update() -> void; private: void parseMeminfo(); std::unordered_map meminfo_; util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/mpd/000077500000000000000000000000001416677246500167135ustar00rootroot00000000000000waybar-0.9.9/include/modules/mpd/mpd.hpp000066400000000000000000000031641416677246500202100ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "ALabel.hpp" #include "modules/mpd/state.hpp" namespace waybar::modules { class MPD : public ALabel { friend class detail::Context; // State machine detail::Context context_{this}; const std::string module_name_; // Not using unique_ptr since we don't manage the pointer // (It's either nullptr, or from the config) const char* server_; const unsigned port_; const std::string password_; unsigned timeout_; detail::unique_connection connection_; detail::unique_status status_; mpd_state state_; detail::unique_song song_; public: MPD(const std::string&, const Json::Value&); virtual ~MPD() noexcept = default; auto update() -> void; private: std::string getTag(mpd_tag_type type, unsigned idx = 0) const; void setLabel(); std::string getStateIcon() const; std::string getOptionIcon(std::string optionName, bool activated) const; // GUI-side methods bool handlePlayPause(GdkEventButton* const&); void emit() { dp.emit(); } // MPD-side, Non-GUI methods. void tryConnect(); void checkErrors(mpd_connection* conn); void fetchState(); void queryMPD(); inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; } }; #if !defined(MPD_NOINLINE) #include "modules/mpd/state.inl.hpp" #endif } // namespace waybar::modules waybar-0.9.9/include/modules/mpd/state.hpp000066400000000000000000000134171416677246500205520ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "ALabel.hpp" namespace waybar::modules { class MPD; } // namespace waybar::modules namespace waybar::modules::detail { using unique_connection = std::unique_ptr; using unique_status = std::unique_ptr; using unique_song = std::unique_ptr; class Context; /// This state machine loosely follows a non-hierarchical, statechart /// pattern, and includes ENTRY and EXIT actions. /// /// The State class is the base class for all other states. The /// entry and exit methods are automatically called when entering /// into a new state and exiting from the current state. This /// includes initially entering (Disconnected class) and exiting /// Waybar. /// /// The following nested "top-level" states are represented: /// 1. Idle - await notification of MPD activity. /// 2. All Non-Idle states: /// 1. Playing - An active song is producing audio output. /// 2. Paused - The current song is paused. /// 3. Stopped - No song is actively playing. /// 3. Disconnected - periodically attempt MPD (re-)connection. /// /// NOTE: Since this statechart is non-hierarchical, the above /// states are flattened into a set. class State { public: virtual ~State() noexcept = default; virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); } virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); } virtual void play() { spdlog::debug("mpd: ignore play state transition"); } virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); } virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); } /// Request state update the GUI. virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); } }; class Idle : public State { Context* const ctx_; sigc::connection idle_connection_; public: Idle(Context* const ctx) : ctx_{ctx} {} virtual ~Idle() noexcept { this->exit(); }; void entry() noexcept override; void exit() noexcept override; void play() override; void stop() override; void pause() override; void update() noexcept override; private: Idle(const Idle&) = delete; Idle& operator=(const Idle&) = delete; bool on_io(Glib::IOCondition const&); }; class Playing : public State { Context* const ctx_; sigc::connection timer_connection_; public: Playing(Context* const ctx) : ctx_{ctx} {} virtual ~Playing() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void pause() override; void stop() override; void update() noexcept override; private: Playing(Playing const&) = delete; Playing& operator=(Playing const&) = delete; bool on_timer(); }; class Paused : public State { Context* const ctx_; sigc::connection timer_connection_; public: Paused(Context* const ctx) : ctx_{ctx} {} virtual ~Paused() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void play() override; void stop() override; void update() noexcept override; private: Paused(Paused const&) = delete; Paused& operator=(Paused const&) = delete; bool on_timer(); }; class Stopped : public State { Context* const ctx_; sigc::connection timer_connection_; public: Stopped(Context* const ctx) : ctx_{ctx} {} virtual ~Stopped() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void play() override; void pause() override; void update() noexcept override; private: Stopped(Stopped const&) = delete; Stopped& operator=(Stopped const&) = delete; bool on_timer(); }; class Disconnected : public State { Context* const ctx_; sigc::connection timer_connection_; public: Disconnected(Context* const ctx) : ctx_{ctx} {} virtual ~Disconnected() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void update() noexcept override; private: Disconnected(Disconnected const&) = delete; Disconnected& operator=(Disconnected const&) = delete; void arm_timer(int interval) noexcept; void disarm_timer() noexcept; bool on_timer(); }; class Context { std::unique_ptr state_; waybar::modules::MPD* mpd_module_; friend class State; friend class Playing; friend class Paused; friend class Stopped; friend class Disconnected; friend class Idle; protected: void setState(std::unique_ptr&& new_state) noexcept { if (state_.get() != nullptr) { state_->exit(); } state_ = std::move(new_state); state_->entry(); } bool is_connected() const; bool is_playing() const; bool is_paused() const; bool is_stopped() const; constexpr std::size_t interval() const; void tryConnect() const; void checkErrors(mpd_connection*) const; void do_update(); void queryMPD() const; void fetchState() const; constexpr mpd_state state() const; void emit() const; [[nodiscard]] unique_connection& connection(); public: explicit Context(waybar::modules::MPD* const mpd_module) : state_{std::make_unique(this)}, mpd_module_{mpd_module} { state_->entry(); } void play() { state_->play(); } void stop() { state_->stop(); } void pause() { state_->pause(); } void update() noexcept { state_->update(); } }; } // namespace waybar::modules::detail waybar-0.9.9/include/modules/mpd/state.inl.hpp000066400000000000000000000021021416677246500213200ustar00rootroot00000000000000#pragma once namespace detail { inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } inline bool Context::is_playing() const { return mpd_module_->playing(); } inline bool Context::is_paused() const { return mpd_module_->paused(); } inline bool Context::is_stopped() const { return mpd_module_->stopped(); } constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } inline void Context::tryConnect() const { mpd_module_->tryConnect(); } inline unique_connection& Context::connection() { return mpd_module_->connection_; } constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } inline void Context::do_update() { mpd_module_->setLabel(); } inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } inline void Context::queryMPD() const { mpd_module_->queryMPD(); } inline void Context::fetchState() const { mpd_module_->fetchState(); } inline void Context::emit() const { mpd_module_->emit(); } } // namespace detail waybar-0.9.9/include/modules/network.hpp000066400000000000000000000046231416677246500203420ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" #endif namespace waybar::modules { class Network : public ALabel { public: Network(const std::string&, const Json::Value&); ~Network(); auto update() -> void; private: static const uint8_t MAX_RETRY = 5; static const uint8_t EPOLL_MAX = 200; static int handleEvents(struct nl_msg*, void*); static int handleEventsDone(struct nl_msg*, void*); static int handleScan(struct nl_msg*, void*); void askForStateDump(void); void worker(); void createInfoSocket(); void createEventSocket(); void parseEssid(struct nlattr**); void parseSignal(struct nlattr**); void parseFreq(struct nlattr**); bool associatedOrJoined(struct nlattr**); bool checkInterface(std::string name); auto getInfo() -> void; const std::string getNetworkState() const; void clearIface(); bool wildcardMatch(const std::string& pattern, const std::string& text) const; std::optional> readBandwidthUsage(); int ifid_; sa_family_t family_; struct sockaddr_nl nladdr_ = {0}; struct nl_sock* sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr; int efd_; int ev_fd_; int nl80211_id_; std::mutex mutex_; bool want_route_dump_; bool want_link_dump_; bool want_addr_dump_; bool dump_in_progress_; unsigned long long bandwidth_down_total_; unsigned long long bandwidth_up_total_; std::string state_; std::string essid_; bool carrier_; std::string ifname_; std::string ipaddr_; std::string gwaddr_; std::string netmask_; int cidr_; int32_t signal_strength_dbm_; uint8_t signal_strength_; std::string signal_strength_app_; float frequency_; uint32_t route_priority; util::SleeperThread thread_; util::SleeperThread thread_timer_; #ifdef WANT_RFKILL util::Rfkill rfkill_; #endif }; } // namespace waybar::modules waybar-0.9.9/include/modules/pulseaudio.hpp000066400000000000000000000027141416677246500210220ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "ALabel.hpp" namespace waybar::modules { class Pulseaudio : public ALabel { public: Pulseaudio(const std::string&, const Json::Value&); ~Pulseaudio(); auto update() -> void; private: static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); static void contextStateCb(pa_context*, void*); static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*); static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data); static void serverInfoCb(pa_context*, const pa_server_info*, void*); static void volumeModifyCb(pa_context*, int, void*); bool handleScroll(GdkEventScroll* e); const std::vector getPulseIcon() const; pa_threaded_mainloop* mainloop_; pa_mainloop_api* mainloop_api_; pa_context* context_; // SINK uint32_t sink_idx_{0}; uint16_t volume_; pa_cvolume pa_volume_; bool muted_; std::string port_name_; std::string form_factor_; std::string desc_; std::string monitor_; std::string current_sink_name_; bool current_sink_running_; // SOURCE uint32_t source_idx_{0}; uint16_t source_volume_; bool source_muted_; std::string source_port_name_; std::string source_desc_; std::string default_source_name_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/river/000077500000000000000000000000001416677246500172625ustar00rootroot00000000000000waybar-0.9.9/include/modules/river/tags.hpp000066400000000000000000000020301416677246500207240ustar00rootroot00000000000000#pragma once #include #include #include "AModule.hpp" #include "bar.hpp" #include "river-status-unstable-v1-client-protocol.h" #include "river-control-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar::modules::river { class Tags : public waybar::AModule { public: Tags(const std::string &, const waybar::Bar &, const Json::Value &); ~Tags(); // Handlers for wayland events void handle_focused_tags(uint32_t tags); void handle_view_tags(struct wl_array *tags); void handle_urgent_tags(uint32_t tags); void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton *event_button, uint32_t tag); struct zriver_status_manager_v1 *status_manager_; struct zriver_control_v1 *control_; struct wl_seat *seat_; private: const waybar::Bar & bar_; Gtk::Box box_; std::vector buttons_; struct zriver_output_status_v1 *output_status_; }; } /* namespace waybar::modules::river */ waybar-0.9.9/include/modules/simpleclock.hpp000066400000000000000000000006451416677246500211560ustar00rootroot00000000000000#pragma once #include #if FMT_VERSION < 60000 #include #else #include #endif #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); ~Clock() = default; auto update() -> void; private: util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/sndio.hpp000066400000000000000000000013171416677246500177620ustar00rootroot00000000000000#pragma once #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Sndio : public ALabel { public: Sndio(const std::string&, const Json::Value&); ~Sndio(); auto update() -> void; auto set_desc(struct sioctl_desc *, unsigned int) -> void; auto put_val(unsigned int, unsigned int) -> void; bool handleScroll(GdkEventScroll *); bool handleToggle(GdkEventButton* const&); private: auto connect_to_sndio() -> void; util::SleeperThread thread_; struct sioctl_hdl *hdl_; std::vector pfds_; unsigned int addr_; unsigned int volume_, old_volume_, maxval_; bool muted_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/sni/000077500000000000000000000000001416677246500167245ustar00rootroot00000000000000waybar-0.9.9/include/modules/sni/host.hpp000066400000000000000000000036201416677246500204130ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "bar.hpp" #include "modules/sni/item.hpp" namespace waybar::modules::SNI { class Host { public: Host(const std::size_t id, const Json::Value&, const Bar&, const std::function&)>&, const std::function&)>&); ~Host(); private: void busAcquired(const Glib::RefPtr&, Glib::ustring); void nameAppeared(const Glib::RefPtr&, Glib::ustring, const Glib::ustring&); void nameVanished(const Glib::RefPtr&, Glib::ustring); static void proxyReady(GObject*, GAsyncResult*, gpointer); static void registerHost(GObject*, GAsyncResult*, gpointer); static void itemRegistered(SnWatcher*, const gchar*, gpointer); static void itemUnregistered(SnWatcher*, const gchar*, gpointer); std::tuple getBusNameAndObjectPath(const std::string); void addRegisteredItem(std::string service); std::vector> items_; const std::string bus_name_; const std::string object_path_; std::size_t bus_name_id_; std::size_t watcher_id_; GCancellable* cancellable_ = nullptr; SnWatcher* watcher_ = nullptr; const Json::Value& config_; const Bar& bar_; const std::function&)> on_add_; const std::function&)> on_remove_; }; } // namespace waybar::modules::SNI waybar-0.9.9/include/modules/sni/item.hpp000066400000000000000000000060361416677246500204000ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include "bar.hpp" namespace waybar::modules::SNI { struct ToolTip { Glib::ustring icon_name; Glib::ustring text; }; class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&, const Bar&); ~Item() = default; std::string bus_name; std::string object_path; int icon_size; int effective_icon_size; Gtk::Image image; Gtk::EventBox event_box; std::string category; std::string id; std::string title; std::string icon_name; Glib::RefPtr icon_pixmap; Glib::RefPtr icon_theme; std::string overlay_icon_name; std::string attention_icon_name; std::string attention_movie_name; std::string icon_theme_path; std::string menu; ToolTip tooltip; DbusmenuGtkMenu* dbus_menu = nullptr; Gtk::Menu* gtk_menu = nullptr; /** * ItemIsMenu flag means that the item only supports the context menu. * Default value is true because libappindicator supports neither ItemIsMenu nor Activate method * while compliant SNI implementation would always reset the flag to desired value. */ bool item_is_menu = true; private: void onConfigure(GdkEventConfigure* ev); void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setStatus(const Glib::ustring& value); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments); void updateImage(); Glib::RefPtr extractPixBuf(GVariant* variant); Glib::RefPtr getIconPixbuf(); Glib::RefPtr getIconByName(const std::string& name, int size); double getScaledIconSize(); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); void makeMenu(); bool handleClick(GdkEventButton* const& /*ev*/); bool handleScroll(GdkEventScroll* const&); // smooth scrolling threshold gdouble scroll_threshold_ = 0; gdouble distance_scrolled_x_ = 0; gdouble distance_scrolled_y_ = 0; // visibility of items with Status == Passive bool show_passive_ = false; Glib::RefPtr proxy_; Glib::RefPtr cancellable_; std::set update_pending_; }; } // namespace waybar::modules::SNI waybar-0.9.9/include/modules/sni/tray.hpp000066400000000000000000000012041416677246500204110ustar00rootroot00000000000000#pragma once #include #include "AModule.hpp" #include "bar.hpp" #include "modules/sni/host.hpp" #include "modules/sni/watcher.hpp" #include "util/json.hpp" namespace waybar::modules::SNI { class Tray : public AModule { public: Tray(const std::string&, const Bar&, const Json::Value&); ~Tray() = default; auto update() -> void; private: void onAdd(std::unique_ptr& item); void onRemove(std::unique_ptr& item); static inline std::size_t nb_hosts_ = 0; Gtk::Box box_; SNI::Watcher::singleton watcher_; SNI::Host host_; }; } // namespace waybar::modules::SNI waybar-0.9.9/include/modules/sni/watcher.hpp000066400000000000000000000030551416677246500210750ustar00rootroot00000000000000#pragma once #include #include #include namespace waybar::modules::SNI { class Watcher { private: Watcher(); public: ~Watcher(); using singleton = std::shared_ptr; static singleton getInstance() { static std::weak_ptr weak; std::shared_ptr strong = weak.lock(); if (!strong) { strong = std::shared_ptr(new Watcher()); weak = strong; } return strong; } private: typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType; typedef struct { GfWatchType type; Watcher * watcher; gchar * service; gchar * bus_name; gchar * object_path; guint watch_id; } GfWatch; void busAcquired(const Glib::RefPtr &, Glib::ustring); static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *, const gchar *); static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *, const gchar *); static GfWatch *gfWatchFind(GSList *list, const gchar *bus_name, const gchar *object_path); static GfWatch *gfWatchNew(GfWatchType, const gchar *, const gchar *, const gchar *, Watcher *); static void nameVanished(GDBusConnection *connection, const char *name, gpointer data); static void gfWatchFree(gpointer data); void updateRegisteredItems(SnWatcher *obj); uint32_t bus_name_id_; GSList * hosts_ = nullptr; GSList * items_ = nullptr; SnWatcher *watcher_ = nullptr; }; } // namespace waybar::modules::SNI waybar-0.9.9/include/modules/sway/000077500000000000000000000000001416677246500171165ustar00rootroot00000000000000waybar-0.9.9/include/modules/sway/bar.hpp000066400000000000000000000017331416677246500203770ustar00rootroot00000000000000#pragma once #include #include "modules/sway/ipc/client.hpp" #include "util/SafeSignal.hpp" #include "util/json.hpp" namespace waybar { class Bar; namespace modules::sway { /* * Supported subset of i3/sway IPC barconfig object */ struct swaybar_config { std::string id; std::string mode; std::string hidden_state; }; /** * swaybar IPC client */ class BarIpcClient { public: BarIpcClient(waybar::Bar& bar); private: void onInitialConfig(const struct Ipc::ipc_response& res); void onIpcEvent(const struct Ipc::ipc_response&); void onConfigUpdate(const swaybar_config& config); void onVisibilityUpdate(bool visible_by_modifier); void update(); Bar& bar_; util::JsonParser parser_; Ipc ipc_; swaybar_config bar_config_; bool visible_by_modifier_ = false; SafeSignal signal_visible_; SafeSignal signal_config_; }; } // namespace modules::sway } // namespace waybar waybar-0.9.9/include/modules/sway/ipc/000077500000000000000000000000001416677246500176715ustar00rootroot00000000000000waybar-0.9.9/include/modules/sway/ipc/client.hpp000066400000000000000000000023131416677246500216570ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "ipc.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules::sway { class Ipc { public: Ipc(); ~Ipc(); struct ipc_response { uint32_t size; uint32_t type; std::string payload; }; sigc::signal signal_event; sigc::signal signal_cmd; void sendCmd(uint32_t type, const std::string &payload = ""); void subscribe(const std::string &payload); void handleEvent(); void setWorker(std::function &&func); protected: static inline const std::string ipc_magic_ = "i3-ipc"; static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8; const std::string getSocketPath() const; int open(const std::string &) const; struct ipc_response send(int fd, uint32_t type, const std::string &payload = ""); struct ipc_response recv(int fd); int fd_; int fd_event_; std::mutex mutex_; util::SleeperThread thread_; }; } // namespace waybar::modules::sway waybar-0.9.9/include/modules/sway/ipc/ipc.hpp000066400000000000000000000017211416677246500211560ustar00rootroot00000000000000#pragma once #define event_mask(ev) (1u << (ev & 0x7F)) enum ipc_command_type { // i3 command types - see i3's I3_REPLY_TYPE constants IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_SUBSCRIBE = 2, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4, IPC_GET_MARKS = 5, IPC_GET_BAR_CONFIG = 6, IPC_GET_VERSION = 7, IPC_GET_BINDING_MODES = 8, IPC_GET_CONFIG = 9, IPC_SEND_TICK = 10, // sway-specific command types IPC_GET_INPUTS = 100, IPC_GET_SEATS = 101, // Events sent from sway to clients. Events have the highest bits set. IPC_EVENT_WORKSPACE = ((1 << 31) | 0), IPC_EVENT_OUTPUT = ((1 << 31) | 1), IPC_EVENT_MODE = ((1 << 31) | 2), IPC_EVENT_WINDOW = ((1 << 31) | 3), IPC_EVENT_BARCONFIG_UPDATE = ((1 << 31) | 4), IPC_EVENT_BINDING = ((1 << 31) | 5), IPC_EVENT_SHUTDOWN = ((1 << 31) | 6), IPC_EVENT_TICK = ((1 << 31) | 7), // sway-specific event types IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20), IPC_EVENT_INPUT = ((1<<31) | 21), }; waybar-0.9.9/include/modules/sway/language.hpp000066400000000000000000000033221416677246500214120ustar00rootroot00000000000000#pragma once #include #include #include #include #include "ALabel.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" namespace waybar::modules::sway { class Language : public ALabel, public sigc::trackable { public: Language(const std::string& id, const Json::Value& config); ~Language() = default; auto update() -> void; private: enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 }; struct Layout { std::string full_name; std::string short_name; std::string variant; std::string short_description; std::string country_flag() const; }; class XKBContext { public: XKBContext(); ~XKBContext(); auto next_layout() -> Layout*; private: rxkb_context* context_ = nullptr; rxkb_layout* xkb_layout_ = nullptr; Layout* layout_ = nullptr; std::map base_layouts_by_name_; }; void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); auto set_current_layout(std::string current_layout) -> void; auto init_layouts_map(const std::vector& used_layouts) -> void; const static std::string XKB_LAYOUT_NAMES_KEY; const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY; Layout layout_; std::string tooltip_format_ = ""; std::map layouts_map_; bool is_variant_displayed; std::byte displayed_short_flag = static_cast(DispayedShortFlag::None); util::JsonParser parser_; std::mutex mutex_; Ipc ipc_; }; } // namespace waybar::modules::sway waybar-0.9.9/include/modules/sway/mode.hpp000066400000000000000000000010661416677246500205560ustar00rootroot00000000000000#pragma once #include #include "ALabel.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" namespace waybar::modules::sway { class Mode : public ALabel, public sigc::trackable { public: Mode(const std::string&, const Json::Value&); ~Mode() = default; auto update() -> void; private: void onEvent(const struct Ipc::ipc_response&); std::string mode_; util::JsonParser parser_; std::mutex mutex_; Ipc ipc_; }; } // namespace waybar::modules::sway waybar-0.9.9/include/modules/sway/window.hpp000066400000000000000000000023601416677246500211370ustar00rootroot00000000000000#pragma once #include #include #include "ALabel.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" namespace waybar::modules::sway { class Window : public ALabel, public sigc::trackable { public: Window(const std::string&, const waybar::Bar&, const Json::Value&); ~Window() = default; auto update() -> void; private: void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); std::tuple getFocusedNode(const Json::Value& nodes, std::string& output); void getTree(); std::string rewriteTitle(const std::string& title); const Bar& bar_; std::string window_; int windowId_; std::string app_id_; std::string old_app_id_; std::size_t app_nb_; util::JsonParser parser_; std::mutex mutex_; Ipc ipc_; }; } // namespace waybar::modules::sway waybar-0.9.9/include/modules/sway/workspaces.hpp000066400000000000000000000033371416677246500220160ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" namespace waybar::modules::sway { class Workspaces : public AModule, public sigc::trackable { public: Workspaces(const std::string&, const waybar::Bar&, const Json::Value&); ~Workspaces() = default; auto update() -> void; private: static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\""; static int convertWorkspaceNameToNum(std::string name); void onCmd(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&); bool filterButtons(); Gtk::Button& addButton(const Json::Value&); void onButtonReady(const Json::Value&, Gtk::Button&); std::string getIcon(const std::string&, const Json::Value&); const std::string getCycleWorkspace(std::vector::iterator, bool prev) const; uint16_t getWorkspaceIndex(const std::string& name) const; std::string trimWorkspaceName(std::string); bool handleScroll(GdkEventScroll*); const Bar& bar_; std::vector workspaces_; std::vector workspaces_order_; Gtk::Box box_; util::JsonParser parser_; std::unordered_map buttons_; std::mutex mutex_; Ipc ipc_; }; } // namespace waybar::modules::sway waybar-0.9.9/include/modules/temperature.hpp000066400000000000000000000007561416677246500212110ustar00rootroot00000000000000#pragma once #include #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { class Temperature : public ALabel { public: Temperature(const std::string&, const Json::Value&); ~Temperature() = default; auto update() -> void; private: float getTemperature(); bool isCritical(uint16_t); std::string file_path_; util::SleeperThread thread_; }; } // namespace waybar::modules waybar-0.9.9/include/modules/wlr/000077500000000000000000000000001416677246500167375ustar00rootroot00000000000000waybar-0.9.9/include/modules/wlr/taskbar.hpp000066400000000000000000000106601416677246500211020ustar00rootroot00000000000000#pragma once #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" #include "giomm/desktopappinfo.h" #include "util/json.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" namespace waybar::modules::wlr { class Taskbar; class Task { public: Task(const waybar::Bar&, const Json::Value&, Taskbar*, struct zwlr_foreign_toplevel_handle_v1 *, struct wl_seat*); ~Task(); public: enum State { MAXIMIZED = (1 << 0), MINIMIZED = (1 << 1), ACTIVE = (1 << 2), FULLSCREEN = (1 << 3), INVALID = (1 << 4) }; private: static uint32_t global_id; private: const waybar::Bar &bar_; const Json::Value &config_; Taskbar *tbar_; struct zwlr_foreign_toplevel_handle_v1 *handle_; struct wl_seat *seat_; uint32_t id_; Gtk::Button button_; Gtk::Box content_; Gtk::Image icon_; Gtk::Label text_before_; Gtk::Label text_after_; Glib::RefPtr app_info_; bool button_visible_ = false; bool ignored_ = false; bool with_icon_ = false; bool with_name_ = false; std::string format_before_; std::string format_after_; std::string format_tooltip_; std::string name_; std::string title_; std::string app_id_; uint32_t state_ = 0; private: std::string repr() const; std::string state_string(bool = false) const; void set_app_info_from_app_id_list(const std::string& app_id_list); bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); void hide_if_ignored(); public: /* Getter functions */ uint32_t id() const { return id_; } std::string title() const { return title_; } std::string app_id() const { return app_id_; } uint32_t state() const { return state_; } bool maximized() const { return state_ & MAXIMIZED; } bool minimized() const { return state_ & MINIMIZED; } bool active() const { return state_ & ACTIVE; } bool fullscreen() const { return state_ & FULLSCREEN; } public: /* Callbacks for the wlr protocol */ void handle_title(const char *); void handle_app_id(const char *); void handle_output_enter(struct wl_output *); void handle_output_leave(struct wl_output *); void handle_state(struct wl_array *); void handle_done(); void handle_closed(); /* Callbacks for Gtk events */ bool handle_clicked(GdkEventButton *); public: bool operator==(const Task&) const; bool operator!=(const Task&) const; public: void update(); public: /* Interaction with the tasks */ void maximize(bool); void minimize(bool); void activate(); void fullscreen(bool); void close(); }; using TaskPtr = std::unique_ptr; class Taskbar : public waybar::AModule { public: Taskbar(const std::string&, const waybar::Bar&, const Json::Value&); ~Taskbar(); void update(); private: const waybar::Bar &bar_; Gtk::Box box_; std::vector tasks_; std::vector> icon_themes_; std::unordered_set ignore_list_; std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; public: /* Callbacks for global registration */ void register_manager(struct wl_registry*, uint32_t name, uint32_t version); void register_seat(struct wl_registry*, uint32_t name, uint32_t version); /* Callbacks for the wlr protocol */ void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *); void handle_finished(); public: void add_button(Gtk::Button &); void move_button(Gtk::Button &, int); void remove_button(Gtk::Button &); void remove_task(uint32_t); bool show_output(struct wl_output *) const; bool all_outputs() const; const std::vector>& icon_themes() const; const std::unordered_set& ignore_list() const; const std::map& app_ids_replace_map() const; }; } /* namespace waybar::modules::wlr */ waybar-0.9.9/include/modules/wlr/workspace_manager.hpp000066400000000000000000000120441416677246500231410ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include "AModule.hpp" #include "bar.hpp" #include "ext-workspace-unstable-v1-client-protocol.h" namespace waybar::modules::wlr { class WorkspaceManager; class WorkspaceGroup; class Workspace { public: Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, zext_workspace_handle_v1 *workspace, uint32_t id); ~Workspace(); auto update() -> void; auto id() const -> uint32_t { return id_; } auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); } auto is_urgent() const -> bool { return state_ & static_cast(State::URGENT); } auto is_hidden() const -> bool { return state_ & static_cast(State::HIDDEN); } // wlr stuff auto handle_name(const std::string &name) -> void; auto handle_coordinates(const std::vector &coordinates) -> void; auto handle_state(const std::vector &state) -> void; auto handle_remove() -> void; auto handle_done() -> void; auto handle_clicked(GdkEventButton *bt) -> bool; auto show() -> void; auto hide() -> void; auto get_button_ref() -> Gtk::Button & { return button_; } auto get_name() -> std::string & { return name_; } auto get_coords() -> std::vector & { return coordinates_; } enum class State { ACTIVE = (1 << 0), URGENT = (1 << 1), HIDDEN = (1 << 2), }; private: auto get_icon() -> std::string; const Bar &bar_; const Json::Value &config_; WorkspaceGroup &workspace_group_; // wlr stuff zext_workspace_handle_v1 *workspace_handle_; uint32_t state_ = 0; uint32_t id_; std::string name_; std::vector coordinates_; static std::map icons_map_; std::string format_; bool with_icon_ = false; Gtk::Button button_; Gtk::Box content_; Gtk::Label label_; }; class WorkspaceGroup { public: WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id); ~WorkspaceGroup(); auto update() -> void; auto id() const -> uint32_t { return id_; } auto is_visible() const -> bool; auto remove_workspace(uint32_t id_) -> void; auto active_only() const -> bool; auto creation_delayed() const -> bool; auto workspaces() -> std::vector> & { return workspaces_; } auto sort_workspaces() -> void; auto set_need_to_sort() -> void { need_to_sort = true; } auto add_button(Gtk::Button &button) -> void; auto remove_button(Gtk::Button &button) -> void; // wlr stuff auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void; auto handle_remove() -> void; auto handle_output_enter(wl_output *output) -> void; auto handle_output_leave() -> void; auto handle_done() -> void; auto commit() -> void; private: static uint32_t workspace_global_id; const waybar::Bar &bar_; Gtk::Box &box_; const Json::Value &config_; WorkspaceManager &workspace_manager_; // wlr stuff zext_workspace_group_handle_v1 *workspace_group_handle_; wl_output *output_ = nullptr; uint32_t id_; std::vector> workspaces_; bool need_to_sort = false; }; class WorkspaceManager : public AModule { public: WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); ~WorkspaceManager() override; auto update() -> void override; auto all_outputs() const -> bool { return all_outputs_; } auto active_only() const -> bool { return active_only_; } auto workspace_comparator() const -> std::function &, std::unique_ptr &)>; auto creation_delayed() const -> bool { return creation_delayed_; } auto sort_workspaces() -> void; auto remove_workspace_group(uint32_t id_) -> void; // wlr stuff auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle) -> void; auto handle_done() -> void; auto handle_finished() -> void; auto commit() -> void; private: const waybar::Bar &bar_; Gtk::Box box_; std::vector> groups_; // wlr stuff zext_workspace_manager_v1 *workspace_manager_ = nullptr; static uint32_t group_global_id; bool sort_by_name_ = true; bool sort_by_coordinates_ = true; bool all_outputs_ = false; bool active_only_ = false; bool creation_delayed_ = false; }; } // namespace waybar::modules::wlr waybar-0.9.9/include/modules/wlr/workspace_manager_binding.hpp000066400000000000000000000006761416677246500246430ustar00rootroot00000000000000#include "ext-workspace-unstable-v1-client-protocol.h" namespace waybar::modules::wlr { void add_registry_listener(void *data); void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data); void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data); zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data); } waybar-0.9.9/include/util/000077500000000000000000000000001416677246500154405ustar00rootroot00000000000000waybar-0.9.9/include/util/SafeSignal.hpp000066400000000000000000000042331416677246500201670ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace waybar { /** * Thread-safe signal wrapper. * Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments. */ template struct SafeSignal : sigc::signal...)> { public: SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } template void emit(EmitArgs&&... args) { if (main_tid_ == std::this_thread::get_id()) { /* * Bypass the queue if the method is called the main thread. * Ensures that events emitted from the main thread are processed synchronously and saves a * few CPU cycles on locking/queuing. * As a downside, this makes main thread events prioritized over the other threads and * disrupts chronological order. */ signal_t::emit(std::forward(args)...); } else { { std::unique_lock lock(mutex_); queue_.emplace(std::forward(args)...); } dp_.emit(); } } template inline void operator()(EmitArgs&&... args) { emit(std::forward(args)...); } protected: using signal_t = sigc::signal...)>; using slot_t = decltype(std::declval().make_slot()); using arg_tuple_t = std::tuple...>; // ensure that unwrapped methods are not accessible using signal_t::emit_reverse; using signal_t::make_slot; void handle_event() { for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { auto args = queue_.front(); queue_.pop(); lock.unlock(); std::apply(cached_fn_, args); } } Glib::Dispatcher dp_; std::mutex mutex_; std::queue queue_; const std::thread::id main_tid_ = std::this_thread::get_id(); // cache functor for signal emission to avoid recreating it on each event const slot_t cached_fn_ = make_slot(); }; } // namespace waybar waybar-0.9.9/include/util/clara.hpp000066400000000000000000001246731416677246500172500ustar00rootroot00000000000000// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // See https://github.com/philsquared/Clara for more details // Clara v1.1.5 #ifndef CLARA_HPP_INCLUDED #define CLARA_HPP_INCLUDED #ifndef CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH 80 #endif #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #endif #ifndef CLARA_CONFIG_OPTIONAL_TYPE #ifdef __has_include #if __has_include() && __cplusplus >= 201703L #include #define CLARA_CONFIG_OPTIONAL_TYPE std::optional #endif #endif #endif // ----------- #included from clara_textflow.hpp ----------- // TextFlowCpp // // A single-header library for wrapping and laying out basic text, by Phil Nash // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // This project is hosted at https://github.com/philsquared/textflowcpp #ifndef CLARA_TEXTFLOW_HPP_INCLUDED #define CLARA_TEXTFLOW_HPP_INCLUDED #include #include #include #include #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 #endif namespace clara { namespace TextFlow { inline auto isWhitespace( char c ) -> bool { static std::string chars = " \t\n\r"; return chars.find( c ) != std::string::npos; } inline auto isBreakableBefore( char c ) -> bool { static std::string chars = "[({<|"; return chars.find( c ) != std::string::npos; } inline auto isBreakableAfter( char c ) -> bool { static std::string chars = "])}>.,:;*+-=&/\\"; return chars.find( c ) != std::string::npos; } class Columns; class Column { std::vector m_strings; size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; size_t m_indent = 0; size_t m_initialIndent = std::string::npos; public: class iterator { friend Column; Column const& m_column; size_t m_stringIndex = 0; size_t m_pos = 0; size_t m_len = 0; size_t m_end = 0; bool m_suffix = false; iterator( Column const& column, size_t stringIndex ) : m_column( column ), m_stringIndex( stringIndex ) {} auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } auto isBoundary( size_t at ) const -> bool { assert( at > 0 ); assert( at <= line().size() ); return at == line().size() || ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || isBreakableBefore( line()[at] ) || isBreakableAfter( line()[at-1] ); } void calcLength() { assert( m_stringIndex < m_column.m_strings.size() ); m_suffix = false; auto width = m_column.m_width-indent(); m_end = m_pos; while( m_end < line().size() && line()[m_end] != '\n' ) ++m_end; if( m_end < m_pos + width ) { m_len = m_end - m_pos; } else { size_t len = width; while (len > 0 && !isBoundary(m_pos + len)) --len; while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) --len; if (len > 0) { m_len = len; } else { m_suffix = true; m_len = width - 1; } } } auto indent() const -> size_t { auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; return initial == std::string::npos ? m_column.m_indent : initial; } auto addIndentAndSuffix(std::string const &plain) const -> std::string { return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); } public: using difference_type = std::ptrdiff_t; using value_type = std::string; using pointer = value_type*; using reference = value_type&; using iterator_category = std::forward_iterator_tag; explicit iterator( Column const& column ) : m_column( column ) { assert( m_column.m_width > m_column.m_indent ); assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); calcLength(); if( m_len == 0 ) m_stringIndex++; // Empty string } auto operator *() const -> std::string { assert( m_stringIndex < m_column.m_strings.size() ); assert( m_pos <= m_end ); return addIndentAndSuffix(line().substr(m_pos, m_len)); } auto operator ++() -> iterator& { m_pos += m_len; if( m_pos < line().size() && line()[m_pos] == '\n' ) m_pos += 1; else while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) ++m_pos; if( m_pos == line().size() ) { m_pos = 0; ++m_stringIndex; } if( m_stringIndex < m_column.m_strings.size() ) calcLength(); return *this; } auto operator ++(int) -> iterator { iterator prev( *this ); operator++(); return prev; } auto operator ==( iterator const& other ) const -> bool { return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex && &m_column == &other.m_column; } auto operator !=( iterator const& other ) const -> bool { return !operator==( other ); } }; using const_iterator = iterator; explicit Column( std::string const& text ) { m_strings.push_back( text ); } auto width( size_t newWidth ) -> Column& { assert( newWidth > 0 ); m_width = newWidth; return *this; } auto indent( size_t newIndent ) -> Column& { m_indent = newIndent; return *this; } auto initialIndent( size_t newIndent ) -> Column& { m_initialIndent = newIndent; return *this; } auto width() const -> size_t { return m_width; } auto begin() const -> iterator { return iterator( *this ); } auto end() const -> iterator { return { *this, m_strings.size() }; } inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { bool first = true; for( auto line : col ) { if( first ) first = false; else os << "\n"; os << line; } return os; } auto operator + ( Column const& other ) -> Columns; auto toString() const -> std::string { std::ostringstream oss; oss << *this; return oss.str(); } }; class Spacer : public Column { public: explicit Spacer( size_t spaceWidth ) : Column( "" ) { width( spaceWidth ); } }; class Columns { std::vector m_columns; public: class iterator { friend Columns; struct EndTag {}; std::vector const& m_columns; std::vector m_iterators; size_t m_activeIterators; iterator( Columns const& columns, EndTag ) : m_columns( columns.m_columns ), m_activeIterators( 0 ) { m_iterators.reserve( m_columns.size() ); for( auto const& col : m_columns ) m_iterators.push_back( col.end() ); } public: using difference_type = std::ptrdiff_t; using value_type = std::string; using pointer = value_type*; using reference = value_type&; using iterator_category = std::forward_iterator_tag; explicit iterator( Columns const& columns ) : m_columns( columns.m_columns ), m_activeIterators( m_columns.size() ) { m_iterators.reserve( m_columns.size() ); for( auto const& col : m_columns ) m_iterators.push_back( col.begin() ); } auto operator ==( iterator const& other ) const -> bool { return m_iterators == other.m_iterators; } auto operator !=( iterator const& other ) const -> bool { return m_iterators != other.m_iterators; } auto operator *() const -> std::string { std::string row, padding; for( size_t i = 0; i < m_columns.size(); ++i ) { auto width = m_columns[i].width(); if( m_iterators[i] != m_columns[i].end() ) { std::string col = *m_iterators[i]; row += padding + col; if( col.size() < width ) padding = std::string( width - col.size(), ' ' ); else padding = ""; } else { padding += std::string( width, ' ' ); } } return row; } auto operator ++() -> iterator& { for( size_t i = 0; i < m_columns.size(); ++i ) { if (m_iterators[i] != m_columns[i].end()) ++m_iterators[i]; } return *this; } auto operator ++(int) -> iterator { iterator prev( *this ); operator++(); return prev; } }; using const_iterator = iterator; auto begin() const -> iterator { return iterator( *this ); } auto end() const -> iterator { return { *this, iterator::EndTag() }; } auto operator += ( Column const& col ) -> Columns& { m_columns.push_back( col ); return *this; } auto operator + ( Column const& col ) -> Columns { Columns combined = *this; combined += col; return combined; } inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { bool first = true; for( auto line : cols ) { if( first ) first = false; else os << "\n"; os << line; } return os; } auto toString() const -> std::string { std::ostringstream oss; oss << *this; return oss.str(); } }; inline auto Column::operator + ( Column const& other ) -> Columns { Columns cols; cols += *this; cols += other; return cols; } }} #endif // CLARA_TEXTFLOW_HPP_INCLUDED // ----------- end of #include from clara_textflow.hpp ----------- // ........... back in clara.hpp #include #include #include #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) #define CLARA_PLATFORM_WINDOWS #endif namespace clara { namespace detail { // Traits for extracting arg and return type of lambdas (for single argument lambdas) template struct UnaryLambdaTraits : UnaryLambdaTraits {}; template struct UnaryLambdaTraits { static const bool isValid = false; }; template struct UnaryLambdaTraits { static const bool isValid = true; using ArgType = typename std::remove_const::type>::type; using ReturnType = ReturnT; }; class TokenStream; // Transport for raw args (copied from main args, or supplied via init list for testing) class Args { friend TokenStream; std::string m_exeName; std::vector m_args; public: Args( int argc, char const* const* argv ) : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} Args( std::initializer_list args ) : m_exeName( *args.begin() ), m_args( args.begin()+1, args.end() ) {} auto exeName() const -> std::string { return m_exeName; } }; // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string // may encode an option + its argument if the : or = form is used enum class TokenType { Option, Argument }; struct Token { TokenType type; std::string token; }; inline auto isOptPrefix( char c ) -> bool { return c == '-' #ifdef CLARA_PLATFORM_WINDOWS || c == '/' #endif ; } // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled class TokenStream { using Iterator = std::vector::const_iterator; Iterator it; Iterator itEnd; std::vector m_tokenBuffer; void loadBuffer() { m_tokenBuffer.resize( 0 ); // Skip any empty strings while( it != itEnd && it->empty() ) ++it; if( it != itEnd ) { auto const &next = *it; if( isOptPrefix( next[0] ) ) { auto delimiterPos = next.find_first_of( " :=" ); if( delimiterPos != std::string::npos ) { m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); } else { if( next[1] != '-' && next.size() > 2 ) { std::string opt = "- "; for( size_t i = 1; i < next.size(); ++i ) { opt[1] = next[i]; m_tokenBuffer.push_back( { TokenType::Option, opt } ); } } else { m_tokenBuffer.push_back( { TokenType::Option, next } ); } } } else { m_tokenBuffer.push_back( { TokenType::Argument, next } ); } } } public: explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { loadBuffer(); } explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; } auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } auto operator*() const -> Token { assert( !m_tokenBuffer.empty() ); return m_tokenBuffer.front(); } auto operator->() const -> Token const * { assert( !m_tokenBuffer.empty() ); return &m_tokenBuffer.front(); } auto operator++() -> TokenStream & { if( m_tokenBuffer.size() >= 2 ) { m_tokenBuffer.erase( m_tokenBuffer.begin() ); } else { if( it != itEnd ) ++it; loadBuffer(); } return *this; } }; class ResultBase { public: enum Type { Ok, LogicError, RuntimeError }; protected: ResultBase( Type type ) : m_type( type ) {} virtual ~ResultBase() = default; virtual void enforceOk() const = 0; Type m_type; }; template class ResultValueBase : public ResultBase { public: auto value() const -> T const & { enforceOk(); return m_value; } protected: ResultValueBase( Type type ) : ResultBase( type ) {} ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { if( m_type == ResultBase::Ok ) new( &m_value ) T( other.m_value ); } ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { new( &m_value ) T( value ); } auto operator=( ResultValueBase const &other ) -> ResultValueBase & { if( m_type == ResultBase::Ok ) m_value.~T(); ResultBase::operator=(other); if( m_type == ResultBase::Ok ) new( &m_value ) T( other.m_value ); return *this; } ~ResultValueBase() override { if( m_type == Ok ) m_value.~T(); } union { T m_value; }; }; template<> class ResultValueBase : public ResultBase { protected: using ResultBase::ResultBase; }; template class BasicResult : public ResultValueBase { public: template explicit BasicResult( BasicResult const &other ) : ResultValueBase( other.type() ), m_errorMessage( other.errorMessage() ) { assert( type() != ResultBase::Ok ); } template static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } static auto ok() -> BasicResult { return { ResultBase::Ok }; } static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } explicit operator bool() const { return m_type == ResultBase::Ok; } auto type() const -> ResultBase::Type { return m_type; } auto errorMessage() const -> std::string { return m_errorMessage; } protected: void enforceOk() const override { // Errors shouldn't reach this point, but if they do // the actual error message will be in m_errorMessage assert( m_type != ResultBase::LogicError ); assert( m_type != ResultBase::RuntimeError ); if( m_type != ResultBase::Ok ) std::abort(); } std::string m_errorMessage; // Only populated if resultType is an error BasicResult( ResultBase::Type type, std::string const &message ) : ResultValueBase(type), m_errorMessage(message) { assert( m_type != ResultBase::Ok ); } using ResultValueBase::ResultValueBase; using ResultBase::m_type; }; enum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame }; class ParseState { public: ParseState( ParseResultType type, TokenStream const &remainingTokens ) : m_type(type), m_remainingTokens( remainingTokens ) {} auto type() const -> ParseResultType { return m_type; } auto remainingTokens() const -> TokenStream { return m_remainingTokens; } private: ParseResultType m_type; TokenStream m_remainingTokens; }; using Result = BasicResult; using ParserResult = BasicResult; using InternalParseResult = BasicResult; struct HelpColumns { std::string left; std::string right; }; template inline auto convertInto( std::string const &source, T& target ) -> ParserResult { std::stringstream ss; ss << source; ss >> target; if( ss.fail() ) return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); else return ParserResult::ok( ParseResultType::Matched ); } inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { target = source; return ParserResult::ok( ParseResultType::Matched ); } inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { std::string srcLC = source; std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") target = true; else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") target = false; else return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); return ParserResult::ok( ParseResultType::Matched ); } #ifdef CLARA_CONFIG_OPTIONAL_TYPE template inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { T temp; auto result = convertInto( source, temp ); if( result ) target = std::move(temp); return result; } #endif // CLARA_CONFIG_OPTIONAL_TYPE struct NonCopyable { NonCopyable() = default; NonCopyable( NonCopyable const & ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable &operator=( NonCopyable const & ) = delete; NonCopyable &operator=( NonCopyable && ) = delete; }; struct BoundRef : NonCopyable { virtual ~BoundRef() = default; virtual auto isContainer() const -> bool { return false; } virtual auto isFlag() const -> bool { return false; } }; struct BoundValueRefBase : BoundRef { virtual auto setValue( std::string const &arg ) -> ParserResult = 0; }; struct BoundFlagRefBase : BoundRef { virtual auto setFlag( bool flag ) -> ParserResult = 0; virtual auto isFlag() const -> bool { return true; } }; template struct BoundValueRef : BoundValueRefBase { T &m_ref; explicit BoundValueRef( T &ref ) : m_ref( ref ) {} auto setValue( std::string const &arg ) -> ParserResult override { return convertInto( arg, m_ref ); } }; template struct BoundValueRef> : BoundValueRefBase { std::vector &m_ref; explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} auto isContainer() const -> bool override { return true; } auto setValue( std::string const &arg ) -> ParserResult override { T temp; auto result = convertInto( arg, temp ); if( result ) m_ref.push_back( temp ); return result; } }; struct BoundFlagRef : BoundFlagRefBase { bool &m_ref; explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} auto setFlag( bool flag ) -> ParserResult override { m_ref = flag; return ParserResult::ok( ParseResultType::Matched ); } }; template struct LambdaInvoker { static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); template static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { return lambda( arg ); } }; template<> struct LambdaInvoker { template static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { lambda( arg ); return ParserResult::ok( ParseResultType::Matched ); } }; template inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { ArgType temp{}; auto result = convertInto( arg, temp ); return !result ? result : LambdaInvoker::ReturnType>::invoke( lambda, temp ); } template struct BoundLambda : BoundValueRefBase { L m_lambda; static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} auto setValue( std::string const &arg ) -> ParserResult override { return invokeLambda::ArgType>( m_lambda, arg ); } }; template struct BoundFlagLambda : BoundFlagRefBase { L m_lambda; static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} auto setFlag( bool flag ) -> ParserResult override { return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); } }; enum class Optionality { Optional, Required }; struct Parser; class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } auto parse( Args const &args ) const -> InternalParseResult { return parse( args.exeName(), TokenStream( args ) ); } }; template class ComposableParserImpl : public ParserBase { public: template auto operator|( T const &other ) const -> Parser; template auto operator+( T const &other ) const -> Parser; }; // Common code and state for Args and Opts template class ParserRefImpl : public ComposableParserImpl { protected: Optionality m_optionality = Optionality::Optional; std::shared_ptr m_ref; std::string m_hint; std::string m_description; explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} public: template ParserRefImpl( T &ref, std::string const &hint ) : m_ref( std::make_shared>( ref ) ), m_hint( hint ) {} template ParserRefImpl( LambdaT const &ref, std::string const &hint ) : m_ref( std::make_shared>( ref ) ), m_hint(hint) {} auto operator()( std::string const &description ) -> DerivedT & { m_description = description; return static_cast( *this ); } auto optional() -> DerivedT & { m_optionality = Optionality::Optional; return static_cast( *this ); }; auto required() -> DerivedT & { m_optionality = Optionality::Required; return static_cast( *this ); }; auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } auto cardinality() const -> size_t override { if( m_ref->isContainer() ) return 0; else return 1; } auto hint() const -> std::string { return m_hint; } }; class ExeName : public ComposableParserImpl { std::shared_ptr m_name; std::shared_ptr m_ref; template static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { return std::make_shared>( lambda) ; } public: ExeName() : m_name( std::make_shared( "" ) ) {} explicit ExeName( std::string &ref ) : ExeName() { m_ref = std::make_shared>( ref ); } template explicit ExeName( LambdaT const& lambda ) : ExeName() { m_ref = std::make_shared>( lambda ); } // The exe name is not parsed out of the normal tokens, but is handled specially auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); } auto name() const -> std::string { return *m_name; } auto set( std::string const& newName ) -> ParserResult { auto lastSlash = newName.find_last_of( "\\/" ); auto filename = ( lastSlash == std::string::npos ) ? newName : newName.substr( lastSlash+1 ); *m_name = filename; if( m_ref ) return m_ref->setValue( filename ); else return ParserResult::ok( ParseResultType::Matched ); } }; class Arg : public ParserRefImpl { public: using ParserRefImpl::ParserRefImpl; auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { auto validationResult = validate(); if( !validationResult ) return InternalParseResult( validationResult ); auto remainingTokens = tokens; auto const &token = *remainingTokens; if( token.type != TokenType::Argument ) return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); assert( !m_ref->isFlag() ); auto valueRef = static_cast( m_ref.get() ); auto result = valueRef->setValue( remainingTokens->token ); if( !result ) return InternalParseResult( result ); else return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); } }; inline auto normaliseOpt( std::string const &optName ) -> std::string { #ifdef CLARA_PLATFORM_WINDOWS if( optName[0] == '/' ) return "-" + optName.substr( 1 ); else #endif return optName; } class Opt : public ParserRefImpl { protected: std::vector m_optNames; public: template explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} template Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} template Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} auto operator[]( std::string const &optName ) -> Opt & { m_optNames.push_back( optName ); return *this; } auto getHelpColumns() const -> std::vector { std::ostringstream oss; bool first = true; for( auto const &opt : m_optNames ) { if (first) first = false; else oss << ", "; oss << opt; } if( !m_hint.empty() ) oss << " <" << m_hint << ">"; return { { oss.str(), m_description } }; } auto isMatch( std::string const &optToken ) const -> bool { auto normalisedToken = normaliseOpt( optToken ); for( auto const &name : m_optNames ) { if( normaliseOpt( name ) == normalisedToken ) return true; } return false; } using ParserBase::parse; auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { auto validationResult = validate(); if( !validationResult ) return InternalParseResult( validationResult ); auto remainingTokens = tokens; if( remainingTokens && remainingTokens->type == TokenType::Option ) { auto const &token = *remainingTokens; if( isMatch(token.token ) ) { if( m_ref->isFlag() ) { auto flagRef = static_cast( m_ref.get() ); auto result = flagRef->setFlag( true ); if( !result ) return InternalParseResult( result ); if( result.value() == ParseResultType::ShortCircuitAll ) return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); } else { auto valueRef = static_cast( m_ref.get() ); ++remainingTokens; if( !remainingTokens ) return InternalParseResult::runtimeError( "Expected argument following " + token.token ); auto const &argToken = *remainingTokens; if( argToken.type != TokenType::Argument ) return InternalParseResult::runtimeError( "Expected argument following " + token.token ); auto result = valueRef->setValue( argToken.token ); if( !result ) return InternalParseResult( result ); if( result.value() == ParseResultType::ShortCircuitAll ) return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); } return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); } } return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); } auto validate() const -> Result override { if( m_optNames.empty() ) return Result::logicError( "No options supplied to Opt" ); for( auto const &name : m_optNames ) { if( name.empty() ) return Result::logicError( "Option name cannot be empty" ); #ifdef CLARA_PLATFORM_WINDOWS if( name[0] != '-' && name[0] != '/' ) return Result::logicError( "Option name must begin with '-' or '/'" ); #else if( name[0] != '-' ) return Result::logicError( "Option name must begin with '-'" ); #endif } return ParserRefImpl::validate(); } }; struct Help : Opt { Help( bool &showHelpFlag ) : Opt([&]( bool flag ) { showHelpFlag = flag; return ParserResult::ok( ParseResultType::ShortCircuitAll ); }) { static_cast( *this ) ("display usage information") ["-?"]["-h"]["--help"] .optional(); } }; struct Parser : ParserBase { mutable ExeName m_exeName; std::vector m_options; std::vector m_args; auto operator|=( ExeName const &exeName ) -> Parser & { m_exeName = exeName; return *this; } auto operator|=( Arg const &arg ) -> Parser & { m_args.push_back(arg); return *this; } auto operator|=( Opt const &opt ) -> Parser & { m_options.push_back(opt); return *this; } auto operator|=( Parser const &other ) -> Parser & { m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); return *this; } template auto operator|( T const &other ) const -> Parser { return Parser( *this ) |= other; } // Forward deprecated interface with '+' instead of '|' template auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } template auto operator+( T const &other ) const -> Parser { return operator|( other ); } auto getHelpColumns() const -> std::vector { std::vector cols; for (auto const &o : m_options) { auto childCols = o.getHelpColumns(); cols.insert( cols.end(), childCols.begin(), childCols.end() ); } return cols; } void writeToStream( std::ostream &os ) const { if (!m_exeName.name().empty()) { os << "usage:\n" << " " << m_exeName.name() << " "; bool required = true, first = true; for( auto const &arg : m_args ) { if (first) first = false; else os << " "; if( arg.isOptional() && required ) { os << "["; required = false; } os << "<" << arg.hint() << ">"; if( arg.cardinality() == 0 ) os << " ... "; } if( !required ) os << "]"; if( !m_options.empty() ) os << " options"; os << "\n\nwhere options are:" << std::endl; } auto rows = getHelpColumns(); size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; size_t optWidth = 0; for( auto const &cols : rows ) optWidth = (std::max)(optWidth, cols.left.size() + 2); optWidth = (std::min)(optWidth, consoleWidth/2); for( auto const &cols : rows ) { auto row = TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + TextFlow::Spacer(4) + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); os << row << std::endl; } } friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { parser.writeToStream( os ); return os; } auto validate() const -> Result override { for( auto const &opt : m_options ) { auto result = opt.validate(); if( !result ) return result; } for( auto const &arg : m_args ) { auto result = arg.validate(); if( !result ) return result; } return Result::ok(); } using ParserBase::parse; auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { struct ParserInfo { ParserBase const* parser = nullptr; size_t count = 0; }; const size_t totalParsers = m_options.size() + m_args.size(); assert( totalParsers < 512 ); // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do ParserInfo parseInfos[512]; { size_t i = 0; for (auto const &opt : m_options) parseInfos[i++].parser = &opt; for (auto const &arg : m_args) parseInfos[i++].parser = &arg; } m_exeName.set( exeName ); auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); while( result.value().remainingTokens() ) { bool tokenParsed = false; for( size_t i = 0; i < totalParsers; ++i ) { auto& parseInfo = parseInfos[i]; if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); if (!result) return result; if (result.value().type() != ParseResultType::NoMatch) { tokenParsed = true; ++parseInfo.count; break; } } } if( result.value().type() == ParseResultType::ShortCircuitAll ) return result; if( !tokenParsed ) return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); } // !TBD Check missing required options return result; } }; template template auto ComposableParserImpl::operator|( T const &other ) const -> Parser { return Parser() | static_cast( *this ) | other; } } // namespace detail // A Combined parser using detail::Parser; // A parser for options using detail::Opt; // A parser for arguments using detail::Arg; // Wrapper for argc, argv from main() using detail::Args; // Specifies the name of the executable using detail::ExeName; // Convenience wrapper for option parser that specifies the help option using detail::Help; // enum of result types from a parse using detail::ParseResultType; // Result type for parser operation using detail::ParserResult; } // namespace clara #endif // CLARA_HPP_INCLUDED waybar-0.9.9/include/util/command.hpp000066400000000000000000000074211416677246500175730ustar00rootroot00000000000000#pragma once #include #include #include #include #ifdef __linux__ #include #endif #ifdef __FreeBSD__ #include #endif #include extern std::mutex reap_mtx; extern std::list reap; namespace waybar::util::command { struct res { int exit_code; std::string out; }; inline std::string read(FILE* fp) { std::array buffer = {0}; std::string output; while (feof(fp) == 0) { if (fgets(buffer.data(), 128, fp) != nullptr) { output += buffer.data(); } } // Remove last newline if (!output.empty() && output[output.length() - 1] == '\n') { output.erase(output.length() - 1); } return output; } inline int close(FILE* fp, pid_t pid) { int stat = -1; pid_t ret; fclose(fp); do { ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED); if (WIFEXITED(stat)) { spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat)); } else if (WIFSIGNALED(stat)) { spdlog::debug("Cmd killed by {}", WTERMSIG(stat)); } else if (WIFSTOPPED(stat)) { spdlog::debug("Cmd stopped by {}", WSTOPSIG(stat)); } else if (WIFCONTINUED(stat)) { spdlog::debug("Cmd continued"); } else if (ret == -1) { spdlog::debug("waitpid failed: {}", strerror(errno)); } else { break; } } while (!WIFEXITED(stat) && !WIFSIGNALED(stat)); return stat; } inline FILE* open(const std::string& cmd, int& pid) { if (cmd == "") return nullptr; int fd[2]; pipe(fd); pid_t child_pid = fork(); if (child_pid < 0) { spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno)); return nullptr; } if (!child_pid) { int err; sigset_t mask; sigfillset(&mask); // Reset sigmask err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); if (err != 0) spdlog::error("pthread_sigmask in open failed: {}", strerror(err)); // Kill child if Waybar exits int deathsig = SIGTERM; #ifdef __linux__ if (prctl(PR_SET_PDEATHSIG, deathsig) != 0) { spdlog::error("prctl(PR_SET_PDEATHSIG) in open failed: {}", strerror(errno)); } #endif #ifdef __FreeBSD__ if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, reinterpret_cast(&deathsig)) == -1) { spdlog::error("procctl(PROC_PDEATHSIG_CTL) in open failed: {}", strerror(errno)); } #endif ::close(fd[0]); dup2(fd[1], 1); setpgid(child_pid, child_pid); execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); exit(0); } else { ::close(fd[1]); } pid = child_pid; return fdopen(fd[0], "r"); } inline struct res exec(const std::string& cmd) { int pid; auto fp = command::open(cmd, pid); if (!fp) return {-1, ""}; auto output = command::read(fp); auto stat = command::close(fp, pid); return {WEXITSTATUS(stat), output}; } inline struct res execNoRead(const std::string& cmd) { int pid; auto fp = command::open(cmd, pid); if (!fp) return {-1, ""}; auto stat = command::close(fp, pid); return {WEXITSTATUS(stat), ""}; } inline int32_t forkExec(const std::string& cmd) { if (cmd == "") return -1; pid_t pid = fork(); if (pid < 0) { spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno)); return pid; } // Child executes the command if (!pid) { int err; sigset_t mask; sigfillset(&mask); // Reset sigmask err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err)); setpgid(pid, pid); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); exit(0); } else { reap_mtx.lock(); reap.push_back(pid); reap_mtx.unlock(); spdlog::debug("Added child to reap list: {}", pid); } return pid; } } // namespace waybar::util::command waybar-0.9.9/include/util/format.hpp000066400000000000000000000056761416677246500174570ustar00rootroot00000000000000#pragma once #include class pow_format { public: pow_format(long long val, std::string&& unit, bool binary = false): val_(val), unit_(unit), binary_(binary) { }; long long val_; std::string unit_; bool binary_; }; namespace fmt { template <> struct formatter { char spec = 0; int width = 0; template constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == ':') ++it; if (it && (*it == '>' || *it == '<' || *it == '=')) { spec = *it; ++it; } if (it == end || *it == '}') return it; if ('0' <= *it && *it <= '9') { // We ignore it for now, but keep it for compatibility with // existing configs where the format for pow_format'ed numbers was // 'string' and specifications such as {:>9} were valid. // The rationale for ignoring it is that the only reason to specify // an alignment and a with is to get a fixed width bar, and ">" is // sufficient in this implementation. #if FMT_VERSION < 80000 width = parse_nonnegative_int(it, end, ctx); #else width = detail::parse_nonnegative_int(it, end, -1); #endif } return it; } template auto format(const pow_format& s, FormatContext &ctx) -> decltype (ctx.out()) { const char* units[] = { "", "k", "M", "G", "T", "P", nullptr}; auto base = s.binary_ ? 1024ull : 1000ll; auto fraction = (double) s.val_; int pow; for (pow = 0; units[pow+1] != nullptr && fraction / base >= 1; ++pow) { fraction /= base; } auto max_width = 4 // coeff in {:.3g} format + 1 // prefix from units array + s.binary_ // for the 'i' in GiB. + s.unit_.length(); const char * format; std::string string; switch (spec) { case '>': return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); case '<': return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); case '=': format = "{coefficient:<4.3g}{padding}{prefix}{unit}"; break; case 0: default: format = "{coefficient:.3g}{prefix}{unit}"; break; } return format_to(ctx.out(), format , fmt::arg("coefficient", fraction) , fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")) , fmt::arg("unit", s.unit_) , fmt::arg("padding", pow ? "" : s.binary_ ? " " : " ") ); } }; } waybar-0.9.9/include/util/json.hpp000066400000000000000000000011761416677246500171270ustar00rootroot00000000000000#pragma once #include namespace waybar::util { struct JsonParser { JsonParser() {} const Json::Value parse(const std::string& data) const { Json::Value root(Json::objectValue); if (data.empty()) { return root; } std::unique_ptr const reader(builder_.newCharReader()); std::string err; bool res = reader->parse(data.c_str(), data.c_str() + data.size(), &root, &err); if (!res) throw std::runtime_error(err); return root; } ~JsonParser() = default; private: Json::CharReaderBuilder builder_; }; } // namespace waybar::util waybar-0.9.9/include/util/rfkill.hpp000066400000000000000000000010071416677246500174320ustar00rootroot00000000000000#pragma once #include #include #include #include namespace waybar::util { class Rfkill : public sigc::trackable { public: Rfkill(enum rfkill_type rfkill_type); ~Rfkill(); bool getState() const; sigc::signal on_update; private: enum rfkill_type rfkill_type_; bool state_ = false; int fd_ = -1; bool on_event(Glib::IOCondition cond); }; } // namespace waybar::util waybar-0.9.9/include/util/sleeper_thread.hpp000066400000000000000000000046041416677246500211430ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace waybar::util { /** * Defer pthread_cancel until the end of a current scope. * * Required to protect a scope where it's unsafe to raise `__forced_unwind` exception. * An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such * a method may result in a `std::terminate` call. */ class CancellationGuard { int oldstate; public: CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); } ~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); } }; class SleeperThread { public: SleeperThread() = default; SleeperThread(std::function func) : thread_{[this, func] { while (do_run_) { signal_ = false; func(); } }} {} SleeperThread& operator=(std::function func) { thread_ = std::thread([this, func] { while (do_run_) { signal_ = false; func(); } }); return *this; } bool isRunning() const { return do_run_; } auto sleep_for(std::chrono::system_clock::duration dur) { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; }); } auto sleep_until( std::chrono::time_point time_point) { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; }); } auto wake_up() { { std::lock_guard lck(mutex_); signal_ = true; } condvar_.notify_all(); } auto stop() { { std::lock_guard lck(mutex_); signal_ = true; do_run_ = false; } condvar_.notify_all(); auto handle = thread_.native_handle(); if (handle != 0) { // TODO: find a proper way to terminate thread... pthread_cancel(handle); } } ~SleeperThread() { stop(); if (thread_.joinable()) { thread_.join(); } } private: std::thread thread_; std::condition_variable condvar_; std::mutex mutex_; bool do_run_ = true; bool signal_ = false; }; } // namespace waybar::util waybar-0.9.9/include/util/string.hpp000066400000000000000000000006751416677246500174670ustar00rootroot00000000000000#include const std::string WHITESPACE = " \n\r\t\f\v"; std::string ltrim(const std::string s) { size_t begin = s.find_first_not_of(WHITESPACE); return (begin == std::string::npos) ? "" : s.substr(begin); } std::string rtrim(const std::string s) { size_t end = s.find_last_not_of(WHITESPACE); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } std::string trim(const std::string& s) { return rtrim(ltrim(s)); } waybar-0.9.9/include/util/ustring_clen.hpp000066400000000000000000000001721416677246500206450ustar00rootroot00000000000000#pragma once #include // calculate column width of ustring int ustring_clen(const Glib::ustring &str);waybar-0.9.9/include/util/waybar_time.hpp000066400000000000000000000015311416677246500204540ustar00rootroot00000000000000#pragma once #include #include namespace waybar { struct waybar_time { std::locale locale; date::zoned_seconds ztime; }; } // namespace waybar template <> struct fmt::formatter { std::string_view specs; template constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); if (it != ctx.end() && *it == ':') { ++it; } auto end = it; while (end != ctx.end() && *end != '}') { ++end; } if (end != it) { specs = {it, std::string_view::size_type(end - it)}; } return end; } template auto format(const waybar::waybar_time& t, FormatContext& ctx) { return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime)); } }; waybar-0.9.9/man/000077500000000000000000000000001416677246500136135ustar00rootroot00000000000000waybar-0.9.9/man/waybar-backlight.5.scd000066400000000000000000000033701416677246500176670ustar00rootroot00000000000000waybar-backlight(5) # NAME waybar - backlight module # DESCRIPTION The *backlight* module displays the current backlight level. # CONFIGURATION *interval*: ++ typeof: integer ++ default: 2 ++ The interval in which information gets polled. *format*: ++ typeof: string ++ default: {percent}% ++ The format, how information should be displayed. On {} data gets inserted. *max-length*: ++ typeof: integer ++ The maximum length in characters the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *states*: ++ typeof: array ++ A number of backlight states which get activated on certain brightness levels. *on-click*: ++ typeof: string ++ Command to execute when the module is clicked. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when the module is right clicked. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when performing a scroll up on the module. *on-scroll-down*: ++ typeof: string Command to execute when performing a scroll down on the module. *smooth-scrolling-threshold*: ++ typeof: double Threshold to be used when scrolling. # EXAMPLE: ``` "backlight": { "device": "intel_backlight", "format": "{percent}% {icon}", "format-icons": ["", "ï„‘"] } ``` # STYLE - *#backlight* waybar-0.9.9/man/waybar-battery.5.scd000066400000000000000000000110501416677246500174030ustar00rootroot00000000000000waybar-battery(5) # NAME waybar - battery module # DESCRIPTION The *battery* module displays the current capacity and state (eg. charging) of your battery. # CONFIGURATION *bat*: ++ typeof: string ++ The battery to monitor, as in /sys/class/power_supply/ instead of auto detect. *adapter*: ++ typeof: string ++ The adapter to monitor, as in /sys/class/power_supply/ instead of auto detect. *full-at*: ++ typeof: integer ++ Define the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%. *design-capacity*: ++ typeof: bool ++ default: false ++ Option to use the battery design capacity instead of it's current maximal capacity. *interval*: ++ typeof: integer ++ default: 60 ++ The interval in which the information gets polled. *states*: ++ typeof: array ++ A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*. *format*: ++ typeof: string ++ default: {capacity}% ++ The format, how the time should be displayed. *format-time*: ++ typeof: string ++ default: {H} h {M} min ++ The format, how the time should be displayed. *format-icons*: ++ typeof: array/object ++ Based on the current capacity, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. *max-length*: ++ typeof: integer++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *rotate*: ++ typeof: integer++ Positive value to rotate the text label. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{capacity}*: Capacity in percentage *{power}*: Power in watts *{icon}*: Icon, as defined in *format-icons*. *{time}*: Estimate of time until full or empty. Note that this is based on the power draw at the last refresh time, not an average. # TIME FORMAT The *battery* module allows you to define how time should be formatted via *format-time*. The two arguments are: *{H}*: Hours *{M}*: Minutes # CUSTOM FORMATS The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected. *format-*: With *states*, a custom format can be set depending on the capacity of your battery. *format-*: With the status, a custom format can be set depending on the status in /sys/class/power_supply//status (in lowercase). *format--*: You can also set a custom format depending on both values. # STATES - Every entry (*state*) consists of a ** (typeof: *string*) and a ** (typeof: *integer*). - The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the ** of the state. Each class gets activated when the current capacity is equal or below the configured **. - Also each state can have its own *format*. Those con be configured via *format-*. Or if you want to differentiate a bit more even as *format--*. For more information see *custom-formats*. # EXAMPLES ``` "battery": { "bat": "BAT2", "interval": 60, "states": { "warning": 30, "critical": 15 }, "format": "{capacity}% {icon}", "format-icons": ["", "", "", "ï‰", ""], "max-length": 25 } ``` # STYLE - *#battery* - *#battery.* - ** is the value of /sys/class/power_supply//status in lowercase. - *#battery.* - ** can be defined in the *config*. For more information see *states*. - *#battery..* - Combination of both ** and **. waybar-0.9.9/man/waybar-bluetooth.5.scd000066400000000000000000000041551416677246500177460ustar00rootroot00000000000000waybar-bluetooth(5) # NAME waybar - bluetooth module # DESCRIPTION The *bluetooth* module displays information about the status of the device's bluetooth device. # CONFIGURATION Addressed by *bluetooth* *format*: ++ typeof: string ++ default: *{icon}* ++ The format, how information should be displayed. This format is used when other formats aren't specified. *format-icons*: ++ typeof: array/object ++ Based on the device status, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: *true* ++ Option to disable tooltip on hover. *tooltip-format*: ++ typeof: string ++ The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified. # FORMAT REPLACEMENTS *{status}*: Status of the bluetooth device. *{icon}*: Icon, as defined in *format-icons*. # EXAMPLES ``` "bluetooth": { "format": "{icon}", "format-alt": "bluetooth: {status}", "format-icons": { "enabled": "", "disabled": "" }, "tooltip-format": "{}" } ``` # STYLE - *#bluetooth* waybar-0.9.9/man/waybar-clock.5.scd000066400000000000000000000051141416677246500170300ustar00rootroot00000000000000waybar-clock(5) # NAME waybar - clock module # DESCRIPTION The *clock* module displays the current date and time. # CONFIGURATION *interval*: ++ typeof: integer ++ default: 60 ++ The interval in which the information gets polled. *format*: ++ typeof: string ++ default: {:%H:%M} ++ The format, how the date and time should be displayed. ++ It uses the format of the date library. See https://howardhinnant.github.io/date/date.html#to_stream_formatting for details. *timezone*: ++ typeof: string ++ default: inferred local timezone ++ The timezone to display the time in, e.g. America/New_York. ++ This field will be ignored if *timezones* field is set and have at least one value. *timezones*: ++ typeof: list of strings ++ A list of timezones to use for time display, changed using the scroll wheel. ++ Use "" to represent the system's local timezone. Using %Z in the format or tooltip format is useful to track which time zone is currently displayed. *locale*: ++ typeof: string ++ default: inferred from current locale ++ A locale to be used to display the time. Intended to render times in custom timezones with the proper language and format. *today-format*: ++ typeof: string ++ default: {} ++ The format of today's date in the calendar. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. View all valid format options in *strftime(3)*. # FORMAT REPLACEMENTS *{calendar}*: Current month calendar # EXAMPLES ``` "clock": { "interval": 60, "format": "{:%H:%M}", "max-length": 25 } ``` # STYLE - *#clock* waybar-0.9.9/man/waybar-cpu.5.scd000066400000000000000000000054071416677246500165310ustar00rootroot00000000000000waybar-cpu(5) # NAME waybar - cpu module # DESCRIPTION The *cpu* module displays the current cpu utilization. # CONFIGURATION *interval*: ++ typeof: integer ++ default: 10 ++ The interval in which the information gets polled. *format*: ++ typeof: string ++ default: {usage}% ++ The format, how information should be displayed. On {} data gets inserted. *format-icons*: ++ typeof: array/object ++ Based on the current usage, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *states*: ++ typeof: array ++ A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{load}*: Current cpu load. *{usage}*: Current overall cpu usage. *{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}. *{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz. *{max_frequency}*: Current cpu max frequency (based on the core with the highest frequency) in GHz. *{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz. *{icon}*: Icon for overall cpu usage. *{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}. # EXAMPLES Basic configuration: ``` "cpu": { "interval": 10, "format": "{}% ï‹›", "max-length": 10 } ``` Cpu usage per core rendered as icons: ``` "cpu": { "interval": 1, "format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ï‹›", "format-icons": ["â–", "â–‚", "â–ƒ", "â–„", "â–…", "â–†", "â–‡", "â–ˆ"], }, ``` # STYLE - *#cpu* waybar-0.9.9/man/waybar-custom.5.scd000066400000000000000000000122621416677246500172510ustar00rootroot00000000000000waybar-custom(5) # NAME waybar - custom module # DESCRIPTION The *custom* module displays either the output of a script or static text. To display static text, specify only the *format* field. # CONFIGURATION Addressed by *custom/* *exec*: ++ typeof: string ++ The path to the script, which should be executed. *exec-if*: ++ typeof: string ++ The path to a script, which determines if the script in *exec* should be executed. *exec* will be executed if the exit code of *exec-if* equals 0. *exec-on-event*: ++ typeof: bool ++ default: true ++ If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after executing the event command. *return-type*: ++ typeof: string ++ See *return-type* *interval*: ++ typeof: integer ++ The interval (in seconds) in which the information gets polled. Use *once* if you want to execute the module only on startup. You can update it manually with a signal. If no *interval* is defined, it is assumed that the out script loops it self. *restart-interval*: ++ typeof: integer ++ The restart interval (in seconds). Can't be used with the *interval* option, so only with continuous scripts. Once the script exit, it'll be re-executed after the *restart-interval*. *signal*: ++ typeof: integer ++ The signal number used to update the module. The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. *format*: ++ typeof: string ++ default: {} ++ The format, how information should be displayed. On {} data gets inserted. *format-icons*: ++ typeof: array ++ Based on the set percentage, the corresponding icon gets selected. The order is *low* to *high*. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. *escape*: ++ typeof: bool ++ default: false ++ Option to enable escaping of script output. # RETURN-TYPE When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format. This should look like this: ``` {"text": "$text", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage } ``` The *class* parameter also accepts an array of strings. If nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated. This should look like this: ``` $text\\n$tooltip\\n$class* ``` *class* is a CSS class, to apply different styles in *style.css* # FORMAT REPLACEMENTS *{}*: Output of the script. *{percentage}* Percentage which can be set via a json return-type. *{icon}*: An icon from 'format-icons' according to percentage. # EXAMPLES ## Spotify: ``` "custom/spotify": { "format": " {}", "max-length": 40, "interval": 30, // Remove this if your script is endless and write in loop "exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder "exec-if": "pgrep spotify" } ``` ## mpd: ``` "custom/mpd": { "format": "♪ {}", //"max-length": 15, "interval": 10, "exec": "mpc current", "exec-if": "pgrep mpd", "on-click": "mpc toggle", "on-click-right": "sonata" } ``` ## cmus: ``` "custom/cmus": { "format": "♪ {}", //"max-length": 15, "interval": 10, "exec": "cmus-remote -C \"format_print '%a - %t'\"", // artist - title "exec-if": "pgrep cmus", "on-click": "cmus-remote -u", //toggle pause "escape": true //handle markup entities } ``` ## Pacman ``` "custom/pacman": { "format": "{} ", "interval": "once", "exec": "pacman_packages", "on-click": "update-system", "signal": 8 } ``` ## Alternate Pacman ``` "custom/pacman": { "format": "{} ", "interval": 3600, // every hour "exec": "checkupdates | wc -l", // # of updates "exec-if": "exit 0", // always run; consider advanced run conditions "on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system "signal": 8 } ``` You can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*. # STYLE - *#custom-* - *#custom-.* - ** can be set by the script. For more information see *return-type* waybar-0.9.9/man/waybar-disk.5.scd000066400000000000000000000047201416677246500166710ustar00rootroot00000000000000waybar-disk(5) # NAME waybar - disk module # DESCRIPTION The *disk* module displays the current disk space used. # CONFIGURATION Addressed by *disk* *path*: ++ typeof: string ++ default: "/" ++ Any path residing in the filesystem or mountpoint for which the information should be displayed. *interval*: ++ typeof: integer++ default: 30 ++ The interval in which the information gets polled. *format*: ++ typeof: string ++ default: "{percentage_used}%" ++ The format, how information should be displayed. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *states*: ++ typeof: array ++ A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. *tooltip-format*: ++ typeof: string ++ default: "{used} out of {total} used ({percentage_used}%)" ++ The format of the information displayed in the tooltip. # FORMAT REPLACEMENTS *{percentage_used}*: Percentage of disk in use. *{percentage_free}*: Percentage of free disk space *{total}*: Total amount of space on the disk, partition or mountpoint. *{used}*: Amount of used disk space. *{free}*: Amount of available disk space for normal users. *{path}*: The path specified in the configuration. # EXAMPLES ``` "disk": { "interval": 30, "format": "{percentage_free}% free on {path}", } ``` # STYLE - *#disk* waybar-0.9.9/man/waybar-idle-inhibitor.5.scd000066400000000000000000000036011416677246500206360ustar00rootroot00000000000000waybar-idle-inhibitor(5) # NAME waybar - idle_inhibitor module # DESCRIPTION The *idle_inhibitor* module can inhibiting the idle behavior such as screen blanking, locking, and screensaving, also known as "presentation mode". # CONFIGURATION *format*: ++ typeof: string ++ The format, how the state should be displayed. *format-icons*: ++ typeof: array ++ Based on the current state, the corresponding icon gets selected. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. A click also toggles the state *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{status}*: status (*activated* or *deactivated*) *{icon}*: Icon, as defined in *format-icons* # EXAMPLES ``` "idle_inhibitor": { "format": "{icon}", "format-icons": { "activated": "ï®", "deactivated": "ï°" } } ``` waybar-0.9.9/man/waybar-inhibitor.5.scd000066400000000000000000000041551416677246500177300ustar00rootroot00000000000000waybar-inhibitor(5) # NAME waybar - inhibitor module # DESCRIPTION The *inhibitor* module allows to take an inhibitor lock that logind provides. See *systemd-inhibit*(1) for more information. # CONFIGURATION *what*: ++ typeof: string or array ++ The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*. *format*: ++ typeof: string ++ The format, how the state should be displayed. *format-icons*: ++ typeof: array ++ Based on the current state, the corresponding icon gets selected. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. A click also toggles the state *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{status}*: status (*activated* or *deactivated*) *{icon}*: Icon, as defined in *format-icons* # EXAMPLES ``` "inhibitor": { "what": "handle-lid-switch", "format": "{icon}", "format-icons": { "activated": "ï®", "deactivated": "ï°" } } ``` waybar-0.9.9/man/waybar-keyboard-state.5.scd000066400000000000000000000040071416677246500206530ustar00rootroot00000000000000waybar-keyboard-state(5) # NAME waybar - keyboard-state module # DESCRIPTION The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock. # CONFIGURATION *interval*: ++ typeof: integer ++ default: 1 ++ The interval, in seconds, to poll the keyboard state. *format*: ++ typeof: string|object ++ default: {name} {icon} ++ The format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields "numlock", "capslock", and "scrolllock" each specify the format for the corresponding state. Any unspecified states use the default format. *format-icons*: ++ typeof: object ++ default: {"locked": "locked", "unlocked": "unlocked"} ++ Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*. *numlock*: ++ typeof: bool ++ default: false ++ Display the number lock state. *capslock*: ++ typeof: bool ++ default: false ++ Display the caps lock state. *scrolllock*: ++ typeof: bool ++ default: false ++ Display the scroll lock state. *device-path*: ++ typeof: string ++ default: chooses first valid input device ++ Which libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events. # FORMAT REPLACEMENTS *{name}*: Caps, Num, or Scroll. *{icon}*: Icon, as defined in *format-icons*. # ICONS The following *format-icons* can be set. - *locked*: Will be shown when the keyboard state is locked. Default "locked". - *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked" # EXAMPLE: ``` "keyboard-state": { "numlock": true, "capslock": true, "format": "{name} {icon}", "format-icons": { "locked": "", "unlocked": "" } } ``` # STYLE - *#keyboard-state* - *#keyboard-state label* - *#keyboard-state label.locked* waybar-0.9.9/man/waybar-memory.5.scd000066400000000000000000000044601416677246500172500ustar00rootroot00000000000000waybar-memory(5) # NAME waybar - memory module # DESCRIPTION The *memory* module displays the current memory utilization. # CONFIGURATION Addressed by *memory* *interval*: ++ typeof: integer++ default: 30 ++ The interval in which the information gets polled. *format*: ++ typeof: string ++ default: {percentage}% ++ The format, how information should be displayed. *format-icons*: ++ typeof: array/object ++ Based on the current percentage, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *states*: ++ typeof: array ++ A number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{percentage}*: Percentage of memory in use. *{total}*: Amount of total memory available in GiB. *{used}*: Amount of used memory in GiB. *{avail}*: Amount of available memory in GiB. # EXAMPLES ``` "memory": { "interval": 30, "format": "{}% ", "max-length": 10 } ``` ## FORMATTED MEMORY VALUES ``` "memory": { "interval": 30, "format": "{used:0.1f}G/{total:0.1f}G " } ``` # STYLE - *#memory* waybar-0.9.9/man/waybar-mpd.5.scd000066400000000000000000000142361416677246500165220ustar00rootroot00000000000000waybar-mpd(5) # NAME waybar - mpd module # DESCRIPTION The *mpd* module displays information about a running "Music Player Daemon" instance. # CONFIGURATION Addressed by *mpd* *server*: ++ typeof: string ++ The network address or Unix socket path of the MPD server. If empty, connect to the default host. *port*: ++ typeof: integer ++ The port MPD listens to. If empty, use the default port. *password*: ++ typeof: string ++ The password required to connect to the MPD server. If empty, no password is sent to MPD. *interval*: ++ typeof: integer++ default: 5 ++ The interval in which the connection to the MPD server is retried *timeout*: ++ typeof: integer++ default: 30 ++ The timeout for the connection. Change this if your MPD server has a low `connection_timeout` setting *unknown-tag*: ++ typeof: string ++ default: "N/A" ++ The text to display when a tag is not present in the current song, but used in `format` *format*: ++ typeof: string ++ default: "{album} - {artist} - {title}" ++ Information displayed when a song is playing. *format-stopped*: ++ typeof: string ++ default: "stopped" ++ Information displayed when the player is stopped. *format-paused*: ++ typeof: string ++ This format is used when a song is paused. *format-disconnected*: ++ typeof: string ++ default: "disconnected" ++ Information displayed when the MPD server can't be reached. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. *tooltip-format*: ++ typeof: string ++ default: "MPD (connected)" ++ Tooltip information displayed when connected to MPD. *tooltip-format-disconnected*: ++ typeof: string ++ default: "MPD (disconnected)" ++ Tooltip information displayed when the MPD server can't be reached. *artist-len*: ++ typeof: integer ++ Maximum length of the Artist tag. *album-len*: ++ typeof: integer ++ Maximum length of the Album tag. *album-artist-len*: ++ typeof: integer ++ Maximum length of the Album Artist tag. *title-len*: ++ typeof: integer ++ Maximum length of the Title tag. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *state-icons*: ++ typeof: object ++ default: {} ++ Icon to show depending on the play/pause state of the player (*{ "playing": "...", "paused": "..." }*) *consume-icons*: ++ typeof: object ++ default: {} ++ Icon to show depending on the "consume" option (*{ "on": "...", "off": "..." }*) *random-icons*: ++ typeof: object ++ default: {} ++ Icon to show depending on the "random" option (*{ "on": "...", "off": "..." }*) *repeat-icons*: ++ typeof: object ++ default: {} ++ Icon to show depending on the "repeat" option (*{ "on": "...", "off": "..." }*) *single-icons*: ++ typeof: object ++ default: {} ++ Icon to show depending on the "single" option (*{ "on": "...", "off": "..." }*) # FORMAT REPLACEMENTS ## WHEN PLAYING/PAUSED *{artist}*: The artist of the current song *{albumArtist}*: The artist of the current album *{album}*: The album of the current song *{title}*: The title of the current song *{date}*: The date of the current song *{volume}*: The current volume in percent *{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration) *{totalTime}*: The length of the current song. To format as a date/time (see example configuration) *{songPosition}*: The position of the current song. *{queueLength}*: The length of the current queue. *{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option) *{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option) *{randomIcon}*: The icon corresponding the "random" option (see *random-icons* option) *{repeatIcon}*: The icon corresponding the "repeat" option (see *repeat-icons* option) *{singleIcon}*: The icon corresponding the "single" option (see *single-icons* option) ## WHEN STOPPED *{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option) *{randomIcon}*: The icon corresponding the "random" option (see *random-icons* option) *{repeatIcon}*: The icon corresponding the "repeat" option (see *repeat-icons* option) *{singleIcon}*: The icon corresponding the "single" option (see *single-icons* option) ## WHEN DISCONNECTED Currently, no format replacements when disconnected. # EXAMPLES ``` "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ï€", "format-disconnected": "Disconnected ï€", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ï€", "interval": 2, "consume-icons": { "on": " " // Icon shows only when "consume" is on }, "random-icons": { "off": "ï´ ", // Icon grayed out when "random" is off "on": "ï´ " }, "repeat-icons": { "on": " " }, "single-icons": { "on": "1 " }, "state-icons": { "paused": "ïŒ", "playing": "ï‹" }, "tooltip-format": "MPD (connected)", "tooltip-format-disconnected": "MPD (disconnected)" } ``` # STYLE - *#mpd* - *#mpd.disconnected* - *#mpd.stopped* - *#mpd.playing* - *#mpd.paused* waybar-0.9.9/man/waybar-network.5.scd000066400000000000000000000111661416677246500174320ustar00rootroot00000000000000waybar-network(5) # NAME waybar - network module # DESCRIPTION The *network* module displays information about the current network connections. # CONFIGURATION Addressed by *network* *interface*: ++ typeof: string ++ Use the defined interface instead of auto detection. Accepts wildcard. *interval*: ++ typeof: integer ++ default: 60 ++ The interval in which the network information gets polled (e.g. signal strength). *family*: ++ typeof: string ++ default: *ipv4* ++ The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. *format*: ++ typeof: string ++ default: *{ifname}* ++ The format, how information should be displayed. This format is used when other formats aren't specified. *format-ethernet*: ++ typeof: string ++ This format is used when an ethernet interface is displayed. *format-wifi*: ++ typeof: string ++ This format is used when a wireless interface is displayed. *format-linked*: ++ typeof: string ++ This format is used when a linked interface with no ip address is displayed. *format-disconnected*: ++ typeof: string ++ This format is used when the displayed interface is disconnected. *format-disabled*: ++ typeof: string ++ This format is used when the displayed interface is disabled. *format-icons*: ++ typeof: array/object ++ Based on the current signal strength, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: *true* ++ Option to disable tooltip on hover. *tooltip-format*: ++ typeof: string ++ The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified. *tooltip-format-ethernet*: ++ typeof: string ++ This format is used when an ethernet interface is displayed. *tooltip-format-wifi*: ++ typeof: string ++ This format is used when a wireless interface is displayed. *tooltip-format-disconnected*: ++ typeof: string ++ This format is used when the displayed interface is disconnected. *tooltip-format-disabled*: ++ typeof: string ++ This format is used when the displayed interface is disabled. # FORMAT REPLACEMENTS *{ifname}*: Name of the network interface. *{ipaddr}*: The first IP of the interface. *{gwaddr}*: The default gateway for the interface *{netmask}*: The subnetmask corresponding to the IP. *{cidr}*: The subnetmask corresponding to the IP in CIDR notation. *{essid}*: Name (SSID) of the wireless network. *{signalStrength}*: Signal strength of the wireless network. *{signaldBm}*: Signal strength of the wireless network in dBm. *{frequency}*: Frequency of the wireless network in MHz. *{bandwidthUpBits}*: Instant up speed in bits/seconds. *{bandwidthDownBits}*: Instant down speed in bits/seconds. *{bandwidthUpOctets}*: Instant up speed in octets/seconds. *{bandwidthDownOctets}*: Instant down speed in octets/seconds. *{icon}*: Icon, as defined in *format-icons*. # EXAMPLES ``` "network": { "interface": "wlp2s0", "format": "{ifname}", "format-wifi": "{essid} ({signalStrength}%) ", "format-ethernet": "{ifname} ïƒ", "format-disconnected": "", //An empty format will hide the module. "format-disconnected": "ï²", "tooltip-format": "{ifname}", "tooltip-format-wifi": "{essid} ({signalStrength}%) ", "tooltip-format-ethernet": "{ifname} ïƒ", "tooltip-format-disconnected": "Disconnected", "max-length": 50 } ``` # STYLE - *#network* - *#network.disconnected* - *#network.disabled* - *#network.linked* - *#network.ethernet* - *#network.wifi* waybar-0.9.9/man/waybar-pulseaudio.5.scd000066400000000000000000000070221416677246500201070ustar00rootroot00000000000000waybar-pulseaudio(5) # NAME waybar - pulseaudio module # DESCRIPTION The *pulseaudio* module displays the current volume reported by PulseAudio. Additionally you can control the volume by scrolling *up* or *down* while the cursor is over the module. # CONFIGURATION *format*: ++ typeof: string ++ default: {volume}% ++ The format, how information should be displayed. This format is used when other formats aren't specified. *format-bluetooth*: ++ typeof: string ++ This format is used when using bluetooth speakers. *format-muted*: ++ typeof: string ++ This format is used when the sound is muted. *format-source*: ++ typeof: string ++ default: {volume}% ++ This format used for the source. *format-source-muted*: ++ typeof: string ++ This format is used when the source is muted. *format-icons*: ++ typeof: array ++ Based on the current port-name and volume, the corresponding icon gets selected. The order is *low* to *high*. See *Icons*. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *states*: ++ typeof: array ++ A number of volume states which get activated on certain volume levels. See *waybar-states(5)* *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *scroll-step*: ++ typeof: float ++ default: 1.0 ++ The speed in which to change the volume when scrolling. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. This replaces the default behaviour of volume control. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. This replaces the default behaviour of volume control. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. *{volume}*: Volume in percentage. *{icon}*: Icon, as defined in *format-icons*. *{format_source}*: Source format, *format-source*, *format-source-muted*. # ICONS: The following strings for *format-icons* are supported. - the device name If they are found in the current PulseAudio port name, the corresponding icons will be selected. - *default* (Shown, when no other port is found) - *headphone* - *speaker* - *hdmi* - *headset* - *hands-free* - *portable* - *car* - *hifi* - *phone* # EXAMPLES ``` "pulseaudio": { "format": "{volume}% {icon}", "format-bluetooth": "{volume}% {icon}", "format-muted": "", "format-icons": { "alsa_output.pci-0000_00_1f.3.analog-stereo": "", "headphones": "", "handsfree": "ï–", "headset": "ï–", "phone": "ï‚•", "portable": "ï‚•", "car": "", "default": ["", ""] }, "scroll-step": 1, "on-click": "pavucontrol" } ``` # STYLE - *#pulseaudio* - *#pulseaudio.bluetooth* - *#pulseaudio.muted* waybar-0.9.9/man/waybar-river-tags.5.scd000066400000000000000000000015551416677246500200250ustar00rootroot00000000000000waybar-river-tags(5) # NAME waybar - river tags module # DESCRIPTION The *tags* module displays the current state of tags in river. # CONFIGURATION Addressed by *river/tags* *num-tags*: ++ typeof: uint ++ default: 9 ++ The number of tags that should be displayed. Max 32. *tag-labels*: ++ typeof: array ++ The label to display for each tag. *disable-click*: ++ typeof: bool ++ default: false ++ If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled. # EXAMPLE ``` "river/tags": { "num-tags": 5 } ``` # STYLE - *#tags button* - *#tags button.occupied* - *#tags button.focused* - *#tags button.urgent* Note that occupied/focused/urgent status may overlap. That is, a tag may be both occupied and focused at the same time. # SEE ALSO waybar(5), river(1) waybar-0.9.9/man/waybar-sndio.5.scd000066400000000000000000000037571416677246500170640ustar00rootroot00000000000000waybar-sndio(5) # NAME waybar - sndio module # DESCRIPTION The *sndio* module displays the current volume reported by sndio(7). Additionally, you can control the volume by scrolling *up* or *down* while the cursor is over the module, and clicking on the module toggles mute. # CONFIGURATION *format*: ++ typeof: string ++ default: {volume}% ++ The format for how information should be displayed. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *scroll-step*: ++ typeof: int ++ default: 5 ++ The speed in which to change the volume when scrolling. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. This replaces the default behaviour of toggling mute. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. This replaces the default behaviour of volume control. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. This replaces the default behaviour of volume control. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. # FORMAT REPLACEMENTS *{volume}*: Volume in percentage. *{raw_value}*: Volume as value reported by sndio. # EXAMPLES ``` "sndio": { "format": "{raw_value} 🎜", "scroll-step": 3 } ``` # STYLE - *#sndio* - *#sndio.muted* waybar-0.9.9/man/waybar-states.5.scd000066400000000000000000000021301416677246500172330ustar00rootroot00000000000000waybar-states(5) # OVERVIEW Some modules support 'states' which allows percentage values to be used as styling triggers to apply a class when the value matches the declared state value. # STATES - Every entry (*state*) consists of a ** (typeof: *string*) and a ** (typeof: *integer*). - The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the ** of the state. Each class gets activated when the current capacity is equal or below the configured **. - Also each state can have its own *format*. Those can be configured via *format-*. Or if you want to differentiate a bit more even as *format--*. # EXAMPLE ``` "battery": { "bat": "BAT2", "interval": 60, "states": { "warning": 30, "critical": 15 }, "format": "{capacity}% {icon}", "format-icons": ["", "", "", "ï‰", ""], "max-length": 25 } ``` # STYLING STATES - *#battery.* - ** can be defined in the *config*. # EXAMPLE: - *#battery.warning: { background: orange; }* - *#battery.critical: { background: red; }* waybar-0.9.9/man/waybar-sway-language.5.scd000066400000000000000000000016301416677246500205000ustar00rootroot00000000000000waybar-sway-language(5) # NAME waybar - sway language module # DESCRIPTION The *language* module displays the current keyboard layout in Sway # CONFIGURATION Addressed by *sway/language* *format*: ++ typeof: string ++ default: {} ++ The format, how layout should be displayed. *tooltip-format*: ++ typeof: string ++ default: {} ++ The format, how layout should be displayed in tooltip. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{short}*: Short name of layout (e.g. "us"). Equals to {}. *{shortDescription}*: Short description of layout (e.g. "en"). *{long}*: Long name of layout (e.g. "English (Dvorak)"). *{variant}*: Variant of layout (e.g. "dvorak"). *{flag}*: Country flag of layout. # EXAMPLES ``` "sway/language": { "format": "{}", }, "sway/language": { "format": "{short} {variant}", } ``` # STYLE - *#language* waybar-0.9.9/man/waybar-sway-mode.5.scd000066400000000000000000000031061416677246500176410ustar00rootroot00000000000000waybar-sway-mode(5) # NAME waybar - sway mode module # DESCRIPTION The *mode* module displays the current binding mode of Sway # CONFIGURATION Addressed by *sway/mode* *format*: ++ typeof: string ++ default: {} ++ The format, how information should be displayed. On {} data gets inserted. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # EXAMPLES ``` "sway/window": { "format": "ï—¼ {}", "max-length": 50 } ``` # STYLE - *#mode* waybar-0.9.9/man/waybar-sway-window.5.scd000066400000000000000000000046021416677246500202260ustar00rootroot00000000000000waybar-sway-window(5) # NAME waybar - sway window module # DESCRIPTION The *window* module displays the title of the currently focused window in Sway # CONFIGURATION Addressed by *sway/window* *format*: ++ typeof: string ++ default: {} ++ The format, how information should be displayed. On {} data gets inserted. *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. *rewrite*: ++ typeof: object ++ Rules to rewrite window title. See *rewrite rules*. # REWRITE RULES *rewrite* is an object where keys are regular expressions and values are rewrite rules if the expression matches. Rules may contain references to captures of the expression. Regular expression and replacement follow ECMA-script rules. If no expression matches, the title is left unchanged. Invalid expressions (e.g., mismatched parentheses) are skipped. # EXAMPLES ``` "sway/window": { "format": "{}", "max-length": 50, "rewrite": { "(.*) - Mozilla Firefox": "🌎 $1", "(.*) - zsh": "> [$1]" } } ``` # STYLE - *#window* - *window#waybar.empty* When no windows is in the workspace - *window#waybar.solo* When one window is in the workspace - *window#waybar.* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace waybar-0.9.9/man/waybar-sway-workspaces.5.scd000066400000000000000000000101261416677246500210760ustar00rootroot00000000000000waybar-sway-workspaces(5) # NAME waybar - sway workspaces module # DESCRIPTION The *workspaces* module displays the currently used workspaces in Sway. # CONFIGURATION Addressed by *sway/workspaces* *all-outputs*: ++ typeof: bool ++ default: false ++ If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output. *format*: ++ typeof: string ++ default: {value} ++ The format, how information should be displayed. *format-icons*: ++ typeof: array ++ Based on the workspace name and state, the corresponding icon gets selected. See *icons*. *disable-scroll*: ++ typeof: bool ++ default: false ++ If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled. *disable-click*: ++ typeof: bool ++ default: false ++ If set to false, you can click to change workspace. If set to true this behaviour is disabled. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *disable-scroll-wraparound*: ++ typeof: bool ++ default: false ++ If set to false, scrolling on the workspace indicator will wrap around to the first workspace when reading the end, and vice versa. If set to true this behavior is disabled. *enable-bar-scroll*: ++ typeof: bool ++ default: false ++ If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled. *disable-markup*: ++ typeof: bool ++ default: false ++ If set to true, button label will escape pango markup. *current-only*: ++ typeof: bool ++ default: false ++ If set to true. Only focused workspaces will be shown. *persistent_workspaces*: ++ typeof: json (see below) ++ default: empty ++ Lists workspaces that should always be shown, even when non existent *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *disable-auto-back-and-forth*: ++ typeof: bool ++ Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. # FORMAT REPLACEMENTS *{value}*: Name of the workspace, as defined by sway. *{name}*: Number stripped from workspace value. *{icon}*: Icon, as defined in *format-icons*. *{index}*: Index of the workspace. # ICONS Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string matches is found. - *urgent*: Will be shown, when workspace is flagged as urgent - *focused*: Will be shown, when workspace is focused - *persistent*: Will be shown, when workspace is persistent one. # PERSISTENT WORKSPACES Each entry of *persistent_workspace* names a workspace that should always be shown. Associated with that value is a list of outputs indicating *where* the workspace should be shown, an empty list denoting all outputs. ``` "sway/workspaces": { "persistent_workspaces": { "3": [], // Always show a workspace with name '3', on all outputs if it does not exists "4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exists "5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exists } } ``` n.b.: the list of outputs can be obtained from command line using *swaymsg -t get_outputs* # EXAMPLES ``` "sway/workspaces": { "disable-scroll": true, "all-outputs": true, "format": "{name}: {icon}", "format-icons": { "1": "ï„ ", "2": "", "3": "ï„¡", "4": "", "5": "ïµ", "urgent": "ïª", "focused": "", "default": "ï„‘" } } ``` # Style - *#workspaces button* - *#workspaces button.visible* - *#workspaces button.focused* - *#workspaces button.urgent* - *#workspaces button.persistent* - *#workspaces button.current_output* - *#workspaces button#sway-workspace-${name}* waybar-0.9.9/man/waybar-temperature.5.scd000066400000000000000000000062331416677246500202750ustar00rootroot00000000000000waybar-temperature(5) # NAME waybar - temperature module # DESCRIPTION The *temperature* module displays the current temperature from a thermal zone. # CONFIGURATION Addressed by *temperature* *thermal-zone*: ++ typeof: integer ++ The thermal zone, as in */sys/class/thermal/*. *hwmon-path*: ++ typeof: string ++ The temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*. *hwmon-path-abs*: ++ typeof: string ++ The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*. *input-filename*: ++ typeof: string ++ The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input* *critical-threshold*: ++ typeof: integer ++ The threshold before it is considered critical (Celsius). *interval*: ++ typeof: integer ++ default: 10 ++ The interval in which the information gets polled. *format-critical*: ++ typeof: string ++ The format to use when temperature is considered critical *format*: ++ typeof: string ++ default: {temperatureC}°C ++ The format (Celsius/Fahrenheit/Kelvin) in which the temperature should be displayed. *format-icons*: ++ typeof: array ++ Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*. *tooltip-format*: ++ typeof: string ++ default: {temperatureC}°C ++ The format for the tooltip *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. *max-length*: ++ typeof: integer ++ The maximum length in characters the module should display. *min-length*: ++ typeof: integer ++ The minimum length in characters the module should take up. *align*: ++ typeof: float ++ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text. *on-click*: ++ typeof: string ++ Command to execute when you clicked on the module. *on-click-middle*: ++ typeof: string ++ Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *tooltip*: ++ typeof: bool ++ default: true ++ Option to disable tooltip on hover. # FORMAT REPLACEMENTS *{temperatureC}*: Temperature in Celsius. *{temperatureF}*: Temperature in Fahrenheit. *{temperatureK}*: Temperature in Kelvin. # EXAMPLES ``` "temperature": { // "thermal-zone": 2, // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", // "critical-threshold": 80, // "format-critical": "{temperatureC}°C ", "format": "{temperatureC}°C " } ``` # STYLE - *#temperature* - *#temperature.critical* waybar-0.9.9/man/waybar-tray.5.scd000066400000000000000000000014611416677246500167150ustar00rootroot00000000000000waybar-tray(5) # NAME waybar - tray module # DESCRIPTION _WARNING_ *tray* is still in beta. There may me bugs. Breaking changes may occur. # CONFIGURATION Addressed by *tray* *icon-size*: ++ typeof: integer ++ Defines the size of the tray icons. *show-passive-items*: ++ typeof: bool ++ default: false ++ Defines visibility of the tray icons with *Passive* status. *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. *spacing*: ++ typeof: integer ++ Defines the spacing between the tray icons. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. # EXAMPLES ``` "tray": { "icon-size": 21, "spacing": 10 } ``` # STYLE - *#tray* - *#tray > .passive* - *#tray > .active* - *#tray > .needs-attention* waybar-0.9.9/man/waybar-wlr-taskbar.5.scd000066400000000000000000000064361416677246500201760ustar00rootroot00000000000000waybar-wlr-taskbar(5) # NAME wlroots - Taskbar module # DESCRIPTION The *taskbar* module displays the currently open applications. This module requires a compositor that implements the foreign-toplevel-manager interface. # CONFIGURATION Addressed by *wlr/taskbar* *all-outputs*: ++ typeof: bool ++ default: false ++ If set to false applications on the waybar's current output will be shown. Otherwise all applications are shown. *format*: ++ typeof: string ++ default: {icon} ++ The format, how information should be displayed. *icon-theme*: ++ typeof: array|string ++ The names of the icon-themes that should be used to find an icon. The list will be traversed from left to right. If omitted, the system default will be used. *icon-size*: ++ typeof: integer ++ default: 16 ++ The size of the icon. *markup*: ++ typeof: bool ++ default: false ++ If set to true, pango markup will be accepted in format and tooltip-format. *tooltip*: ++ typeof: bool ++ default: true ++ If set to false no tooltip will be shown. *tooltip-format*: ++ typeof: string ++ default: {title} ++ The format, how information in the tooltip should be displayed. *active-first*: ++ typeof: bool ++ default: false ++ If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder. *on-click*: ++ typeof: string ++ The action which should be triggered when clicking on the application button with the left mouse button. *on-click-middle*: ++ typeof: string ++ The action which should be triggered when clicking on the application button with the middle mouse button. *on-click-right*: ++ typeof: string ++ The action which should be triggered when clicking on the application button with the right mouse button. *on-update*: ++ typeof: string ++ Command to execute when the module is updated. *ignore-list*: ++ typeof: array ++ List of app_id/titles to be invisible. *app_ids-mapping*: ++ typeof: object ++ Dictionary of app_id to be replaced with # FORMAT REPLACEMENTS *{icon}*: The icon of the application. *{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id} *{title}*: The title of the application. *{app_id}*: The app_id (== application name) of the application. *{state}*: The state (minimized, maximized, active, fullscreen) of the application. *{short_state}*: The state (minimize == m, maximized == M, active == A, fullscreen == F) represented as one character of the application. # CLICK ACTIONS *activate*: Bring the application into foreground. *minimize*: Toggle application's minimized state. *minimize-raise*: Bring the application into foreground or toggle its minimized state. *maximize*: Toggle application's maximized state. *fullscreen*: Toggle application's fullscreen state. *close*: Close the application. # EXAMPLES ``` "wlr/taskbar": { "format": "{icon}", "icon-size": 14, "icon-theme": "Numix-Circle", "tooltip-format": "{title}", "on-click": "activate", "on-click-middle": "close", "ignore-list": [ "Alacritty" ], "app_ids-mapping": { "firefoxdeveloperedition": "firefox-developer-edition" } } ``` # Style - *#taskbar* - *#taskbar button* - *#taskbar button.maximized* - *#taskbar button.minimized* - *#taskbar button.active* - *#taskbar button.fullscreen* waybar-0.9.9/man/waybar-wlr-workspaces.5.scd000066400000000000000000000034521416677246500207230ustar00rootroot00000000000000waybar-wlr-workspaces(5) # NAME waybar - wlr workspaces module # DESCRIPTION The *workspaces* module displays the currently used workspaces in wayland compositor. # CONFIGURATION Addressed by *wlr/workspaces* *format*: ++ typeof: string ++ default: {name} ++ The format, how information should be displayed. *format-icons*: ++ typeof: array ++ Based on the workspace name and state, the corresponding icon gets selected. See *icons*. *sort-by-name*: ++ typeof: bool ++ default: true ++ Should workspaces be sorted by name. *sort-by-coordinates*: ++ typeof: bool ++ default: true ++ Should workspaces be sorted by coordinates. Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first. If both are false - sort by id will be performed. *all-outputs*: ++ typeof: bool ++ default: false ++ If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown. *active-only*: ++ typeof: bool ++ default: false ++ If set to true only active or urgent workspaces will be shown. # FORMAT REPLACEMENTS *{name}*: Name of workspace assigned by compositor *{icon}*: Icon, as defined in *format-icons*. # CLICK ACTIONS *activate*: Switch to workspace. *close*: Close the workspace. # ICONS Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string match is found. - *focused*: Will be shown, when workspace is focused # EXAMPLES ``` "wlr/workspaces": { "format": "{name}: {icon}", "format-icons": { "1": "ï„ ", "2": "", "3": "ï„¡", "4": "", "5": "ïµ", "focused": "", "default": "ï„‘" } } ``` # Style - *#workspaces* - *#workspaces button* - *#workspaces button.active* - *#workspaces button.urgent* - *#workspaces button.hidden* waybar-0.9.9/man/waybar.5.scd.in000066400000000000000000000157711416677246500163560ustar00rootroot00000000000000waybar(5) # NAME waybar - configuration file # DESCRIPTION The configuration uses the JSON file format and is named *config*. Valid locations for this file are: - *$XDG_CONFIG_HOME/waybar/config* - *~/.config/waybar/config* - *~/waybar/config* - */etc/xdg/waybar/config* - *@sysconfdir@/xdg/waybar/config* A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config Also a minimal example configuration can be found on the at the bottom of this man page. # BAR CONFIGURATION *layer* ++ typeof: string ++ default: bottom ++ Decide if the bar is displayed in front (*top*) of the windows or behind (*bottom*) them. *output* ++ typeof: string|array ++ Specifies on which screen this bar will be displayed. Exclamation mark(*!*) can be used to exclude specific output. *position* ++ typeof: string ++ default: top ++ Bar position, can be *top*, *bottom*, *left*, *right*. *height* ++ typeof: integer ++ Height to be used by the bar if possible. Leave blank for a dynamic value. *width* ++ typeof: integer ++ Width to be used by the bar if possible. Leave blank for a dynamic value. *modules-left* ++ typeof: array ++ Modules that will be displayed on the left. *modules-center* ++ typeof: array ++ Modules that will be displayed in the center. *modules-right* ++ typeof: array Modules that will be displayed on the right. *margin* ++ typeof: string ++ Margins value using the CSS format without units. *margin-* ++ typeof: integer ++ Margins value without units. *spacing* ++ typeof: integer ++ Size of gaps in between of the different modules. *name* ++ typeof: string ++ Optional name added as a CSS class, for styling multiple waybars. *mode* ++ typeof: string ++ Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++ Note: *hide* and *invisible* modes may be not as useful without Sway IPC. *exclusive* ++ typeof: bool ++ default: *true* ++ Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. *fixed-center* ++ typeof: bool ++ default: *true* Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space. When false, the center block is centered in the space between the left and right block. *passthrough* ++ typeof: bool ++ default: *false* ++ Option to pass any pointer events to the window under the bar. Intended to be used with either *top* or *overlay* layers and without exclusive zone. *gtk-layer-shell* ++ typeof: bool ++ default: true ++ Option to disable the use of gtk-layer-shell for popups. Only functional if compiled with gtk-layer-shell support. *ipc* ++ typeof: bool ++ default: false ++ Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++ Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option. *id* ++ typeof: string ++ *bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance. *include* ++ typeof: string|array ++ Paths to additional configuration files. Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports. For a multi-bar config, the include directive affects only current bar configuration object. # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). e.g. ``` "format": "{}" ``` # MULTIPLE INSTANCES OF A MODULE If you want to have a second instance of a module, you can suffix it by a '#' and a custom name. For example if you want a second battery module, you can add *"battery#bat2"* to your modules. To configure the newly added module, you then also add a module configuration with the same name. This could then look something like this *(this is an incomplete example)*: ``` "modules-right": ["battery", "battery#bat2"], "battery": { "bat": "BAT1" }, "battery#bat2": { "bat": "BAT2" } ``` # MINIMAL CONFIGURATION A minimal *config* file could look like this: ``` { "layer": "top", "modules-left": ["sway/workspaces", "sway/mode"], "modules-center": ["sway/window"], "modules-right": ["battery", "clock"], "sway/window": { "max-length": 50 }, "battery": { "format": "{capacity}% {icon}", "format-icons": ["", "", "", "ï‰", ""] }, "clock": { "format-alt": "{:%a, %d. %b %H:%M}" } } ``` # MULTI OUTPUT CONFIGURATION ## Limit a configuration to some outputs ``` { "layer": "top", "output": "eDP-1", "modules-left": ["sway/workspaces", "sway/mode"], ... } ``` ``` { "layer": "top", "output": ["eDP-1", "VGA"], "modules-left": ["sway/workspaces", "sway/mode"], ... } ``` ## Configuration of multiple outputs Don't specify an output to create multiple bars on the same screen. ``` [{ "layer": "top", "output": "eDP-1", "modules-left": ["sway/workspaces", "sway/mode"], ... }, { "layer": "top", "output": "VGA", "modules-right": ["clock"], ... }] ``` ## Rotating modules When positioning Waybar on the left or right side of the screen, sometimes it's useful to be able to rotate the contents of a module so the text runs vertically. This can be done using the "rotate" property of the module. Example: ``` { "clock": { "rotate": 90 } } ``` Valid options for the "rotate" property are: 0, 90, 180 and 270. ## Grouping modules Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example: ``` { "modules-right": ["group/hardware", "clock"], "group/hardware": { "modules": [ "cpu", "memory", "battery" ] }, ... } ``` # SUPPORTED MODULES - *waybar-backlight(5)* - *waybar-battery(5)* - *waybar-bluetooth(5)* - *waybar-clock(5)* - *waybar-cpu(5)* - *waybar-custom(5)* - *waybar-disk(5)* - *waybar-idle-inhibitor(5)* - *waybar-keyboard-state(5)* - *waybar-memory(5)* - *waybar-mpd(5)* - *waybar-network(5)* - *waybar-pulseaudio(5)* - *waybar-river-tags(5)* - *waybar-states(5)* - *waybar-sway-mode(5)* - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* - *waybar-wlr-taskbar(5)* - *waybar-wlr-workspaces(5)* - *waybar-temperature(5)* - *waybar-tray(5)* waybar-0.9.9/meson.build000066400000000000000000000277751416677246500152240ustar00rootroot00000000000000project( 'waybar', 'cpp', 'c', version: '0.9.9', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ 'cpp_std=c++17', 'buildtype=release', 'default_library=static' ], ) compiler = meson.get_compiler('cpp') cpp_args = [] cpp_link_args = [] if get_option('libcxx') cpp_args += ['-stdlib=libc++'] cpp_link_args += ['-stdlib=libc++', '-lc++abi'] endif if compiler.has_link_argument('-lc++fs') cpp_link_args += ['-lc++fs'] elif compiler.has_link_argument('-lc++experimental') cpp_link_args += ['-lc++experimental'] elif compiler.has_link_argument('-lstdc++fs') cpp_link_args += ['-lstdc++fs'] endif git = find_program('git', native: true, required: false) if not git.found() add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp') else git_path = run_command([git.path(), 'rev-parse', '--show-toplevel']).stdout().strip() if meson.source_root() == git_path git_commit_hash = run_command([git.path(), 'describe', '--always', '--tags']).stdout().strip() git_branch = run_command([git.path(), 'rev-parse', '--abbrev-ref', 'HEAD']).stdout().strip() version = '"@0@ (branch \'@1@\')"'.format(git_commit_hash, git_branch) add_project_arguments('-DVERSION=@0@'.format(version), language: 'cpp') else add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp') endif endif if not compiler.has_header('filesystem') if compiler.has_header('experimental/filesystem') add_project_arguments('-DFILESYSTEM_EXPERIMENTAL', language: 'cpp') else add_project_arguments('-DNO_FILESYSTEM', language: 'cpp') warning('No filesystem header found, some modules may not work') endif endif code = ''' #include #include int main(int argc, char** argv) { locale_t locale = newlocale(LC_ALL, "en_US.UTF-8", nullptr); char* str; str = nl_langinfo_l(_NL_TIME_WEEK_1STDAY, locale); str = nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, locale); freelocale(locale); return 0; } ''' if compiler.links(code, name : 'nl_langinfo with _NL_TIME_WEEK_1STDAY, _NL_TIME_FIRST_WEEKDAY') add_project_arguments('-DHAVE_LANGINFO_1STDAY', language: 'cpp') endif add_global_arguments(cpp_args, language : 'cpp') add_global_link_arguments(cpp_link_args, language : 'cpp') is_linux = host_machine.system() == 'linux' is_dragonfly = host_machine.system() == 'dragonfly' is_freebsd = host_machine.system() == 'freebsd' is_netbsd = host_machine.system() == 'netbsd' is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep']) spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libevdev = dependency('libevdev', required: get_option('libevdev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) xkbregistry = dependency('xkbregistry') libsndio = compiler.find_library('sndio', required: get_option('sndio')) if libsndio.found() if not compiler.has_function('sioctl_open', prefix: '#include ', dependencies: libsndio) if get_option('sndio').enabled() error('libsndio is too old, required >=1.7.0') else warning('libsndio is too old, required >=1.7.0') libsndio = dependency('', required: false) endif endif endif gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) tz_dep = dependency('date', required: false, default_options : [ 'use_system_tzdb=true' ], modules : [ 'date::date', 'date::date-tz' ], fallback: [ 'date', 'tz_dep' ]) prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') conf_data = configuration_data() conf_data.set('prefix', prefix) add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir)), language : 'cpp') if systemd.found() user_units_dir = systemd.get_pkgconfig_variable('systemduserunitdir') configure_file( configuration: conf_data, input: './resources/waybar.service.in', output: '@BASENAME@', install_dir: user_units_dir ) endif src_files = files( 'src/factory.cpp', 'src/AModule.cpp', 'src/ALabel.cpp', 'src/modules/custom.cpp', 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', 'src/modules/temperature.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp', 'src/config.cpp', 'src/group.cpp', 'src/util/ustring_clen.cpp' ) if is_linux add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp') add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp') src_files += files( 'src/modules/battery.cpp', 'src/modules/cpu/common.cpp', 'src/modules/cpu/linux.cpp', 'src/modules/memory/common.cpp', 'src/modules/memory/linux.cpp', ) elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp') add_project_arguments('-DHAVE_MEMORY_BSD', language: 'cpp') src_files += files( 'src/modules/cpu/bsd.cpp', 'src/modules/cpu/common.cpp', 'src/modules/memory/bsd.cpp', 'src/modules/memory/common.cpp', ) endif add_project_arguments('-DHAVE_SWAY', language: 'cpp') src_files += [ 'src/modules/sway/ipc/client.cpp', 'src/modules/sway/bar.cpp', 'src/modules/sway/mode.cpp', 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', 'src/modules/sway/workspaces.cpp' ] if true add_project_arguments('-DHAVE_WLR', language: 'cpp') src_files += 'src/modules/wlr/taskbar.cpp' src_files += 'src/modules/wlr/workspace_manager.cpp' src_files += 'src/modules/wlr/workspace_manager_binding.cpp' endif if true add_project_arguments('-DHAVE_RIVER', language: 'cpp') src_files += 'src/modules/river/tags.cpp' endif if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += 'src/modules/network.cpp' endif if libpulse.found() add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') src_files += 'src/modules/pulseaudio.cpp' endif if dbusmenu_gtk.found() add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp') src_files += files( 'src/modules/sni/tray.cpp', 'src/modules/sni/watcher.cpp', 'src/modules/sni/host.cpp', 'src/modules/sni/item.cpp' ) endif if libudev.found() and (is_linux or libepoll.found()) add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp') src_files += 'src/modules/backlight.cpp' endif if libevdev.found() and (is_linux or libepoll.found()) add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') src_files += 'src/modules/keyboard_state.cpp' endif if libmpdclient.found() add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') src_files += 'src/modules/mpd/mpd.cpp' src_files += 'src/modules/mpd/state.cpp' endif if gtk_layer_shell.found() add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') endif if libsndio.found() add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp') src_files += 'src/modules/sndio.cpp' endif if (giounix.found() and not get_option('logind').disabled()) add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') src_files += 'src/modules/inhibitor.cpp' endif if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') src_files += files( 'src/modules/bluetooth.cpp', 'src/util/rfkill.cpp' ) endif endif if tz_dep.found() add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') src_files += 'src/modules/clock.cpp' else src_files += 'src/modules/simpleclock.cpp' endif if get_option('experimental') add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp') endif subdir('protocol') executable( 'waybar', src_files, dependencies: [ thread_dep, client_protos, wayland_client, fmt, spdlog, sigcpp, jsoncpp, wayland_cursor, gtkmm, dbusmenu_gtk, giounix, libnl, libnlgen, libpulse, libudev, libepoll, libmpdclient, libevdev, gtk_layer_shell, libsndio, tz_dep, xkbregistry ], include_directories: [include_directories('include')], install: true, ) install_data( './resources/config', './resources/style.css', install_dir: sysconfdir + '/xdg/waybar' ) scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) sh = find_program('sh', native: true) main_manpage = configure_file( input: 'man/waybar.5.scd.in', output: 'waybar.5.scd', configuration: { 'sysconfdir': join_paths(prefix, sysconfdir) } ) main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage)) mandir = get_option('mandir') man_files = [ main_manpage_path, 'waybar-backlight.5.scd', 'waybar-battery.5.scd', 'waybar-clock.5.scd', 'waybar-cpu.5.scd', 'waybar-custom.5.scd', 'waybar-disk.5.scd', 'waybar-idle-inhibitor.5.scd', 'waybar-keyboard-state.5.scd', 'waybar-memory.5.scd', 'waybar-mpd.5.scd', 'waybar-network.5.scd', 'waybar-pulseaudio.5.scd', 'waybar-river-tags.5.scd', 'waybar-sway-language.5.scd', 'waybar-sway-mode.5.scd', 'waybar-sway-window.5.scd', 'waybar-sway-workspaces.5.scd', 'waybar-temperature.5.scd', 'waybar-tray.5.scd', 'waybar-states.5.scd', 'waybar-wlr-taskbar.5.scd', 'waybar-wlr-workspaces.5.scd', 'waybar-bluetooth.5.scd', 'waybar-sndio.5.scd', ] if (giounix.found() and not get_option('logind').disabled()) man_files += 'waybar-inhibitor.5.scd' endif foreach file : man_files path = '@0@'.format(file) basename = path.split('/')[-1] topic = basename.split('.')[-3] section = basename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, # drops the 'man' if `path` is an absolute path input: join_paths('man', path), output: output, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif catch2 = dependency( 'catch2', fallback: ['catch2', 'catch2_dep'], required: get_option('tests'), ) if catch2.found() subdir('test') endif clangtidy = find_program('clang-tidy', required: false) if clangtidy.found() run_target( 'tidy', command: [ clangtidy, '-checks=*,-fuchsia-default-arguments', '-p', meson.build_root() ] + src_files) endif waybar-0.9.9/meson_options.txt000066400000000000000000000030041416677246500164720ustar00rootroot00000000000000option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') waybar-0.9.9/package/000077500000000000000000000000001416677246500144335ustar00rootroot00000000000000waybar-0.9.9/package/archlinux/000077500000000000000000000000001416677246500164305ustar00rootroot00000000000000waybar-0.9.9/preview-2.png000066400000000000000000000124731416677246500153750ustar00rootroot00000000000000‰PNG  IHDR€ú·óI\PLTE$<#:%=$: %<%< &=fÌ™)€¹$;dr}BL\ÿÿÿ#;$:ñÄ›Y¶.Ìq%;$="9AK\ &>#<!:!=$<";BL]@K]#<8;9AK[9!'> ;>GZ*\E/fLòöùbÅ“îèò<~^U­_¿ùúü7‰¾dÉ–t@äï÷DŒi3nSI—q  /ƒ»O¢yÔæñÅÈÍYµˆäáèoªÐBÂ___~³Öppp…Œ“ 3#69FµÓçkWšÃÞc£Í-bIº¿Ã*¾iÝ´ ϰܙŸ©Œ•Õ×Ù<1ÄÜì½ÛϨ +_G¿•Ño{†§ª®ž€ */CXG¥Êâ'®`­°¸%=ˆK§m¿…lÛÃåV0x„M•Å[É»˜ PTa²ÇTšÇ¡cº#œW]cxŽ³Ì¢¢¢ òКIDATxÚíÝ [ÚÈ×p@DñõÆÒÕv»¯¥Y €rGÀª‚*ÞÕZmW[«ZÛ>ûýŸç™†If‚ͱJÈ´òãœ9‰–W:bD1šVû³õ9C½,ÏÚý$1J ÷è¬eTC´ìÏÉVç sÐí¶‚ c‚š9Cßö¤Ýu)&›Ã)=CÂ9éæÞ±íï{X)ñBO -ÉMÚó@÷‚x<ë|­¿Úh~´d¬­Ž0ü¤ñ®m„´—qÝñpµ7bD{ í,¸5žÀ8hFÛʶÑW³mlÎ„Ì €×Z㬃ƒÖά•Í£Øh€Ýv뀽ïîèM€q¥ßO``à^Øm«UÜØùç ³ö«æüv`; `ý°S-ï•Üájœ{ÐiÄQâ7X¦°q`@­}p'ޚЫ÷¤ãõÀD‹M€€_N3†ò>)ö5À8ÆlVÎH1jdüùÏ+ÚñˆÅgÎX<  TF÷1Õ<L8 KÈ,VY ,MÙ ’urn§]6ÃŽ…ŠÝ=Aš²¾ahû¤`´Óïutf·œ³êʈ•~щhã­ºA[ŸÀžúžmDON~Ÿ à6l&–ð{ àŽô¯w±º¯f©ÛçsOÀ3z:­´FKFJêIžÄ[•n™Eà6NƒêÀƒº¦Ëp È=$pNvê6À-§‰a¿ÏÄ÷¸Ý9DzÝu8ó‹·¼C~g‘ixww÷ˆyY¢û{›ˆ /–îÐ$º‰•D’7F_òOÑ£;€?`·‚ø4±}%ÇvÞ+Ä<àï¿ ×ðóãucA1Ôˆï€Î„‡}¾ÏŠmüMôñ´^'Þóöa¬(ürzð›é×§ÓÓS#€Oáîø}À¶_+j×ýõûëÊR©@JÆg.PåæêËRÕ0V°Žøƒ-$À©®-îG`”Üv`6I¡o+Z0%‹L0%#Ö°!]Ñ]XWtàÞçðýzæMìdÇvÊ_ÊäçCïùع÷˹÷¢`kÓyÀ|>Ÿç—špÍ/ÑNÜ /ñk‰#ÀuÞÄòÇù[!-ÎoŠG]®ÄÚññeµ£¼Y˜¸òf2™ùíÌüê§ ÑàùÕÀ«ó€wù¯ü.`ÜŽÿ톮ÇEƒ£’–›ÏÆÞϼ/¿ /gÞ»\¡•WQØúÁ+Ìà•-U…ÏøÐ~£N|0²wB^/–ÔQX@fÁ¤1àÓý…"±]+>¨œó's…H!— ø"˜`d: ™šƒ/átcY0•¦øÆSI‚°á´*À軀hÅ«Í} °>JÐk¯ †]šîkØæ€aÀ-ËÙiS9zjÏ`ôC0~^®_¸^”ÇÎ×Ǿ|;¾???ô€w€ÂÞBÜ0à²Õ ðÒÚ2Æß=ò1p³Yº>^Êæ¡À¥kËàÏË®•ýhEÈ‚½å÷RT¼Ë€‹[ Ù=!3ÝêÄbšÚ× ð›iÏ-ž’›°ÎŠÑ3"Àg{Ój‡ý¹t2 Žçê)0DAYHUSsCÉj5ITÃeÉp$°N"©\*F§åùnü&ÐsÕxíe(†;Ф’U?ØOcãdµ ,iøÍŒ.,³Tùµ)A+AÏ6Ò*³¿vw`ö`˜íñ™×cRÃõ˺|YîÎLùÉ1­oT z|^þr8v1¶~F±€ÕùEùC¦|ïýÒ °Å‚|ëõä£hŠó ¯]ZLlð·GÑ(F 7‹‰ã\JDײp«£¥á¬ Œö&J·Pë|én T®ÑÀ/Ü=†{În.—@ò{›?¢|•øÎßÀ韫7Wóbº»}Õøj[à!ð%3Ü]ð÷¯àÝÝqmó^Ù–ÏÀÔ2xáÁµüßÊŠ°ÈûþlYP£^ñz©%è-pÁFhWàDqÞÛ+ŽbjïóT#@ÕYú¬ìËç5—»å%èÆ:!_e¡‚íG¯êO%sÀà@*\‘ žÌ åªCñ$â‡Óq çdćÓ2>ç€Ó)ùÈp..GæR`OÉx*Ÿ ÁÚ… Ø´qA\‚eÀf™’øLýR2^­ôÚl›Åfw:ìø"‹‡°]¹ðŒ¦R(SoÇmWV¹º5ñ]D§žì €™žŠ®Â¼ðgþcQþÇ/ˆÅó`ZžÌ”QÓžÞõN\€ØðNx” ðNù<¶~xñáP>÷®_x?x×)—¢ä7KùËálâ8[âcÙÄF¬]ZÉk,±‘½ãï²ÂÍ¢ë8¶é:Š-B“‡³Q€ïÆÑæFq ³àËüÚm݃:#ˆ‡³â ðÚâpv˜ðvæ'øô[ƒoVo>­ÖG€3 €3óÍ !æáhoàñÝâ×ë¢Ú0ñR”Ë•Ï˼0¸÷þ ­¼,>’ÿÀàð[6€O] |t«Q–æÑp°ÀpÀ’ /gö>“+šÜÂYÓ}ÙIHàË,n¬²W,C|‘kÞ‘àp:Næ‚9¿?)ÖãñHÁ7JÇÕp2™J†…›t@ðw.—ƒs'(öâÂP ˆ SÂÚa˜076—HÙ+=Ná.><¥d{§XBCÝëÜša—íµšt :N«ÃæhŽãÜœÛBÊpmøp/–{©„MJ“™ÔÓ;ã¯ÈØAÛ V|Ñ-]Tߦ†Ä`÷‚xB–,åT9‹é -Z?lbè?å»àuP|ä=¼ßÙ‘ ì]oXjhi^;J, oò±X,QÚä³°½Rb4¹±&ÞÀ9Ã<Àw f¹èÞ&ŸçQÊK][G14Ò»&¶ae.Wþòòd•òÖh2À77¨ü|5¿ýS`võçö6­ ø¿{ýÑÕÄ×ï?®)cÀ8À[™J曫0ˆ· ¡"#ÀØiHào®-œŠàž†ÐÄ~hDÀÿ~ÞâÐßøÀ33/gVð=É{°ª0-Sþ¦4T7‡1€`åÂÐð‰˜Åæ’ÉÊ€ƒB<”Lƒ tî$"GX\BÏ€§´ ¼½]Šò ––òÃu€ÑäR^¼Aä&6E]]ÙáXôQ 7›À-]–òGPäËKqßyÔý¼xt”¥ü3óiûfu[ìºBÃÀW«BÏ•BLxü{4^Éþøõc±¸«é1„2 ®P£ âlù¬RYÑ]‚þæª×¢¿E!>ø¬à©ü!†^¼|§òíþQ8í$ÃápºðŸ%€ã©ÜP¡ âωmWÍÂá¦^ªêꞆÀ`¢ë½ZXœÂš°HOaucFQ5††§‡-vè Úk9­N­<æk8ÀL%s|}CBßCÕ·iŸL Ôõûàõr¹œñ–a)z8\Fó××ËàÞ/ –6JÙÀ µÝL {èFÌ€áäñ‘xÓ p̵8|Ç ÉnöHð6–å³pm”$×c³”Å;¸H߀æçŒ731¿-¼½ >&2*cÀD€Q#ÖÀ?B?@VHÀ[ðåýràP-T)FË:š°Dw]ß,4ÎÚUX!‰^½$ po:rÓvÓ¯^) ç 4*ÐÙB4JN"Àç:ÀI9Àq0<sÕÖx’è.h…†•gÐ!µp Ì^ØÑpK(_܃²¡¼aª*žÜhc$ ŠñäI °DÏùÃ2€‡Ò ›Y–Án MWתžTÓÅð©@5)dÀp?²…%€ñâ2“¢²0`©Pm4ÀN¤À¿|v gk©{¬ ]Îd€94u=( oú16îe€uõ+ ªÐb ZøÔ§ï3òX`—+ ÏܽK$øK8°Ëçó·`ÎFýø|-ñp‹;WD]«”‡g0Å¢²s~—@ –€?­f2°¤ºõô•ì<à+ À?¢!a„Wø6F£¦, cÀg ¨°,ƒáß³ZeE×…8Àߊ<¿» ÷Ñ·ª8ò-ú`áŒ$|8”`Ê%”›Îâ C~«¤ósƒqò4$6¨~I+l¹´5œ’/lޯƌµídWcÓ`¤˜ÊªìsðÃWqƒZ3¥sJa¹r,QËÔ™Õr.Þ´¥!ƒUuή?#—QÛ°çOcÍæè٠٩üó¶߀u`âðm¯¬Ü;;ØŒCïý `)bB­8Ö:§¥ˆ TÎ’dáÆÇrqckG·¬‹íÐÒ”j© š?Æ5• aÅ|åCm]Šò”r)Jл5Û0ê<šÑ ðKõh¢r(2—›ë‘¿ÆÐ㓃°¾4H€­œÅÉYFi­SZ_ðÙn9c‰Ø´¥AÎÍ h ?¾ìÈØL•p˜›w `šÈ€5Åå†âE¢‹”mÕf½43Àãšîòµ gÀ¸ÀŠ­ºÆŒ î•x@{`ÿqœÓîé À4‹US>²Úðpå—ö ?>>)< à.Ü—%h¦¿ÙÕa€±0àᵘ‚Ça£&ÆsÕ0ûpoE?ú«`©NL>e× oí{Œ~Á×°®P¹PW{0°Ž<‰ß `ÒƳxòYÜF˜“–õ ý CÀæßFVÞ'Å0àÞ˜spÜ Çn-ºìá”[¯¤<´þ,Ħ-  °´ƒ©=âô$<”066þ-®_ÔRÀ ,z3'€ŸŽV•­¤²©r5_•ëÙ<6êq™ƒkÙ<ÚF’iºuvv;L¥=_cfúÿGó&ï oÇ O2§ (_JsýR ±œñ*Ÿ, â2Å™þ¼¤ ðóÌ€gÿ6–Üc¹m£ÓGáô‡Êk³´ÜèŒÓÓ {TŠÝË«‰]Ü6–Ön¼K¼åª¦VÎ]©µ‘¡? ÀZ¯²Ív©P àA Ã&ÀÀè\¤~øoð_:BùQ6ìY€ÿèF¼†Á¾fKüÏàÀ÷L<Èk=ñ¿~gñ$z-Þ¾~ûë¶’Ö_*c™Lë·ûJðVc´°´Ÿ§Xzíìk„~€¥}´ °´#£íL€%€i¬Ôè¯ p¿øk l8ÀðÛ†m|d]øß`ŸøÑÀˆíÜØ‡ ° ðSLÜs_LÞÿëö¢ã3}“Ø(}ú¶æ-üg¬]áç•ûÚÏ€!&dÀ<=ÀÿJbÌPÔQ9IEND®B`‚waybar-0.9.9/preview.png000066400000000000000000000541461416677246500152410ustar00rootroot00000000000000‰PNG  IHDR€…+a}’PLTEúûüxg‹GIb?DHÿÿÿJJc(Uw$).ñóôèêíMJcwgŒX^nKJcIHbÁÁÁJIcJJbNKbHIbdr}LKcMJaïô÷¶´¶vhŒáäètg‹.žôññòøúûàâåòõøöøùóçÄÈË^bfÿ¢ŠÍÏÑÿÌš‚j‹ÿÒéê냆ŠÕw‚{ƒø¢ð£‘ÑÓÔjns·¹¼ÉËÍÛÝ߉’褒h×»½ÁchnÜ£“‘•™˜¡+05ºv‡ptxŸ¢¦l‰íîïÒ¤“GKOÅv…LIcty~W[^39<×ØÚ¨«®ãb ÿØŸNRVÿʘ°³·;?CÌw‡äææçè꣧ª¬¯²“pŠýýþßxƒ$‘C$zÛ´ÑòéÃpE¡_ô—wn´ƒÄ•“æµ£éÈÖæøŽÄŸ_žåz®é•¿í²Õ¾NI[´ŠŽBŒàti‰¡|ŒÉ—`]¬uºÔàË•ú¶“± 劋ËáÓñÙÒ2(×§vÈœzÞ­gïÊ¿¤z_¶&H1$lQ=ÄEPŒbGÌdjziNÖ äaVIDATxÚìœíOâÊÇñl{ãJÑò˜ëًȺ1d‰{|"ÑÀļ‹É FvÃ+c²ï}½oÎÿ~ÚíPf˜‡NK ß«`™™Nó›ù0€›ûƒI… ½|™àÕPKòêK´Ÿ e*Jõ£ž__£ñÄGc¤;é¡1“Ë)ÏϵœŸߨë[y+³>-ùU‘Ç«—ƒ€!`@À0œ*CÁP0 CÁP0—‚s«:CÀ0 CÀpLÞ߇€!`@À0¼»]\úæ4I­J«Õß´²¼ú2í'ö]fM+…úQÏŸÆXE½>ù¥ö½8ÙõÇݾL|Msù;âø¤4?9çg.öQÆ·ÌY+¢Æ$•ëãšÖµXõBÁé1—ËŽ€©”W x_ Ûe_ÛGºýÅ{¶ôù…JRû·â$áòý)º¬Šl¨´Œµ»j' ®\¬ªRP¹ãí?¾ð´Z¼}¡Ar1‰öMvjñ’H=ÿÃå=LÙúQç—–ü%JŒ÷bI›ñ œÂúcÒM®ϨùŸÈ"áÝNn¼õ…¢Ì›¶*Ì'­‚€Z9V‡Ýg°º+{~[|­IDÀEš€mZË)°-ÏT˜·Bý/‹£$`¢>;µèùMío*lDz"‹ ˜wþð|u‘õÇ,K².&7ÞC!14Ëlšñ XvÖÙ’Z±ÙÀ–<¿PQ[Ò5áò¶’€ƒ¥P·€muóºÂ°ØZ(TSü*ŠJ.Êù—è¹MºðIŠ‹SÙÏoÞT°%Ý8ÓnS'Žõ˜žEê³X‰'¼Âªä®?EÛqä2«D|¶Å'm,¶i.Î* ئáM=vMÄK1€XÅã>Uz.º˜žö ɶ/4tâíÇUÀYŸ?JEM}¡Øœ ®{þ&˜ÿ “VAÀN­Üb^ToÉ߈¢í~—¬Iê¼›,.*Ì\椥Dy¢fè )Þ”ØYÙ V^¼?D*\Lv‚˜ê„¦lÐWt…·öð®Ú¯*¢}N-µÐ†ûï /?„~§ç7µ¿²ùF*ø¿ËæƒÆx*zÅXÞT“Í'}ëÄz!ÿõ™»j%7ÞëÅü ‚€Z¤€ÿxÕoè>¸¿ú:¤ ¸0úzórÃ讵{*·¤j§d4uŠT‰[.‚5ÖˆÈEG‰¯Ò¥ª >]™,¯B3“ˆE1•£¶áAO‡Pi *Š¿µ­Î­hEÒ¬í^‡¸™ßkw]:L›×?Oèî4+Ç=Š€+G–iUè]:ù˜YÛêO6õ\OÜ.‚€!`8µƒ¯K Oõ“ݾ#6o3j5z ÓªNu61ê¶€ìž~=õoü?†÷§lÛû£rþáü÷—\Ø+9w õ3jôÚþ)’2õZL»€×ݾ„‹ân? `J"„B_KX´bO}û6wQx?²Lo3j·Gf¯WZÞ£Æø&`hŒBCI¸Ùuö«ÝùM×?àò±Ë°iÿôœ˜€Lki3 œq»#Õi8?®;Îø¸›Ñ÷sÔé÷Mo¸Œ›ÎhÊ»Yè“PRªÌ9?ÎG³›Ç£ÎìžûXÿÏÊŠ°=b øì¾OýÖ {ÝìÒ®fÿrdöO&p:g9µSB]Õs=Äç¯ØMk9•×HЬñ¢•ˆAÌÄ,`ÙxAÀz<ØË.%¼üJ8ÿœA·Û÷{ƒÁy¯`N]8ÞT†³ý¥q3š¾ú<éšëÔ e`Çî®óãäfvpkôWxjýàÚI7nŸ‚¶mk÷‘r5÷ÖÓ¹…½l„õÛþTícÚa§LuÛñj)mÐx¯A뉧ö nà %–ú¥ú«7é9¿Ì_ >}·¬E]ÓtÌ| º[w}{0» Ìï-xaì‚÷€CMV†'Š€íýJÇIàóîòªcÆãq½9®r?dHœz´(ŒlynSìö$à¨ç—XK”ú±«j-­'~úê‹7*íZõüã^‰d’陟•‰ç/è1O ùM/l;5®t‘Ÿ Dz¾ª…JfþT«ïÃñÓK»XtvÀÞ£ËÎð¼ïÞ%_‚¶{Æ$TŸðñt|ìï€ý{ÃÑJ›,_Wh6í÷f¥x{üÎÀþ©êRUÀ…XÒŽUÀÄx¨ÛˆŒ Xb T ¾šŸ€ Ö©•+•”éôÀÿ*°ÿ ¢FÕª2¯ÆÆ€©ä÷Áõ±Pj,º©…üŠ’?ÖQcº¡œ Ø*:×CÚs°Çpç¾~ü4¿y<êÌœ¹^p• [Àd©½j a¤%È(Û6žI_¯ô¯~–u_ÿ¦eҦόD®/È·Au0˜šÙͶ-Käü ‚|\¸Y¸·¨Ñ\­VbÂ0Yj/€zú¸D}^£ÞçÂ÷T°«®8¿W‰þ8çJ%( ë_£^䊦„âI*©\uP’Lç˜ã§±>/¾D¤8×§Ö¾ÄEñŠRÛg¨Oú’RV{¿{g&–Í`r³ŠÚ>}ÐâÈ?éöÃ]£¥Ú` ¹ô ­¿jƒÆ[D„òg@]jW4ÍYÔ´¤`Fh^ï)gRø¿ Z›%àX¿¨´ 8AEmjsÌ]ê" Xm|ãžJI ¸æ°`U€eð,ÔTT‹¯¿¥¨K‘zþÔjþ(ˆŠ4ûÒR-²€Ù°k’¥È®PîQ—¨_ª­n5vsÎO}œz¥µ¨ççÁ©_o_qþª4T"W˜ã§±>/¾ä¬WI‰ñŸõÌ%¨ý Xlþ©Lù±}úøé0wýTù£¶hÒ1þ%®-Wi4W*é0/@¼±¢×¯­YÀœó—x—µÿQÓ^iT4öT¼V‰:+b镯ú ¨’€£ŽoÜ3ImÖ«$5± Ú í`ÄæŸÒ¤Ñ˜Û§—¤ XãHÖb°ÐP”ˆQ–[Tj¼¨IÄ?èåTép-¢€kjËFiOõu"ÿ‡T‚”²%à%@+›Ч_(C®é«Ï‰¯â^B¼}¥¼&1h‰ù’uó‚ºN/öŒ^tUÀÔùuüÄe%óÖ'î¢QÀ5m¶¬¨ï§ 7¸Éž/cHH¨¸Æ ‡\U*mx|ÓÖ>µù½4¦y°ÉÞª¡µgÑßxÏpÎ*5Ò¶®¾(¾¼Y›B6ý ØN”ÌÆ`bÀCAÀN­Ü§O0¸€!` qçóŸ0Ž€"xSaèÁ0ˆ—tj³»Š¤7¾H•8€!`ÄI 0¾H•uQ…€?$%Àø"U `Ì,À©íêN~c!vPÝß[y¬ Xt àd sZ¡UàátJ_“0€Üå: n!Ä ö¿‡³×ÆZùÿļÿÍÏïøÿ `’ð¿+00b°û9ÇC÷ $&àÖüÿ7™ øã¶qß<šYcrÔ¼ÿ SüÇÅÑíakºv¾ÜùÖv øØÈ*ÇÈeÈ¢€[;yç;×òô»ÓÚÙÒpÎÈ.9$3dNÀÓ]ï|ì}måøØÈ2Ø@övÀ3ëæfîõ¾¶OÀ÷F¶ÁûÀ¹°/๷RÀÍŒ ¸‰t€¬ x&Ý\°ÿÝBsðÒ2&àà%èV¾5c ßžd\À¤3dm¹ûþúúýîÈxÇßGðËç·¿^–‹õ–«>]F¹†ö“Ø1ãeçn_gÜÒ>™õåßâRlwy%î~ˆþ™Q€MÜ·ò¹[ÀÏÏ+üuü×›Ï_K¾; è²§ØýF#²€ÇËÎ=¼ú<n?È øÿÏ_]{>ôÜ_û’>>:¼6Œ—: '`ïïõøã¯©{?þû÷··Ï¡RÃ/ Ýïu# øÏeß¾>\ý÷û÷×ÎÕÃë-ñ‡¶g’~y®7?¸Å/?üÏkúI]Àx 6t¬IÀ¿g›ß¿ß>»wCï_žéì~,¾z}½3v cwbܽ¾^ù‚»ürðp$'àË/ŽaÛÞKÑÏ/N‹š|6rþlCÀàöÎö'moã$œÄòŠ·ÆixAÇF¡M@ú»-ÀMY›Œ<ÅdL寯DÍo›l^ÜÓýÃï9}¢U‘ŸSôûYÛó€1Ý>ûžsÚ àéüΙºJlÉ¥–=¬#‘8oD„ŽPÒévDîD¢%ƒ ԜαHK/ i¯¡Wׯó|9"ÈNQ»£Š²dš<×)ër¡ ”;ôƒÚ…|±`ŽÇãs. iw;:ðE`UK†£Ž€CZ£ž­’T˜jUPCJ#ÜÐ1›,õ†«$'Ö›Yg ¸ÛìÒ”ø.í®Aú³UU¯—rÄ+ jKonöumVÀmÿt‹ÏÓ>ø °ýGû-•ïÛO£Lfüé-[ í ¸ÔMKö°'àlJ2™x©€™|S¢Ô¨aòÔ˜¦^0”’ä6ôêÎ$`ªOAwŠ4Áh7Ö§¹Ç&Ý©›Õ ©¥E"©b|+HÀ ^Ny‹ ÂQ³Ž€Ëb<“U™|Iºž!f*Þ §HfÏ ZTÉv]ÉH,:7¨°3{‘šD¡µeê㙪DmäöËÌãNQæQí ¦#àdr;¹í xr³“Ï›¼‰!hxÞ󔀩c1.Á%\µúîðÐp‡½Kl Ø0'Vó¶€ãb^Ï;cÇ2íKLÑ ½ã6ôêÎØW´UÊ9à«>馉´…ý Ej›´ÒV´½XÀ>çZ6v7» úÍÆ ©€·©ŒIu#±f¾‘wF…©]EqÓ¶+aîe©•z²##œ$ÍÂ\U{ºTž¨Šº¬hf+uì[fò3þ…€`\ÑÙkU'vÞqÜ»DÌ'`kV¶È¦€§œmÙŽyÙ­f ƒßb‚•݆^ÝÓ¢/ªªÊKþê¾nºÌìTÇmE’¨ÒŠÑèV€7¼ ÙL,ʱ˜ÁÖASK{M [ß\Üc¡8ÖÍŠu™ Ó LE™ ëQÚI‘g •¨QÒo¦âLß³U-kê¤`jRxzº?-à< /+WV™~{x8âFãDb<æF¾!hË£Š(à4ïÙ´+P›²$«• X°¦~ç,ñ“ꉔ½n|Ž›“¥Øó~?x«¬òƒÁ{oA³…` 8³çŽRìÈvC.ì Tž±pfS·ËM>i 9Óí:W§¦•e3» ªåÛŽ>)¸Ÿ€MÞ4g ŒÓÖVÀ•Õç€3ÃÃÃDrT¸ÄŽo–åQ]¬·xK7EZ(hÌ“Zd¿TtÝ;GDsNÀ¾n|ŽtÓ·X¤©×¹Vac0p¯ÜÕ¬ëˆLb©€ûuÇ’ÉF§ž"F˜F[&O¢vuÇŠ=Î[× ÷› [É•jªTìsU-ßêʤàO† [lüyÆÀ8`ý\Ñ+Î4“0Û[é2¤qlDáÆ¾Ë,%Ú3´Šž3:³V„HKG„RKj±ESŠ˜’:büvwE#>)ê–Š[¹è”€}Ýøœní¸ àœuŽ”µš’sï¥Ù³ºQ"–ûTÄÅm‰Ö)«¤Õä2{Z&ÚdVÍ‹uŽjÙŒsÑ=Éb&„aÖ6WYÓU‰Ú,d:õ̤Àp_è.°76®[ó¿f€µ°•|+vÎÚcЫ݈ãíx4³;bíLeRk 8—KjGpŠ-YØ5EÙR–åÓ¶¦Š²¹]À[rIöµô’ªM xÒ_À‘\15L#p®S§ÆK3ÿêÎA‰]ÇË ­‹,‚†› I×÷I_”I·YWefÕD˜5Èd›áÆ&‰òÎÅLöš,ݺ¢i¦*QåF]4|¶€ï¸ 7‗‘€-熬ü[±uüpûoEù©=Xï¼?Æ7qþ§™˜¬¾g²‹Qÿ6–}"B£è['¥Ý»*ƯQÀö¸³3\±Ç¡WxC{ò0†ö²?г0Q4ï¶aY JaîªBÀðBpÅNÀWÅ« x•Ç>“r½JHµ¡,-Á²þU!`x© Øz…*7ÿ®(àHæ¡?Ð3ðƒin>FU^¤€ÝÐk ˜~±×j^GȺƒÓÖNÀ¶t©€-¬¥¼ö@Àþ°€méz¶xuŽ­½€c8Ÿ`í°ÅëpbíœÀù ë—€!àþÚ ¸ó ``D`ðTÉK Š3ÖTÀ½W*ày Àz ¸Çl½"W¹y)„8¤`X#÷†  _îUB=/ÿBÀÀã Øv®/÷ `à$`k¸ç½ `àñ0{…œ5Ð=ÆÓ 8”LpÀc’H†ž…€ܳWA?©€·qVø#l?½€¥WTÀ®~{çO#àþs ¿1œ °þýczb[֥ߡ޹-ßÞùùÓ8„“À$ôÄvðù¹«ßó³§0ü `­ <k××›éAÛ#Ï¡s—³³§0ü àO³Ê³ðŒÆÍ·ÝáõÅḭ̈5] ¹ú=;~?£ù_§$¼\2™ÉöÜÃB#BvGlYÒlÉlÎUÞïÝ\×jµ‹‹?7µÎÛ øìر/%PÀU5{{Ÿ©h2¸ëʪ(›!Ýç¼þ€LkÓ·3½ºOboÆ»¿~½Ù“;\þº÷ó÷Mí_7µëßÂ{åá ¸:>>³ý»Y« ¸ªòê.8'òbÚäçÜ<ù¯ÉÑÁÁQ{é_èÇÓûÕc½Ï¬ž|\(à{v«¦Ý·øâíû·÷èíäÒjžl 󯘮ûwg¯ÇùòýóçÏß¿ÿß.àÜÆÆŸ77ßjÇÃëë›Ný«±Ø|]¾{G®ôBµ˾µcš§‡fþ½ãÙ¹Éh SŸç»Å‚±oߟ#áŸQý~þ2$w.Â’ƒ.nþ½ûí¿þó÷Íûåf§0°õ¢ì,ð=ýhàÿ³wv?‰3 ß„& WÜzÓ­. É¡o mb¤’ /GS0†Ä%(ñÂl¢†¸è¢ûáºÿ÷™™¶P¾]Ü…ç禸ÓéPkå—g:ª²ÛA^‘'8`¼§8bWg¶èÊó€€]W·Í«fãÊ &àæ-¡ ²ZÍÁeßÑÜlY¾å' ¸Åáï꾵׬xÞlÐݱl–2í¸·¨)…j¦H=›"F(TØ&²Z,h:!ªÐ¼JWØÕ‚-¹mGŒj•%”jUq‘³ÅB.©V«u¿ÅšYKk–'àHVË’´V#Ž·²9S“}–“ëYµM{ÛÛjƒ6™ÊÆs„Äm2ROÍä´Œã—u[Õjø“¬ ’-ËfN±"JΔeÿ³8(£‡ëo×T¾Góp1ü1|Ù>>~ìQÿ·ÛíbX~€;\¾Ó·‰ÎÉÔIïįכòŒy7Ø#pȘcyÔMÀýZ}·ýê*˜€›-©ÆJZ-ÇéNKÀ]*çîL{ô0-qZM"5Î-]"b&FˆÃ®»ò…­:’í ØKÀªTc×ûûV=§¹m+ª$Åø6ºf³U’“+D­h(é6FRµ´'àLÝÒíÉfˆQ!d»,U4Q2f]ÇL‹’ãí‚$;Ĉ‰Z™äë#õˆš­I†â—UKø‹¬5UλÉ./«~þL«ñxÏðãÎ䛃‡¼þøë˜ñå®ÍüŸÞíîïwAûþðe_—I¶esž€SþWj¢€·ž `q\Às¬—>=Ó„+R†0]å4ô`>çZuµÀD £ Xd¼®j3» ™›ù•gWÀ´$ÒèRGüJQÅ[H!ÚRtHÀ4˜Æªo³Ze¶•â¿})” ¤|àðU)º-1÷݉BK¨p¹€¹\÷³$bg÷§e‰()¢ç¨_·#ýý ¹ÖÒ$SqSt°ï‚vdÝ/E4`͈(rF$DÌÈJÿ#¼o¢›ØàëÓ›÷óþøý ¾m¾h·;_J¥ç¢î|8.à—wAëýÑÔFtn‡%è¾ ðxô9 \öKhîÈ€®@ö:¦%6¸zÆ ,ÖB {oDÅÝò‚u¾â-ÒU2AÀñ“*_ºÀ7æÛ°õlÛ–h‡n‹º{Ó–Åœ”m m2*³ Âiæ”eäÃ<¬efto?H%§"ŽªA=\7÷K!`Àºa25oD6·­ ® ߸€…'$àêÇÇ,ù³/&áNI~f¾`fúíÜw¦øÕa±aXÙiƒ°&$àÍ„å Xp¤!$`.`‡;ó:ŸcªZ­™£ }°Å£6ë‰æ°ë©|¡HÓœ8┥^¦YöâD†Ì[¬dhÊ·´0ëYæHQ ‘è캲©“š™PÝ8U½ý e­¾OHĬd©ÇŒk™’_ ÖŽ¬M’I’|ÐaÝ3ÿ~{ü,̰ññòØ£Óf*ç^¾x÷!À"nCû·!•MSœzÒ„l5›çŽ~îwA_Yb _µ,éŠÂrœ–±6˜€Åf+éöuË_ôÆ™äœ13ŸKkynfïÊÍf7Bߨv®[gM~ ¸Æz‡ù‚h9ÉQ<9ZÛ°¦D¤„E+Ø„]Çe5‘ ÊÔh‘­ €ÝÆì(Ûƒ õ¨J¿3 ‡è RÏXê>‰›I+Æú’CÚX$*‰‡ª· T×̯Ä0éFÃõˆšqjY£_ ÖÕÈÊrÖPû}c‰[;ì¤ëSñÝ|oüjsûÒèë øßâþ3ì>Éðý,?ÅÀs'âØ,çM9=ýQ 㣠ýÙ2Î]êÍÆ$àn³Ñlñ!WîZ¼‡8˜€‰~Å'¿r'÷p0éÒ2>BšµÌ*ŠÝÙæïÊl¸uËá­6»ÄÊ퓘Bá "Ù…¢ê ¸,ê}—Í‚lðAXÕªBwÓ¤Õ˹-©…ª0o,éú4F_¶s‡¤–Íij9®94â–ż¦emm³Bˆ®h¦Röv|Ž-Sf‚3Ö#j–þ7Ñ/…€ëFB–Í:ÕÜŸºßßüøÀo¾þGxB¾þz·ã ¸Ýù·.<§ šø ˜Øž-à茩(ãþT”é™Ïb`ëƟž¼9ô½8T¾¨á™40ú‡~¿óæpÓSp †·Ÿ«~è/8Ú‹ÏAcó¨Éú“¶ Ö€µ%&+:!ºÒŸ kÐ}zúÀüðù^˜{’üÅ.ßßíñ||—xÆLXÁ(`ï"0 ,RÀù¼o`$``YÎçû†€€¥%à‘AX0ð>€W]-UvÞoÕÊÚ.Ž ƒ¥Pªl ž€µU)ሠ±5üÊ-Ç `°èøt|R.…À“@àyŽ€€×€‚#LÀ)À¿00 Ì¿5a"µƒÑšðêÓÁy€€Álv“T¶ÉL9è^}#M—å]@À`Ad™qíp¸( N:Á1Ãa“•f!`ðæÞáÿ à•〮²(ìJc·Ê®_†øXèx6Õ¢¤X…€HÀàiTxŸsq´˜ X¨@Àsü[úa``œY€W0Àðª±ëÞl–›îíÀ»ð,FüË ƒES*ÝÞÞþ¼ìíõ¾þüõ£x€#‚ þN\Ñ&ÆîšÙ•ø ž…<½§ç°òò6noKÛÐ÷Ÿ½ÞåÏïß•Ð-ìÊ ×€W·Z_¡M胆q_SÀA Ç…Â_uºèBé…û, kûÇV¨§Óõ—ÿÂiü-•¨€{½Þ×ï?òZ€O²Õ0÷/º W4÷ì¤i#6ÙŠôd˪¢¨ÅfÒ´ikþÏÞùý$®ôaœ¤“ŒwÜrl_4Í –k­%RÓ"IÏvY‰7r!^š, æ$‹ÆhÌù¿ÏÌ´ågAö,ºZŸÏ‚b;¨ Ÿ™o§¶ñ°ªŸN·!Õœ´ Ûdýºùϳˆ6¢ƒÐÔÍVÍ-f‰Žj«@Àsþ-iZI+ù;ðÏàéù¡}÷pw÷|£?)œn3ùrÿBÀiCô4W×~„ì)Ì’žîü;/8Í.Hö‹>Û?葆µ}·r¹*Ñr¹òfoÅÉæ&<ÅÚçúÕJv!![lh0`{zxh·ÛÍÑÝC×?€…‚!à´!¯j6¢&ª°\VEVe6u…'Z[Q\G*»®*ùª¢úLÀlM˜ey¦c»®Ö£¬xÂÐvÝUUßU])º)D¦³Æåòõ;°é¿(`]Œ¬{.?¼ºKêóîš.{T-fŠ6û)[h¤A§-ãÒþ‰\ñDRU©Ú«-+QÀQ;êU¾ä$*‘OÍçËÛI DÊ|ôáh_¦V«Z¨øü©Õ‹{]‹ÒÖÞ‰uAÏž³… ½ÎwY¢69 5R?+T¤PÀNö‹¬y¼•q¢S¯V¨šé«©"ÿjš¦ü¦€9Ïwwíád4zÐ||Œ¥^À¡!àt xom6>I°£Ûaý¯Ëœª–%C7=Ýc«<æO©Îì[š–¸wUƒ-q¤Eó%*kÃ\k³›ì^¢y™lgs¾~öTiÓ,©' Ø"Õb×1H¶^ºðhŸhzc^Àñ MÖ՞ܩç‰ë”IÖ=_™*%|œ¨õ¥¨ê™ÚLÀKÛY'à2éÕºlC-»Wµ,rRQŠD¥.ééš xºPe +ù„X ˜mìÀªæv¯F½K²BÀj¦eʲXدrÑþÜþa8öeÎZ¿-à}|5‡‡“ÑåxŒ1àÏ‘€1œBÂó0$È`VÇðfýÈuUW¹m8K†ËÄ)‰«äHu;º9mS·¥e‹\,–ØvÔPXô_+ÞG°ëm%`O&R’€©˜ ¥D|Ë']ZÈthÌ 8^‘¯JTò-úPªÓ¢ G ðljÚù¤Ô¡¾3ðòvÖ øÄ¢VÉW!6%D¢.é³Uíd¢"¬x¡/\°Àæ/@ãß*싵wÑáÖI‹÷À›l¡Âo(–U-¦ý&…ÐÜÂÎv6Ã}h®vAw×£‡Óæäþòz¬H?;›Úƒ$`º ÓGe]ÙMù…*h§¬{¾®0TÉ·UUqE ŒtÌ Ø×Å ñtTxIÀF(`¾R÷#»FøýãØÑ·*Âb´h²€y_xRŒŠÄ‡‚yG+è9! ^EË,å‰\Jø½qÇíÎH¦fvf^ÞÎZgã™XˆIIžRÐ"s.û xy᪀ŰGYÜeíhøJY.æîs#dŸ-ôùˆ6çÂJù;ÍÓD W°±e6×ùtð8º¼¼˜™€ÇO?­Íí0x—„^Hˆb…ž(àp¾'׈B«èe6B ëúsž&àEÛ‹^LÀöGKÀuc ×¾~m”º<þõ—\6<íÛ™€£–Ñ Jëý"ùâÓ@/ɤ›(ลíHäŒ?ìùÊvÖ 8ÇÜcm=I<5îÚï'™ xºp­€ã‘IG•gâ•6Vìp›ü‘ÒþNsµÈ¿š¦&»wƜͤþçÁÓ5ðýd¾]_ïâ.hþMkUXHÀ©#<ÜwuÎ%&⨫¢ ÚcÀF™ËÒWU!`>å]OYž ˜9ÖQ¬8b Yh}:ì”#£•w,à€—`9ÒvcÀbïs%˜Û¯S–¬LÔ=m¯ðYcÝë;±hPæá²š$à¸]Çëð?¤œÊ?]ЋÛÙ$`« [Ôa[ˆ]Ë{›ƒÌ’€7tAÇ®dŽx’¯—ZòJ´Þ åÔWµ¦]Ð¥þ¶¦f¢Ow\¼·ìzÍ<¶~nlR€‘€Óˆµ¾å{õ ±4¿f*JCÑE2¯pv™-Åö•º¨Dò¢*è©€£*è9{Šj/wA»JX-•u[TAûaïµÈźÿ¾°çJÛ 8Èd̯½$3é*|¼4aM[F+:ùžêò"ª3ÒòLÒµõB6IÀq;h®ÚË[´Aúf‘ xy;›Ì6”UŠò,«¤¨ˆz«y‹"¬½õ]Ð9Kaö­]øì^E½Äî!аN2Gf!ÍéÈ_ô.¯ÑN5F8ü^êÛuA¯aàká_1å§HÀ| ø6”ï-œŽ˜|Õ*!UÓˆ¥è½è/ñ¥&vá¬Ä‰8^šãwAù¿ `ªVóG&ó슀©R,UJ­Ðà xÚ2^ái{òûÑÎW]zÔ+TûÉEXq;÷LÎóy¼†|`ðîê¥íl°u«»–våªZ#Á‚€­>[ØX_„Eù»Eý‹†U!öY¡ć!É2báË/× IT¥‹¨:þMó`¡Þkñm, ðA–ö,2pfj_8%"qÌQ-\²?í ûní^Ànʰ‹³û¹ ;\À Å ø'Æ-?Ú¤›†íü4Ž<‹<þN·€Eþ'c¸ýß_è‚N&!Ç— !Qǘ?°Kw.`WñÓ—€¥O+`•œ©¹­º!`þ·­¦å~_ÀôŸq˜|§¾Q±oÓ-àv4ômlß[8%X BúÔiiçQÞµbÿ® 0âì ëNGD—Žðƒ˜êÅBñh›îc˜áõûÞðàéšq9i‡&ž@À©ðax:Âá©—[x8BÀé ¨y¡5,À"% ~Ÿ8˜?°Ó‚Wf0xl_OîG£û ðè~ôÜÁ^I·€ã³! ‡Â¿·Ã[858ÌÀ-£¬. X[Í50îªËóÌü ƒ×ðs›é—‰Wø~tÿ€1à´'àp8ÓNiBÀiA5XÄšp‚!à_ÿ«À«øánò̼;™ û$Ý>ŒfâÈ2õþŸëwx§†NëbNÀ„%i\Z…€Á{ðdô8ºª Y~Â>I»€Ã™82í+®Þ¡˜NN¿Pˆ+ï™Ée5Ð* Þ!vAê° sußl_AÀ©"˜V©:ëJ::ðê¶ *°sk_µ¯B àO ÿþ”€oŽ™yÿf×oW§0ðF>>fê½ùvõíæøÞNÀÇ7̾Çìo&àc!_ ¼€obCÀÀ[&àc$` `¼Š€DW@ `@ÀÀ‡ðø¯.à&0ð0 ü‰üc^Àì‚_KÀØ_Àë ¸F`@>ƒ€›LÀ§0ðvnÎ%àÓð o€›‡"Gú…€€7JÀÍÌL¿§0ðÊn27›| 82ð)0ðúæ0gNOCûž"o•€Ã.èÈÀ0ð6 ø0NÀâ À¿ìÝkâLÆ…&ÂB/ Æ„ˆ7nY«úŽÝšm—Ñ]•B¼ 1@þÿÛ÷œ™|ik›¶Ú}ßä<Ñ|L’™´…ýísæÌ„D"‘Î`é€cSšD"‘H¤Oð¤Xè'®À$‰D"}–þ)á‹k0‰D"‘HgpÜL˜D"‘H¤Oð¤øWb À$‰D"}Š–IXä€I$‰Dú4ÿÀO‚þE&‘H$é“ð?ÿ€I$‰Dú|ø'˜D"VõvóüjUýtÏ’¯^×|¿rþ¤9àL´`0˜D"Š¿=E«œ{i^S¥^?Õ³4sµÑ›VÕwËÌ÷s àIú'… I$Ò‰ÕT4€×™¿Ç|YiŸêYš¹Úè•w./x¯ Rðê&‘Hç°ö zÀ—íS=K3W&‚ô}R^ð%¸˜¦$,‰D>€¤5±(Ñ7ßòrš\Ó8à¢h3L´¡ßé/ØÐοtsøC­äðûT#\nÿ$\ ‡énZêóPl·|ûÎz×÷¸¸¨`ǹ/j{¾:Îwô‡ ÿ—lšg°"ì,|Û›ÅTU§b—ý’&L.€‡O¬sÎÕøÚ†Õ£ÇyÛsŽ€Ã˜ô¼ºÆž”õÄqæ¶bœT9Ü=¼oÒ8M#O°@ꢪ,¢õ«ø­‘.+€q*Êßà8à€œïðèKWl]´¾—@÷Ão°A ’¸=fBíÿ€»ŒuO`%FðB©.âõ+ÑgrÀ%wÀ¿£>à¢:àЭ¸n`uçr×W+Zèêpä†ZÅó¡h§Âyäôc—!ça×põE[d®æq×Ŋƺ¨Ì«hÍ*¾”"ëãÓh·;rÆepÇbËÎjÕY2k! lë¹s zÚ`ïÚÌh‚\hKöâ‡\Tìøá}ÀZȃmÀ»ÄGzºàчP[Ûr»õÃ|׊½4\p߇[UŒG#¦±*ö Ñy(â.ñéä´†…à’xÁ˜SÇºÃØ"€ùuï±±o-sxe®[ ôâ·­™YÅ’>kmXËè°U¦fýÙFLvßbë—“°€«¶;r­4í«f³ÙÛ³¾àcq6A.¸NL0¸˜XCƪQÚÔ6r¹Â à sWªZÜF˜†âPåøÍ=$n%7aµ€cà“³d¨*Èœ¦ÔèÒ8¡¤pÏX€u`(ÃZx¶x1´”qŸõn'zuetØfÄ®õ5ëè½ÎÊf3¼QØ?௑Àzæžñ×”ÇJÿ‚±q×h±?Fu¥Cãúò"s}–ò“ ¤óBp”WFþ*i°¹²¯Tµc€Ë³Ílq$ ë° p±œŠ\L7Œ¼õ`Ù"y£‘¼[n=ÏG|«JTz˜†… [wq¥8kŒnV>Úá®1Í mn àÌipy‡’ª GûnˆÔ݉H¡p÷K5©cÊ[ᄱ×ÇÜçdb,8§W²IX™ÓàX_XÌé|ýÓq˜µ%vÓ¿¬ÆxX·ºm±êŠMÌkUÙܬ[óÁÄZíø@Yw;s«s•ÀcƇ±±?Éðe5ÀpÛ²ñÑQw1EyöúË”ÞzÓSeI3JÂZ¬§M€°pG†¢ã¨µy¼ 09`Òÿ À±÷•kMŽ®hûhÅ0¤Ý6p]UfB‹1¿¢dÇwžˆJ"sz·MîÚIýØ7Œ öd/2pØ Ñ c²+A¬¥Ã2§ À%°!ÖÉDͨ¸~¿´æ3=pŸÝém6k]\M¬î q0·ÆMýÀFv›ðë¿`qψY­Õªe±‘,üú€ã€ëzsé ¢ÆcO(?tœÖÑqÀñ(ßZ<–8ÎfA›# àuñ µÀæñ&À…vÀ¿ÈJO^ưMÀ`òr2‡‡SpàëÆCW5 $ܪ®@¯+âÑq7¡êʽ6p™ÝìF †;=Ï}¿;Ùg¬Ã™x"Žô4¸Ì°·ž8Ö|8ÒOª¼3aeï1¯¿Šÿ\7?ÞÈ>€3Ó`)™*{¯ÌN†…ãh&¬Òø{‚޳  ÀE1Á‹á³åñx#M;<%‘cöÅöñHí7‹ýèÎw3ŧI¥ðyd¾À§¤ü€33]©õÁ¢¿ê(¢¯’,ÉÁ³˜\.üK¸`\œù*æîžÁBydrIYè‰ñæé7‡eýæpö¾·>Ç›p-³V²“>gíp-uÂOú€ À¥ð÷8 K2˜LR¾ïN Y«ô$%@ÒÏ¿ä×ÇžÅÌï€Gø­EK<ãF|”Í—&\FÏÌ„A˜Lr¹¯¿Àœ‡ýIYG¨Ÿû›ÀiÇ|Sð(Ím®=ÿê£d/y¸”8†ô‹’°H$Ò‰|v½ÀgiäÐ×R§8®%¯IJ±|€gpùüÆ“H$ð‰œu·µ,j×2×Ô<“.±þF&‘H'W·úw\iŸêY̼m$ýºû±gäì)NŽkoú9H…ðoDð70‰D:¥zÓÑßp¥^?Õ³˜ïiã™^àÚS gHçü9HÅð÷Ä ü~û—½3ìMTiÃð“5!ñëI‚ñ–aà iBKOŒ{@³ýv~Æùù/3 j·ÝWÜuÙóúÞWKǪ•‡±ìµ÷ÀPp5Œpåü|üoR]¯–ÖÑo;À-%àFÁÔf_$``0‹|ÿ×½ô/00€€ÿPâh,<|?WÀ|j?š,ÔÛ~rV>$`a`*ÿuø„€€ðQ¿00d>œ…C%àc†€€¡°’/0€ Üx¾G~YÆYÐÀ ¸¥½´º ülúC|ŠyÀGCÀÀ ¸°R0 +à{øføûÏëò7º®+àOoü|üóçµù WNÀŸ€oêê®Ð©pecú¹]›;t*üD¿BÀ0 ø“ðëQÁ0 À X~ŸHÀ0  ø€_‘€!`†°200 À€>Œ?#CÀ0 €[ CÀ0 Ã%à.CÀ0 HÀ>¡Ä[ ¸½Ü`î+`»\Åñª´¿³H[Ï]×Íõ^?oÎ…ì§÷ÜEEýË1Xl`_à#¿ÊøÿXÀæØí/à:žù¦Îâï»N´îºµ+½Ç³ËpBÆX~ã «€tÍüáÝ䢢Èa‚™;“­óþ £u"o©f0j›*Æj²ës›üã½ø¿ªAhøfœÏëѨžçÝ÷_Ö›‡ç§øûl²Ræ+£dê_pî\Pb.MçJÛå=TgXN)5×®,ׂèèç»Ìì)ŠÚD>a“ãwÞŽˆ*-%i“ÐíÌ[¯ÍÑv²-´Ïß5‹ý˜É/cóâd Á\ÈÅö-ì0üB¿ªŒyÀ7+`{3~æã­îÈž×¥_¾WÀ+‰ˆ1"*™u±€ua¹x¾ÉJqã|ÜtgqM%£œÍ”€ÓieÖÇ ¸çЫ¥MÞU-7O/ýŠR~Ó¼âÖM——E_'DÝaðVÀß5Œ¬³êÅîäy|ò½ìÚð«üÚ CCÀ·(`š?W£Qõy<'yÇl¼Í:v›& »ó‡Çm#àŠ?|ö&g\ÎŒNÀƬì\GœGnפÂ=ÁŠˆeÜ[ùÖÒ¶™æ&Xo<8ÏG¾Võª%rµˆtÍÏµŠˆy'žh³€u«Ì#^¬4—Š˜ÈÔtQYNÈNîù6×x¬jUE¹Óä1)=‹"a¸c#JÔ¦DÑZ¨4ÛvCÐûB?ïaÃñŠ…4¶f“µÎøÒTÛâ{¼ØJï5ɾ‡Í+-«dl¥îo­ÊîÝh^*‹è5EYæ•âLõv#ðøü%þ•¾ˆÅS÷{^l삹^Ïo–v>/Î ¸•n«ÃrÖ 8I­Išj:/‚Ð^ñŠ‚Øžzì5·±»ÜTýÒ&coÚ\ ØúHÀ–ÝMºU&‘î.µü `omåQJ¬Èí8°CmBªÖ®(—®ÿ˜ö,ê}cðY³„AHµ4›°T}‚°ã½$Dvàh6­½ÚÚ–Ü–œ;¶™µ}¼×æÚüåìK&còhyÈXÕFhÆ•R»®‰vDÍ"œ™Ä¸añµŠ®w°0T†€oXÀKå_ià¥hŸ¼î±¹øþ)¢ñ¬‘ñØõŒÑÈÓ9³ê¨ÃŠuŽÖrÈT5€#‘Ø¢½Í¢ÖâTãiúè»bqÝúüׯ,ðdLÞ ØÖftX¥¶ r´ƒ€Ã¹-Bª½õ,QаªUåÖM&¯—=‹zŸ€)I¨ ŒdKqF­€K^ö‰6ïŽOhᙚmðÑd¾Û²]‘üÒðÒ+ÿÆŒ1‹ˆÈbŒÅòÕõâXÈPöæBá’óÈì+ ЏpˆX¦3¢Y!瑚ñ#æa¿iHâJX§í!Ó*Ë"V|€»UÖK^ˆlz™·jZ1é(É)O8÷VdD|«j=u·Ühûžs£vŠšb[jk"šiÛƒ€É×Â~ ˜Ü’¶Ò¦ªJm‹ïiÁî²|r!5’nï ν­}èšìfÉ ˜VOXAyÛ;€¡¬Æ !àðõ¯]űošþ›KQošÿ’öä5§õT^tªGÖWÂ:m/¡Ò¾æŒÓZۢܩ۷(ÅÄ™\å]»êyÇ•M5ës)ÊSl›h¿ÄþÀð 'aAÀÃý1†¼»äc¯ƒ­ä.œü´½’€?(ª–½Šú—cÄþ1#ø{çû›(òÇq&™Þ#Ÿ6ßD‘H»©„ N4†DДJ´†{öý#šþý÷™ÛÛÞöv·î•}¿êÝRæWí¾úžqë¦oÍC|ÿðküü CÀgy;Ânõ{—÷ÃîÏÆû\ý.Žwþ"Êö±ö¦óG¼ú€_"àçg>×ûÿ'ßù¯MoGø}±ß=œIÀ·êaüù\û†€Ï$`ø}|{{§Æóse_ŠÃ0 À øNH?ÿ©ý CÀ0|°€ïªlÜÝ‘}ÿÿLÇOO0 À‡'`õ0în«øûôôCÀ0|°€«5hãééùN¦ß§‡‡/0 À‡'`½üô¤Ò/ CÀp¦¬^ýðPÙ÷ËÍ  CÀp†,— ¾(ÿ>ï!`€ð]µ lÜÜø?"àÈ|óÒÁÿ8£á/pp8¯€»4Îl‡ç7 Z“€¯n›“M`¸e>¥ðã{|0ÿUÃ?YÀåQÀ_ípi¼ô«Óß…ñ–€¿V§yf«¸Ïwßê1ø$Œì÷ƒµ$`ð,WŠ7¦p"~IŽR!µA2Ub *E_§¢÷ØLÐñžîÞ™H6,æœ/Ø^~²e,âG˜¡Ÿ‹ÜW»\â2¶ÍE6µ¨^q s«µeĦ0«Åá¹l-ÉäÑ”ùN&ò»r»ÌÆR`%.<›î ¶¦ª8E\'àcÅM¹y,Ô(SêÂL@8þÉi6J3‘ŽXA y@'ŠÙ±Ÿ~­f©£ öÆ…0džü<ÉUì—B½äòȧ‘è Çbõµ›UÓNE6.ÙqdÌp Q¸=yBˆÀbŒm êÌQÀà ˂={cbÅÌËD°lb›DÈò4O#j4¨ï«{ `ð9ð-p ¼íJR-àw£0ç!Ûpg²Ä^ øÀ‡ŒÙb£n°Äê:ÌÜFÀNê[¼c»ÅÒî%É(Šé¤Åsß2Ç:V¼´,+&ù<Ž|'—žÌ'þœ¯•€]q°«6Â`C‘E¤Á…ëÈ¥^¬¤]<’ž‰0˜äòy±ZvºâƒÈ+jW7å’`´TOm/YÚ‘Öþ”ê¬OÛ™EžèJõ_‘¯ý,MJÄE©ê^e—ÖV¸¤Áô°VgHÕÌ+æÔí¬¤‘è ÇbõÁž/ºÕt'‹uL®Gg«SŒÙN8k?5mõ%èä•€<Œò¼|kb³Mo]MC#¾±|ºBód„|}Qß×ô|nÏÔ²|)¼cŽÝw!à¶ øê¶úwÀHÀíp…ö¤'M3SîÈEÌŽ°‘eB¡2–%^#`õ +ÈÆ&c•s‡<1“Ÿ…Ò‡ÇaYRI¶UÊ Kª7”VKwUÀõ´AwÜga:ÝPQ¹ë3Ö§’×t’yJ,¬+WVeÞ ›±-·IoŒ•æQÀÇŠOÊu«ÑÊÏÜŒî’-Ö§Õº°1²Yɼ±°¤Lë~Æ¥Ód¬ .¹ì§+zW5®¦t5&iÏ9u¡.Vؼ^‚võrC=2µnEl,{³ã—ÌËäWîðˆ±u°kbåªÄŒÛuC A½újH¾;ý‚Œñdo‰€W3¢p8°C¾‡€“ŒÜ6Ç#I®œNÕ9)à Æ.øV½¦ÉËå‚­¾Á™ê?k¯„;èI3“€c.“¢—1‹OQ %Aö´€Ë4±e˜4é>¢zeúÌ•¶Fjgt«Î0f.HƒñŠHk“\–~$ɹ¬,6ZÀ^Aç¤óäšê„ïõ2qP ¸ª¸)W]ÑûÄzA= šÓ½¤˜ù¥Íš%çÀVsµ~­ú9¤Éˆ¤U—ý ydÕ[´Û¬\ò¥Ø1s£ž«N,ot³fd„]vÜ„§À®úU ÄBß÷ÖĺjÖuC‘È7»êg )à“ûB<ÙÛ àþŒOèÿôð²ÄU xÃ9Ÿ@À¿‰€ojCÀ­ð‹=à\jv¨lÈ„¦¼æk[ ô yðJÀå"åYl(»j§s,˜ÅÒ–J¨ZÀ±ðc¾¹kÉT¯TÙ†´5áBRiÎ X2˜lµb‘p‡¥!¼ÈŒeD)–y1êQ'±¦ZÀ¡ôÑ1@7¿,W'`=y½>½›Ü¼$+?.y—Ôoš~²iÀâªäD5D¾WÃS yæ,=ìh¬JÀƒ“bõÁ‰€Z«ÍÈ:SÁçK–«9àîß°ò¥žÐžØÓ†X´Êxº®üò>ðyܯ>ì˜o¢(ÚÙ¾š˜JÀû9ïüÑ€€[)àz {À-°JÀÑk³|<©vA™ãœØÒw/bñ:¿ö„/f²èÔQYW ¸«ƒ¨JÀa$驚Â4h‹¡¹¥F iyÒÔR ܹJv¥ªÈ;pçUÖ¿*÷•|úÂïÈ#‡ys’i'Ù“¹ê~²‰¸Èõîtm/e®u–l\—½­Éj×ÅêƒO´N›‘1f‡EÀœTMÁ^÷o}LÀ¼|€_Lì‹|©~‚)¯ÓÂøJ†€?³€ûµ`cµc4µþ‡=&gNß廿zn¡€å/‚¾ººªÞŒ{ÀmðÜdŒÍþ&àÇd~ÜBË×CZª!Ÿt·cÌYîOÿ.`;I•JR™ï%à5 b©­ !E´\Vy”ÏrªdÁ‡,Δ|äÕÀ+JÀ*6v³¬ðNîõ‹°ªŠ_•«°Ú¦6šÓ–œ‚RXh’L/ø"9é'+‹X\Tßr–*0Ntæ®’¤íy€ëbõÁ‰€å<¦i3²‰\1ŽMŠêr£’­äð¢Þ1åÑ[›«œíxÀqWÁ.× -»ðáš+we¦Ã×b,Ø/á§_[îÔZ‹Ÿ.ØúÔµòÇ/¤ö›Zí`9ù¶{ùöã²ü€ÊOࢣyŽÓwÚwš·\…îh}ƒ'9À×/òO´N,¿L„ío޹_â¾dY-®¬Øûçzsýè‡l#¡.&à@o4pj–®¬¦½Ãé8‰~Ù#ÀI_Ñ5Ǩ,&o‹3jL17h]ÕãùÊrÒ8ßðà¿o~N^€en«WÕj•'Ýrì®ßü‚¡;‰ûÇú[®WŠ©qÓáùÞZ˜à¿ ÀÕª ðÕ€“8'Óú±8y¾y¹ `8±r®øá?¼Þ,y~yy¹æ9`ˆ%À9™Öý#ÀÉ ðþuÀÿ‹¬6H`8M€?Š':ÀÕ°¿LÀ@€qºWõ­V«þ¼ÖèÃýý‡°’<W¯öù½"ÀpíÙ?ìïhÖfq%}®ÿ`8E[åiÑß„OÀû­Ðì„@0àdf –3pº_ïþXhõ—p² øˆp²_UÃ)˜'ÄC÷aèq¼éæ‚£so϶{dQ@Üðprø»çèЫ?oßž¯PdY@Üft‚ä6b#‹»}Þ<>ïÄÃfóx×Ý<äå{2È»çÍNMÀEùþƒ:Ëæåq˜°b÷$žvB<>‰mwçË´îT€}±ÙŠGÙ\yD÷Iäº[à'už‚¼íæd€}yà9'ß%Àpâß° :Q6"¬­Ú ú1Ü :h²xêîÔ^Ð;µô&Ü :ì½c/hˆc¾!ÀI†6Õqp"s/Äàk5ßTÕ_õç†ÿº9–Ä>ßÈüV÷&Àð§|ø†ÿeØò ç0Wo0<À×û瀙€8Y€¯Ï3pº ˜C€¯Õ_ 0Éð5àÔ¾f Ž ˜@€ À€@€€ €`0~›‘ 1F<¦`|šþŽ|þš`|ý’T`ƒe@€ñYÌ"X¥A€ÁÚ €Uš`ÖV¬Ò À`mÀ*M€ 0k+Viœ(À_ 0k+Vi0ƒµ«4k+Viü©%À¬­X¥Á ÖV¬ÒÉðWÌÚ €ULÀˆm-eÓt–éO¾ŽSù´~4÷t½þY/­oök 85Í<Æo0Ïࣥ¨˜Þ}¥]®›ÿú9*Ö9,‚NÞw~|ÊóîÏ;ñ^øaYþWþ‡Ù;¿{__×õþí°¼B¸â–Õõ,Æo€_ð_`ÝÑ›u µÔ£¬6Õ†¿ð9Ò3!·±8•w„¨Ô‡á7¥º6ý<6Sú@¿,pª>u/ן#À¦>ÐͨÃõ¥öηe½Tø¶=úîŠÿÎôïp93Þz_t†ÂÉ„fï˜ç€ ðÛ¢¿¥wxˆ±Ü_ °ÿæÓYÇk áäSAK‹éìŸg€ý0À"åDîh [›DgðÏ9À²¿=êpPWwúN€K™Rtž ðÛ;“¿j³£ëºÝGÊ–¥.SÛ œ¿½]Ú···Œ_™€ 0èúáݯ/ÃYÍ;¸âÙæLˆ™i{K_Üõ‡«Õô,yF³Ù±¼u0h¨MÐõŒÝO ­àÌqEL×£9Xˆ¬U{à;{\dÏeÏ4åqý;ÏRsÌÒ²;51wî-kã y˜{£OÝ¢¸Ôfá°´ ÑØêYà‚\Ð[!ÒŽ­.»:eТֱ­~IÄp3x²Nžì° ñ>XN9 p©o[Ô¼Ô»³–á­Ô¦‘²3v/¸bÁ&èðò²B™¢º3ÉkcYýâþºÇüý…j¯® Uá0À¦×îWa€ƒ$ŸÏ*ó0Ïà<Äèºh†ßâ—.¼»}€Gv¹Ø³Ó#»—2¬¦˜Û3ùï^Ì.ÓÂôF~Ýš‰{3xØ®ˆž—÷³VlãŠ<ø·ƒ9Xˆ‚ÇÛËb%Ó¦7Kßf*©Y¦-¯FKC´¬vºoйÕ,52ñ=æEÛ7 û×´…š‚ïÕá†=¥Vۯط*Àóú¨pïù¢_ÏŽXۆߖ•2ïJéN?Ž›¡)ËÛ :<ðöWC´¢뵚9C³Vº³ …L§$G.üRxÅT€÷7Ð>ÀêÎä{ýR¾î„g‰yåjëm5åï¾w„ð­Và,ÆÇ'àÿ$ÝÌêÜIEND®B`‚waybar-0.9.9/protocol/000077500000000000000000000000001416677246500147015ustar00rootroot00000000000000waybar-0.9.9/protocol/dbus-menu.xml000066400000000000000000000050701416677246500173240ustar00rootroot00000000000000 waybar-0.9.9/protocol/dbus-status-notifier-item.xml000066400000000000000000000040431416677246500224530ustar00rootroot00000000000000 waybar-0.9.9/protocol/dbus-status-notifier-watcher.xml000066400000000000000000000035141416677246500231540ustar00rootroot00000000000000 waybar-0.9.9/protocol/ext-workspace-unstable-v1.xml000066400000000000000000000332701416677246500223630ustar00rootroot00000000000000 Copyright © 2019 Christopher Billington Copyright © 2020 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. Workspaces, also called virtual desktops, are groups of surfaces. A compositor with a concept of workspaces may only show some such groups of surfaces (those of 'active' workspaces) at a time. 'Activating' a workspace is a request for the compositor to display that workspace's surfaces as normal, whereas the compositor may hide or otherwise de-emphasise surfaces that are associated only with 'inactive' workspaces. Workspaces are grouped by which sets of outputs they correspond to, and may contain surfaces only from those outputs. In this way, it is possible for each output to have its own set of workspaces, or for all outputs (or any other arbitrary grouping) to share workspaces. Compositors may optionally conceptually arrange each group of workspaces in an N-dimensional grid. The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of workspaces and their properties, and allowing them to activate and deactivate workspaces. After a client binds the zext_workspace_manager_v1, each workspace will be sent via the workspace event. This event is emitted whenever a new workspace group has been created. All initial details of the workspace group (workspaces, outputs) will be sent immediately after this event via the corresponding events in zext_workspace_group_handle_v1. The client must send this request after it has finished sending other requests. The compositor must process a series of requests preceding a commit request atomically. This allows changes to the workspace properties to be seen as atomic, even if they happen via multiple events, and even if they involve multiple zext_workspace_handle_v1 objects, for example, deactivating one workspace and activating another. This event is sent after all changes in all workspace groups have been sent. This allows changes to one or more zext_workspace_group_handle_v1 properties to be seen as atomic, even if they happen via multiple events. In particular, an output moving from one workspace group to another sends an output_enter event and an output_leave event to the two zext_workspace_group_handle_v1 objects in question. The compositor sends the done event only after updating the output information in both workspace groups. This event indicates that the compositor is done sending events to the zext_workspace_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. Indicates the client no longer wishes to receive events for new workspace groups. However the compositor may emit further workspace events, until the finished event is emitted. The client must not send any more requests after this one. A zext_workspace_group_handle_v1 object represents a a workspace group that is assigned a set of outputs and contains a number of workspaces. The set of outputs assigned to the workspace group is conveyed to the client via output_enter and output_leave events, and its workspaces are conveyed with workspace events. For example, a compositor which has a set of workspaces for each output may advertise a workspace group (and its workspaces) per output, whereas a compositor where a workspace spans all outputs may advertise a single workspace group for all outputs. This event is emitted whenever an output is assigned to the workspace group. This event is emitted whenever an output is removed from the workspace group. This event is emitted whenever a new workspace has been created. All initial details of the workspace (name, coordinates, state) will be sent immediately after this event via the corresponding events in zext_workspace_handle_v1. This event means the zext_workspace_group_handle_v1 has been destroyed. It is guaranteed there won't be any more events for this zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes inert so any requests will be ignored except the destroy request. The compositor must remove all workspaces belonging to a workspace group before removing the workspace group. Request that the compositor create a new workspace with the given name. There is no guarantee that the compositor will create a new workspace, or that the created workspace will have the provided name. Destroys the zext_workspace_handle_v1 object. This request should be called either when the client does not want to use the workspace object any more or after the remove event to finalize the destruction of the object. A zext_workspace_handle_v1 object represents a a workspace that handles a group of surfaces. Each workspace has a name, conveyed to the client with the name event; a list of states, conveyed to the client with the state event; and optionally a set of coordinates, conveyed to the client with the coordinates event. The client may request that the compositor activate or deactivate the workspace. Each workspace can belong to only a single workspace group. Depepending on the compositor policy, there might be workspaces with the same name in different workspace groups, but these workspaces are still separate (e.g. one of them might be active while the other is not). This event is emitted immediately after the zext_workspace_handle_v1 is created and whenever the name of the workspace changes. This event is used to organize workspaces into an N-dimensional grid within a workspace group, and if supported, is emitted immediately after the zext_workspace_handle_v1 is created and whenever the coordinates of the workspace change. Compositors may not send this event if they do not conceptually arrange workspaces in this way. If compositors simply number workspaces, without any geometric interpretation, they may send 1D coordinates, which clients should not interpret as implying any geometry. Sending an empty array means that the compositor no longer orders the workspace geometrically. Coordinates have an arbitrary number of dimensions N with an uint32 position along each dimension. By convention if N > 1, the first dimension is X, the second Y, the third Z, and so on. The compositor may chose to utilize these events for a more novel workspace layout convention, however. No guarantee is made about the grid being filled or bounded; there may be a workspace at coordinate 1 and another at coordinate 1000 and none in between. Within a workspace group, however, workspaces must have unique coordinates of equal dimensionality. This event is emitted immediately after the zext_workspace_handle_v1 is created and each time the workspace state changes, either because of a compositor action or because of a request in this protocol. The different states that a workspace can have. The workspace is not visible in its workspace group, and clients attempting to visualize the compositor workspace state should not display such workspaces. This event means the zext_workspace_handle_v1 has been destroyed. It is guaranteed there won't be any more events for this zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so any requests will be ignored except the destroy request. Destroys the zext_workspace_handle_v1 object. This request should be called either when the client does not want to use the workspace object any more or after the remove event to finalize the destruction of the object. Request that this workspace be activated. There is no guarantee the workspace will be actually activated, and behaviour may be compositor-dependent. For example, activating a workspace may or may not deactivate all other workspaces in the same group. Request that this workspace be deactivated. There is no guarantee the workspace will be actually deactivated. Request that this workspace be removed. There is no guarantee the workspace will be actually removed. waybar-0.9.9/protocol/meson.build000066400000000000000000000070221416677246500170440ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') # should check wayland_scanner's version, but it is hard to get if wayland_client.version().version_compare('>=1.14.91') code_type = 'private-code' else code_type = 'code' endif wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: [code_type, '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ] client_protos_src = [] client_protos_headers = [] foreach p : client_protocols xml = join_paths(p) client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach gdbus_codegen = find_program('gdbus-codegen') r = run_command(gdbus_codegen, '--body', '--output', '/dev/null') if r.returncode() != 0 gdbus_code_dsnw = custom_target( 'dbus-status-notifier-watcher.[ch]', output: ['@BASENAME@.c','@BASENAME@.h'], input: './dbus-status-notifier-watcher.xml', command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'], ) gdbus_code_dsni = custom_target( 'dbus-status-notifier-item.[ch]', output: ['@BASENAME@.c','@BASENAME@.h'], input: './dbus-status-notifier-item.xml', command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'], ) gdbus_code_dm = custom_target( 'dbus-menu.[ch]', output: ['@BASENAME@.c','@BASENAME@.h'], input: './dbus-menu.xml', command: [gdbus_codegen,'--c-namespace', 'Sn', '--generate-c-code', 'protocol/@BASENAME@', '@INPUT@'], ) client_protos_src += gdbus_code_dsnw[0] client_protos_headers += gdbus_code_dsnw[1] client_protos_src += gdbus_code_dsni[0] client_protos_headers += gdbus_code_dsni[1] client_protos_src += gdbus_code_dm[0] client_protos_headers += gdbus_code_dm[1] else gdbus_code = generator( gdbus_codegen, output: '@BASENAME@.c', arguments: ['--c-namespace', 'Sn', '--body', '--output', '@OUTPUT@', '@INPUT@'] ) gdbus_header = generator( gdbus_codegen, output: '@BASENAME@.h', arguments: ['--c-namespace', 'Sn', '--header', '--output', '@OUTPUT@', '@INPUT@'] ) client_protos_src += gdbus_code.process('./dbus-status-notifier-watcher.xml') client_protos_headers += gdbus_header.process('./dbus-status-notifier-watcher.xml') client_protos_src += gdbus_code.process('./dbus-status-notifier-item.xml') client_protos_headers += gdbus_header.process('./dbus-status-notifier-item.xml') client_protos_src += gdbus_code.process('./dbus-menu.xml') client_protos_headers += gdbus_header.process('./dbus-menu.xml') endif lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [wayland_client, gtkmm, giounix], include_directories: include_directories('..'), ) # for the include directory client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) waybar-0.9.9/protocol/river-control-unstable-v1.xml000066400000000000000000000071371416677246500223770ustar00rootroot00000000000000 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. This interface allows clients to run compositor commands and receive a success/failure response with output or a failure message respectively. Each command is built up in a series of add_argument requests and executed with a run_command request. The first argument is the command to be run. A complete list of commands should be made available in the man page of the compositor. This request indicates that the client will not use the river_control object any more. Objects that have been created through this instance are not affected. Arguments are stored by the server in the order they were sent until the run_command request is made. Execute the command built up using the add_argument request for the given seat. This object is created by the run_command request. Exactly one of the success or failure events will be sent. This object will be destroyed by the compositor after one of the events is sent. Sent when the command has been successfully received and executed by the compositor. Some commands may produce output, in which case the output argument will be a non-empty string. Sent when the command could not be carried out. This could be due to sending a non-existent command, no command, not enough arguments, too many arguments, invalid arguments, etc. waybar-0.9.9/protocol/river-status-unstable-v1.xml000066400000000000000000000121701416677246500222330ustar00rootroot00000000000000 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. 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. waybar-0.9.9/protocol/wlr-foreign-toplevel-management-unstable-v1.xml000066400000000000000000000264221416677246500257650ustar00rootroot00000000000000 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. waybar-0.9.9/protocol/wlr-layer-shell-unstable-v1.xml000066400000000000000000000340471416677246500226150ustar00rootroot00000000000000 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. 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. 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. Set to 1 to request that the seat send keyboard events to this layer surface. For layers below the shell surface layer, the seat will use normal focus semantics. For layers above the shell surface layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to true. 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. Events 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. waybar-0.9.9/resources/000077500000000000000000000000001416677246500150525ustar00rootroot00000000000000waybar-0.9.9/resources/config000066400000000000000000000123451416677246500162470ustar00rootroot00000000000000{ // "layer": "top", // Waybar at top layer // "position": "bottom", // Waybar position (top|bottom|left|right) "height": 30, // Waybar height (to be removed for auto height) // "width": 1280, // Waybar width "spacing": 4, // Gaps between modules (4px) // Choose the order of the modules "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-center": ["sway/window"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"], // Modules configuration // "sway/workspaces": { // "disable-scroll": true, // "all-outputs": true, // "format": "{name}: {icon}", // "format-icons": { // "1": "ï„ ", // "2": "", // "3": "ï„¡", // "4": "", // "5": "ïµ", // "urgent": "ïª", // "focused": "", // "default": "ï„‘" // } // }, "keyboard-state": { "numlock": true, "capslock": true, "format": "{name} {icon}", "format-icons": { "locked": "", "unlocked": "" } }, "sway/mode": { "format": "{}" }, "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ï€", "format-disconnected": "Disconnected ï€", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ï€", "unknown-tag": "N/A", "interval": 2, "consume-icons": { "on": " " }, "random-icons": { "off": "ï´ ", "on": "ï´ " }, "repeat-icons": { "on": " " }, "single-icons": { "on": "1 " }, "state-icons": { "paused": "ïŒ", "playing": "ï‹" }, "tooltip-format": "MPD (connected)", "tooltip-format-disconnected": "MPD (disconnected)" }, "idle_inhibitor": { "format": "{icon}", "format-icons": { "activated": "ï®", "deactivated": "ï°" } }, "tray": { // "icon-size": 21, "spacing": 10 }, "clock": { // "timezone": "America/New_York", "tooltip-format": "{:%Y %B}\n{calendar}", "format-alt": "{:%Y-%m-%d}" }, "cpu": { "format": "{usage}% ï‹›", "tooltip": false }, "memory": { "format": "{}% " }, "temperature": { // "thermal-zone": 2, // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", "critical-threshold": 80, // "format-critical": "{temperatureC}°C {icon}", "format": "{temperatureC}°C {icon}", "format-icons": ["ï«", "", "ï©"] }, "backlight": { // "device": "acpi_video1", "format": "{percent}% {icon}", "format-icons": ["", "ï„‘"] }, "battery": { "states": { // "good": 95, "warning": 30, "critical": 15 }, "format": "{capacity}% {icon}", "format-charging": "{capacity}% ï—§", "format-plugged": "{capacity}% ", "format-alt": "{time} {icon}", // "format-good": "", // An empty format will hide the module // "format-full": "", "format-icons": ["", "", "", "ï‰", ""] }, "battery#bat2": { "bat": "BAT2" }, "network": { // "interface": "wlp2*", // (Optional) To force the use of this interface "format-wifi": "{essid} ({signalStrength}%) ", "format-ethernet": "{ipaddr}/{cidr} ïž–", "tooltip-format": "{ifname} via {gwaddr} ïž–", "format-linked": "{ifname} (No IP) ïž–", "format-disconnected": "Disconnected âš ", "format-alt": "{ifname}: {ipaddr}/{cidr}" }, "pulseaudio": { // "scroll-step": 1, // %, can be a float "format": "{volume}% {icon} {format_source}", "format-bluetooth": "{volume}% {icon} {format_source}", "format-bluetooth-muted": "ïš© {icon} {format_source}", "format-muted": "ïš© {format_source}", "format-source": "{volume}% ï„°", "format-source-muted": "", "format-icons": { "headphone": "", "hands-free": "ï–", "headset": "ï–", "phone": "ï‚•", "portable": "ï‚•", "car": "", "default": ["", "", ""] }, "on-click": "pavucontrol" }, "custom/media": { "format": "{icon} {}", "return-type": "json", "max-length": 40, "format-icons": { "spotify": "", "default": "🎜" }, "escape": true, "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name } } waybar-0.9.9/resources/custom_modules/000077500000000000000000000000001416677246500201145ustar00rootroot00000000000000waybar-0.9.9/resources/custom_modules/mediaplayer.py000077500000000000000000000074161416677246500227750ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse import logging import sys import signal import gi import json gi.require_version('Playerctl', '2.0') from gi.repository import Playerctl, GLib logger = logging.getLogger(__name__) def write_output(text, player): logger.info('Writing output') output = {'text': text, 'class': 'custom-' + player.props.player_name, 'alt': player.props.player_name} sys.stdout.write(json.dumps(output) + '\n') sys.stdout.flush() def on_play(player, status, manager): logger.info('Received new playback status') on_metadata(player, player.props.metadata, manager) def on_metadata(player, metadata, manager): logger.info('Received new metadata') track_info = '' if player.props.player_name == 'spotify' and \ 'mpris:trackid' in metadata.keys() and \ ':ad:' in player.props.metadata['mpris:trackid']: track_info = 'AD PLAYING' elif player.get_artist() != '' and player.get_title() != '': track_info = '{artist} - {title}'.format(artist=player.get_artist(), title=player.get_title()) else: track_info = player.get_title() if player.props.status != 'Playing' and track_info: track_info = ' ' + track_info write_output(track_info, player) def on_player_appeared(manager, player, selected_player=None): if player is not None and (selected_player is None or player.name == selected_player): init_player(manager, player) else: logger.debug("New player appeared, but it's not the selected player, skipping") def on_player_vanished(manager, player): logger.info('Player has vanished') sys.stdout.write('\n') sys.stdout.flush() def init_player(manager, name): logger.debug('Initialize player: {player}'.format(player=name.name)) player = Playerctl.Player.new_from_name(name) player.connect('playback-status', on_play, manager) player.connect('metadata', on_metadata, manager) manager.manage_player(player) on_metadata(player, player.props.metadata, manager) def signal_handler(sig, frame): logger.debug('Received signal to stop, exiting') sys.stdout.write('\n') sys.stdout.flush() # loop.quit() sys.exit(0) def parse_arguments(): parser = argparse.ArgumentParser() # Increase verbosity with every occurrence of -v parser.add_argument('-v', '--verbose', action='count', default=0) # Define for which player we're listening parser.add_argument('--player') return parser.parse_args() def main(): arguments = parse_arguments() # Initialize logging logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, format='%(name)s %(levelname)s %(message)s') # Logging is set by default to WARN and higher. # With every occurrence of -v it's lowered by one logger.setLevel(max((3 - arguments.verbose) * 10, 0)) # Log the sent command line arguments logger.debug('Arguments received {}'.format(vars(arguments))) manager = Playerctl.PlayerManager() loop = GLib.MainLoop() manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player)) manager.connect('player-vanished', on_player_vanished) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGPIPE, signal.SIG_DFL) for player in manager.props.player_names: if arguments.player is not None and arguments.player != player.name: logger.debug('{player} is not the filtered player, skipping it' .format(player=player.name) ) continue init_player(manager, player) loop.run() if __name__ == '__main__': main() waybar-0.9.9/resources/style.css000066400000000000000000000101311416677246500167200ustar00rootroot00000000000000* { /* `otf-font-awesome` is required to be installed for icons */ font-family: Roboto, Helvetica, Arial, sans-serif; font-size: 13px; } window#waybar { background-color: rgba(43, 48, 59, 0.5); border-bottom: 3px solid rgba(100, 114, 125, 0.5); color: #ffffff; transition-property: background-color; transition-duration: .5s; } window#waybar.hidden { opacity: 0.2; } /* window#waybar.empty { background-color: transparent; } window#waybar.solo { background-color: #FFFFFF; } */ window#waybar.termite { background-color: #3F3F3F; } window#waybar.chromium { background-color: #000000; border: none; } #workspaces button { padding: 0 5px; background-color: transparent; color: #ffffff; /* Use box-shadow instead of border so the text isn't offset */ box-shadow: inset 0 -3px transparent; /* Avoid rounded borders under each workspace name */ border: none; border-radius: 0; } /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ #workspaces button:hover { background: rgba(0, 0, 0, 0.2); box-shadow: inset 0 -3px #ffffff; } #workspaces button.focused { background-color: #64727D; box-shadow: inset 0 -3px #ffffff; } #workspaces button.urgent { background-color: #eb4d4b; } #mode { background-color: #64727D; border-bottom: 3px solid #ffffff; } #clock, #battery, #cpu, #memory, #disk, #temperature, #backlight, #network, #pulseaudio, #custom-media, #tray, #mode, #idle_inhibitor, #mpd { padding: 0 10px; color: #ffffff; } #window, #workspaces { margin: 0 4px; } /* If workspaces is the leftmost module, omit left margin */ .modules-left > widget:first-child > #workspaces { margin-left: 0; } /* If workspaces is the rightmost module, omit right margin */ .modules-right > widget:last-child > #workspaces { margin-right: 0; } #clock { background-color: #64727D; } #battery { background-color: #ffffff; color: #000000; } #battery.charging, #battery.plugged { color: #ffffff; background-color: #26A65B; } @keyframes blink { to { background-color: #ffffff; color: #000000; } } #battery.critical:not(.charging) { background-color: #f53c3c; color: #ffffff; animation-name: blink; animation-duration: 0.5s; animation-timing-function: linear; animation-iteration-count: infinite; animation-direction: alternate; } label:focus { background-color: #000000; } #cpu { background-color: #2ecc71; color: #000000; } #memory { background-color: #9b59b6; } #disk { background-color: #964B00; } #backlight { background-color: #90b1b1; } #network { background-color: #2980b9; } #network.disconnected { background-color: #f53c3c; } #pulseaudio { background-color: #f1c40f; color: #000000; } #pulseaudio.muted { background-color: #90b1b1; color: #2a5c45; } #custom-media { background-color: #66cc99; color: #2a5c45; min-width: 100px; } #custom-media.custom-spotify { background-color: #66cc99; } #custom-media.custom-vlc { background-color: #ffa000; } #temperature { background-color: #f0932b; } #temperature.critical { background-color: #eb4d4b; } #tray { background-color: #2980b9; } #tray > .passive { -gtk-icon-effect: dim; } #tray > .needs-attention { -gtk-icon-effect: highlight; background-color: #eb4d4b; } #idle_inhibitor { background-color: #2d3436; } #idle_inhibitor.activated { background-color: #ecf0f1; color: #2d3436; } #mpd { background-color: #66cc99; color: #2a5c45; } #mpd.disconnected { background-color: #f53c3c; } #mpd.stopped { background-color: #90b1b1; } #mpd.paused { background-color: #51a37a; } #language { background: #00b093; color: #740864; padding: 0 5px; margin: 0 5px; min-width: 16px; } #keyboard-state { background: #97e1ad; color: #000000; padding: 0 0px; margin: 0 5px; min-width: 16px; } #keyboard-state > label { padding: 0 5px; } #keyboard-state > label.locked { background: rgba(0, 0, 0, 0.2); } waybar-0.9.9/resources/waybar.service.in000066400000000000000000000005761416677246500203360ustar00rootroot00000000000000[Unit] Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. Documentation=https://github.com/Alexays/Waybar/wiki/ PartOf=graphical-session.target After=graphical-session.target Requisite=graphical-session.target [Service] ExecStart=@prefix@/bin/waybar ExecReload=kill -SIGUSR2 $MAINPID Restart=on-failure [Install] WantedBy=graphical-session.target waybar-0.9.9/src/000077500000000000000000000000001416677246500136275ustar00rootroot00000000000000waybar-0.9.9/src/ALabel.cpp000066400000000000000000000107321416677246500154560ustar00rootroot00000000000000#include "ALabel.hpp" #include #include namespace waybar { ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id, const std::string& format, uint16_t interval, bool ellipsize, bool enable_click, bool enable_scroll) : AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll), format_(config_["format"].isString() ? config_["format"].asString() : format), interval_(config_["interval"] == "once" ? std::chrono::seconds(100000000) : std::chrono::seconds( config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)), default_format_(format_) { label_.set_name(name); if (!id.empty()) { label_.get_style_context()->add_class(id); } event_box_.add(label_); if (config_["max-length"].isUInt()) { label_.set_max_width_chars(config_["max-length"].asInt()); label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_single_line_mode(true); } else if (ellipsize && label_.get_max_width_chars() == -1) { label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END); label_.set_single_line_mode(true); } if (config_["min-length"].isUInt()) { label_.set_width_chars(config_["min-length"].asUInt()); } uint rotate = 0; if (config_["rotate"].isUInt()) { rotate = config["rotate"].asUInt(); label_.set_angle(rotate); } if (config_["align"].isDouble()) { auto align = config_["align"].asFloat(); if (rotate == 90 || rotate == 270) { label_.set_yalign(align); } else { label_.set_xalign(align); } } } auto ALabel::update() -> void { AModule::update(); } std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) { auto format_icons = config_["format-icons"]; if (format_icons.isObject()) { if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { format_icons = format_icons[alt]; } else { format_icons = format_icons["default"]; } } if (format_icons.isArray()) { auto size = format_icons.size(); if (size) { auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); format_icons = format_icons[idx]; } } if (format_icons.isString()) { return format_icons.asString(); } return ""; } std::string ALabel::getIcon(uint16_t percentage, const std::vector& alts, uint16_t max) { auto format_icons = config_["format-icons"]; if (format_icons.isObject()) { std::string _alt = "default"; for (const auto& alt : alts) { if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { _alt = alt; break; } } format_icons = format_icons[_alt]; } if (format_icons.isArray()) { auto size = format_icons.size(); if (size) { auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); format_icons = format_icons[idx]; } } if (format_icons.isString()) { return format_icons.asString(); } return ""; } bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) { alt_ = !alt_; if (alt_ && config_["format-alt"].isString()) { format_ = config_["format-alt"].asString(); } else { format_ = default_format_; } } return AModule::handleToggle(e); } std::string ALabel::getState(uint8_t value, bool lesser) { if (!config_["states"].isObject()) { return ""; } // Get current state std::vector> states; if (config_["states"].isObject()) { for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) { if (it->isUInt() && it.key().isString()) { states.emplace_back(it.key().asString(), it->asUInt()); } } } // Sort states std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) { return lesser ? a.second < b.second : a.second > b.second; }); std::string valid_state; for (auto const& state : states) { if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) { label_.get_style_context()->add_class(state.first); valid_state = state.first; } else { label_.get_style_context()->remove_class(state.first); } } return valid_state; } } // namespace waybar waybar-0.9.9/src/AModule.cpp000066400000000000000000000100161416677246500156570ustar00rootroot00000000000000#include "AModule.hpp" #include #include namespace waybar { AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id, bool enable_click, bool enable_scroll) : name_(std::move(name)), config_(std::move(config)) { // configure events' user commands if (config_["on-click"].isString() || config_["on-click-middle"].isString() || config_["on-click-backward"].isString() || config_["on-click-forward"].isString() || config_["on-click-right"].isString() || enable_click) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &AModule::handleToggle)); } if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString() || enable_scroll) { event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &AModule::handleScroll)); } } AModule::~AModule() { for (const auto& pid : pid_) { if (pid != -1) { killpg(pid, SIGTERM); } } } auto AModule::update() -> void { // Run user-provided update handler if configured if (config_["on-update"].isString()) { pid_.push_back(util::command::forkExec(config_["on-update"].asString())); } } bool AModule::handleToggle(GdkEventButton* const& e) { std::string format; if (config_["on-click"].isString() && e->button == 1) { format = config_["on-click"].asString(); } else if (config_["on-click-middle"].isString() && e->button == 2) { format = config_["on-click-middle"].asString(); } else if (config_["on-click-right"].isString() && e->button == 3) { format = config_["on-click-right"].asString(); } else if (config_["on-click-backward"].isString() && e->button == 8) { format = config_["on-click-backward"].asString(); } else if (config_["on-click-forward"].isString() && e->button == 9) { format = config_["on-click-forward"].asString(); } if (!format.empty()) { pid_.push_back(util::command::forkExec(format)); } dp.emit(); return true; } AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) { switch (e -> direction) { case GDK_SCROLL_UP: return SCROLL_DIR::UP; case GDK_SCROLL_DOWN: return SCROLL_DIR::DOWN; case GDK_SCROLL_LEFT: return SCROLL_DIR::LEFT; case GDK_SCROLL_RIGHT: return SCROLL_DIR::RIGHT; case GDK_SCROLL_SMOOTH: { SCROLL_DIR dir{SCROLL_DIR::NONE}; distance_scrolled_y_ += e->delta_y; distance_scrolled_x_ += e->delta_x; gdouble threshold = 0; if (config_["smooth-scrolling-threshold"].isNumeric()) { threshold = config_["smooth-scrolling-threshold"].asDouble(); } if (distance_scrolled_y_ < -threshold) { dir = SCROLL_DIR::UP; } else if (distance_scrolled_y_ > threshold) { dir = SCROLL_DIR::DOWN; } else if (distance_scrolled_x_ > threshold) { dir = SCROLL_DIR::RIGHT; } else if (distance_scrolled_x_ < -threshold) { dir = SCROLL_DIR::LEFT; } switch (dir) { case SCROLL_DIR::UP: case SCROLL_DIR::DOWN: distance_scrolled_y_ = 0; break; case SCROLL_DIR::LEFT: case SCROLL_DIR::RIGHT: distance_scrolled_x_ = 0; break; case SCROLL_DIR::NONE: break; } return dir; } // Silence -Wreturn-type: default: return SCROLL_DIR::NONE; } } bool AModule::handleScroll(GdkEventScroll* e) { auto dir = getScrollDir(e); if (dir == SCROLL_DIR::UP && config_["on-scroll-up"].isString()) { pid_.push_back(util::command::forkExec(config_["on-scroll-up"].asString())); } else if (dir == SCROLL_DIR::DOWN && config_["on-scroll-down"].isString()) { pid_.push_back(util::command::forkExec(config_["on-scroll-down"].asString())); } dp.emit(); return true; } bool AModule::tooltipEnabled() { return config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true; } AModule::operator Gtk::Widget&() { return event_box_; } } // namespace waybar waybar-0.9.9/src/bar.cpp000066400000000000000000000653271416677246500151140ustar00rootroot00000000000000#ifdef HAVE_GTK_LAYER_SHELL #include #endif #include #include #include "bar.hpp" #include "client.hpp" #include "factory.hpp" #include "group.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #ifdef HAVE_SWAY #include "modules/sway/bar.hpp" #endif namespace waybar { static constexpr const char* MIN_HEIGHT_MSG = "Requested height: {} is less than the minimum height: {} required by the modules"; static constexpr const char* MIN_WIDTH_MSG = "Requested width: {} is less than the minimum width: {} required by the modules"; static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; static constexpr const char* SIZE_DEFINED = "{} size is defined in the config file so it will stay like that"; const Bar::bar_mode_map Bar::PRESET_MODES = { // {"default", {// Special mode to hold the global bar configuration .layer = bar_layer::BOTTOM, .exclusive = true, .passthrough = false, .visible = true}}, {"dock", {// Modes supported by the sway config; see man sway-bar(5) .layer = bar_layer::BOTTOM, .exclusive = true, .passthrough = false, .visible = true}}, {"hide", {// .layer = bar_layer::TOP, .exclusive = false, .passthrough = false, .visible = true}}, {"invisible", {// .layer = bar_layer::BOTTOM, .exclusive = false, .passthrough = true, .visible = false}}, {"overlay", {// .layer = bar_layer::TOP, .exclusive = false, .passthrough = true, .visible = true}}}; const std::string_view Bar::MODE_DEFAULT = "default"; const std::string_view Bar::MODE_INVISIBLE = "invisible"; const std::string_view DEFAULT_BAR_ID = "bar-0"; /* Deserializer for enum bar_layer */ void from_json(const Json::Value& j, bar_layer& l) { if (j == "bottom") { l = bar_layer::BOTTOM; } else if (j == "top") { l = bar_layer::TOP; } else if (j == "overlay") { l = bar_layer::OVERLAY; } } /* Deserializer for struct bar_mode */ void from_json(const Json::Value& j, bar_mode& m) { if (j.isObject()) { if (auto v = j["layer"]; v.isString()) { from_json(v, m.layer); } if (auto v = j["exclusive"]; v.isBool()) { m.exclusive = v.asBool(); } if (auto v = j["passthrough"]; v.isBool()) { m.passthrough = v.asBool(); } if (auto v = j["visible"]; v.isBool()) { m.visible = v.asBool(); } } } /* Deserializer for JSON Object -> map * Assumes that all the values in the object are deserializable to the same type. */ template ::value>> void from_json(const Json::Value& j, std::map& m) { if (j.isObject()) { for (auto it = j.begin(); it != j.end(); ++it) { from_json(*it, m[it.key().asString()]); } } } #ifdef HAVE_GTK_LAYER_SHELL struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { output_name_ = output.name; // this has to be executed before GtkWindow.realize gtk_layer_init_for_window(window_.gobj()); gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE); gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj()); gtk_layer_set_namespace(window_.gobj(), "waybar"); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap)); window.signal_configure_event().connect_notify( sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure)); } void setExclusiveZone(bool enable) override { if (enable) { gtk_layer_auto_exclusive_zone_enable(window_.gobj()); } else { gtk_layer_set_exclusive_zone(window_.gobj(), 0); } } void setMargins(const struct bar_margins& margins) override { gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom); } void setLayer(bar_layer value) override { auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; if (value == bar_layer::TOP) { layer = GTK_LAYER_SHELL_LAYER_TOP; } else if (value == bar_layer::OVERLAY) { layer = GTK_LAYER_SHELL_LAYER_OVERLAY; } gtk_layer_set_layer(window_.gobj(), layer); } void setPassThrough(bool enable) override { passthrough_ = enable; auto gdk_window = window_.get_window(); if (gdk_window) { Cairo::RefPtr region; if (enable) { region = Cairo::Region::create(); } gdk_window->input_shape_combine_region(region, 0, 0); } } void setPosition(const std::string_view& position) override { auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; vertical_ = false; if (position == "bottom") { unanchored = GTK_LAYER_SHELL_EDGE_TOP; } else if (position == "left") { unanchored = GTK_LAYER_SHELL_EDGE_RIGHT; vertical_ = true; } else if (position == "right") { vertical_ = true; unanchored = GTK_LAYER_SHELL_EDGE_LEFT; } for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT, GTK_LAYER_SHELL_EDGE_RIGHT, GTK_LAYER_SHELL_EDGE_TOP, GTK_LAYER_SHELL_EDGE_BOTTOM}) { gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge); } } void setSize(uint32_t width, uint32_t height) override { width_ = width; height_ = height; window_.set_size_request(width_, height_); }; private: Gtk::Window& window_; std::string output_name_; uint32_t width_; uint32_t height_; bool passthrough_ = false; bool vertical_ = false; void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); } void onConfigure(GdkEventConfigure* ev) { /* * GTK wants new size for the window. * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell * code. This event handler only updates stored size of the window and prints some warnings. * * Note: forced resizing to a window smaller than required by GTK would not work with * gtk-layer-shell. */ if (vertical_) { if (width_ > 1 && ev->width > static_cast(width_)) { spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); } } else { if (height_ > 1 && ev->height > static_cast(height_)) { spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); } } width_ = ev->width; height_ = ev->height; spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_); } }; #endif struct RawSurfaceImpl : public BarSurface, public sigc::trackable { RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); output_name_ = output.name; window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize)); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap)); window.signal_configure_event().connect_notify( sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure)); if (window.get_realized()) { onRealize(); } } void setExclusiveZone(bool enable) override { exclusive_zone_ = enable; if (layer_surface_) { auto zone = 0; if (enable) { // exclusive zone already includes margin for anchored edge, // only opposite margin should be added if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { zone += width_; zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; } else { zone += height_; zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; } } spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone); } } void setLayer(bar_layer layer) override { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; if (layer == bar_layer::TOP) { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP; } else if (layer == bar_layer::OVERLAY) { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; } // updating already mapped window if (layer_surface_) { if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >= ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_); } else { spdlog::warn("Unable to change layer: layer-shell implementation is too old"); } } } void setMargins(const struct bar_margins& margins) override { margins_ = margins; // updating already mapped window if (layer_surface_) { zwlr_layer_surface_v1_set_margin( layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left); } } void setPassThrough(bool enable) override { passthrough_ = enable; /* GTK overwrites any region changes applied directly to the wl_surface, * thus the same GTK region API as in the GLS impl has to be used. */ auto gdk_window = window_.get_window(); if (gdk_window) { Cairo::RefPtr region; if (enable) { region = Cairo::Region::create(); } gdk_window->input_shape_combine_region(region, 0, 0); } } void setPosition(const std::string_view& position) override { anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; if (position == "bottom") { anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else if (position == "left") { anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; } else if (position == "right") { anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; } // updating already mapped window if (layer_surface_) { zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); } } void setSize(uint32_t width, uint32_t height) override { configured_width_ = width_ = width; configured_height_ = height_ = height; // layer_shell.configure handler should update exclusive zone if size changes window_.set_size_request(width, height); }; void commit() override { if (surface_) { wl_surface_commit(surface_); } } private: constexpr static uint8_t VERTICAL_ANCHOR = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; constexpr static uint8_t HORIZONTAL_ANCHOR = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; template using deleter_fn = std::integral_constant; using layer_surface_ptr = std::unique_ptr>; Gtk::Window& window_; std::string output_name_; uint32_t configured_width_ = 0; uint32_t configured_height_ = 0; uint32_t width_ = 0; uint32_t height_ = 0; uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; bool exclusive_zone_ = true; bool passthrough_ = false; struct bar_margins margins_; zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; struct wl_output* output_ = nullptr; // owned by GTK struct wl_surface* surface_ = nullptr; // owned by GTK layer_surface_ptr layer_surface_; void onRealize() { auto gdk_window = window_.get_window()->gobj(); gdk_wayland_window_set_use_custom_surface(gdk_window); } void onMap(GdkEventAny* ev) { static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = onSurfaceConfigure, .closed = onSurfaceClosed, }; auto client = Client::inst(); auto gdk_window = window_.get_window()->gobj(); surface_ = gdk_wayland_window_get_wl_surface(gdk_window); layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface( client->layer_shell, surface_, output_, layer_, "waybar")); zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this); zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false); zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); zwlr_layer_surface_v1_set_margin( layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left); setSurfaceSize(width_, height_); setExclusiveZone(exclusive_zone_); setPassThrough(passthrough_); commit(); wl_display_roundtrip(client->wl_display); } void onConfigure(GdkEventConfigure* ev) { /* * GTK wants new size for the window. * * Prefer configured size if it's non-default. * If the size is not set and the window is smaller than requested by GTK, request resize from * layer surface. */ auto tmp_height = height_; auto tmp_width = width_; if (ev->height > static_cast(height_)) { // Default minimal value if (height_ > 1) { spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); } if (configured_height_ > 1) { spdlog::info(SIZE_DEFINED, "Height"); } else { tmp_height = ev->height; } } if (ev->width > static_cast(width_)) { // Default minimal value if (width_ > 1) { spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); } if (configured_width_ > 1) { spdlog::info(SIZE_DEFINED, "Width"); } else { tmp_width = ev->width; } } if (tmp_width != width_ || tmp_height != height_) { setSurfaceSize(tmp_width, tmp_height); commit(); } } void setSurfaceSize(uint32_t width, uint32_t height) { /* If the client is anchored to two opposite edges, layer_surface.configure will return * size without margins for the axis. * layer_surface.set_size, however, expects size with margins for the anchored axis. * This is not specified by wlr-layer-shell and based on actual behavior of sway. * * If the size for unanchored axis is not set (0), change request to 1 to avoid automatic * assignment by the compositor. */ if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { width = width > 0 ? width : 1; if (height > 1) { height += margins_.top + margins_.bottom; } } else { height = height > 0 ? height : 1; if (width > 1) { width += margins_.right + margins_.left; } } spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_); zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height); } static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) { auto o = static_cast(data); if (width != o->width_ || height != o->height_) { o->width_ = width; o->height_ = height; o->window_.set_size_request(o->width_, o->height_); o->window_.resize(o->width_, o->height_); o->setExclusiveZone(o->exclusive_zone_); spdlog::info(BAR_SIZE_MSG, o->width_ == 1 ? "auto" : std::to_string(o->width_), o->height_ == 1 ? "auto" : std::to_string(o->height_), o->output_name_); o->commit(); } zwlr_layer_surface_v1_ack_configure(surface, serial); } static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) { auto o = static_cast(data); o->layer_surface_.reset(); } }; }; // namespace waybar waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { window.set_title("waybar"); window.set_name("waybar"); window.set_decorated(false); window.get_style_context()->add_class(output->name); window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["position"].asString()); auto position = config["position"].asString(); if (position == "right" || position == "left") { left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); vertical = true; } left_.get_style_context()->add_class("modules-left"); center_.get_style_context()->add_class("modules-center"); right_.get_style_context()->add_class("modules-right"); if (config["spacing"].isInt()) { int spacing = config["spacing"].asInt(); left_.set_spacing(spacing); center_.set_spacing(spacing); right_.set_spacing(spacing); } uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; struct bar_margins margins_; if (config["margin-top"].isInt() || config["margin-right"].isInt() || config["margin-bottom"].isInt() || config["margin-left"].isInt()) { margins_ = { config["margin-top"].isInt() ? config["margin-top"].asInt() : 0, config["margin-right"].isInt() ? config["margin-right"].asInt() : 0, config["margin-bottom"].isInt() ? config["margin-bottom"].asInt() : 0, config["margin-left"].isInt() ? config["margin-left"].asInt() : 0, }; } else if (config["margin"].isString()) { std::istringstream iss(config["margin"].asString()); std::vector margins{std::istream_iterator(iss), {}}; try { if (margins.size() == 1) { auto gaps = std::stoi(margins[0], nullptr, 10); margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps}; } if (margins.size() == 2) { auto vertical_margins = std::stoi(margins[0], nullptr, 10); auto horizontal_margins = std::stoi(margins[1], nullptr, 10); margins_ = {.top = vertical_margins, .right = horizontal_margins, .bottom = vertical_margins, .left = horizontal_margins}; } if (margins.size() == 3) { auto horizontal_margins = std::stoi(margins[1], nullptr, 10); margins_ = {.top = std::stoi(margins[0], nullptr, 10), .right = horizontal_margins, .bottom = std::stoi(margins[2], nullptr, 10), .left = horizontal_margins}; } if (margins.size() == 4) { margins_ = {.top = std::stoi(margins[0], nullptr, 10), .right = std::stoi(margins[1], nullptr, 10), .bottom = std::stoi(margins[2], nullptr, 10), .left = std::stoi(margins[3], nullptr, 10)}; } } catch (...) { spdlog::warn("Invalid margins: {}", config["margin"].asString()); } } else if (config["margin"].isInt()) { auto gaps = config["margin"].asInt(); margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps}; } #ifdef HAVE_GTK_LAYER_SHELL bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; if (use_gls) { surface_impl_ = std::make_unique(window, *output); } else #endif { surface_impl_ = std::make_unique(window, *output); } surface_impl_->setMargins(margins_); surface_impl_->setPosition(position); surface_impl_->setSize(width, height); /* Read custom modes if available */ if (auto modes = config.get("modes", {}); modes.isObject()) { from_json(modes, configured_modes); } /* Update "default" mode with the global bar options */ from_json(config, configured_modes[MODE_DEFAULT]); if (auto mode = config.get("mode", {}); mode.isString()) { setMode(config["mode"].asString()); } else { setMode(MODE_DEFAULT); } window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); #if HAVE_SWAY if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { bar_id = Client::inst()->bar_id; if (auto id = config["id"]; id.isString()) { bar_id = id.asString(); } if (bar_id.empty()) { bar_id = DEFAULT_BAR_ID; } try { _ipc_client = std::make_unique(*this); } catch (const std::exception& exc) { spdlog::warn("Failed to open bar ipc connection: {}", exc.what()); } } #endif setupWidgets(); window.show_all(); if (spdlog::should_log(spdlog::level::debug)) { // Unfortunately, this function isn't in the C++ bindings, so we have to call the C version. char* gtk_tree = gtk_style_context_to_string( window.get_style_context()->gobj(), (GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE | GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE)); spdlog::debug("GTK widget tree:\n{}", gtk_tree); g_free(gtk_tree); } } /* Need to define it here because of forward declared members */ waybar::Bar::~Bar() = default; void waybar::Bar::setMode(const std::string_view& mode) { using namespace std::literals::string_literals; auto style = window.get_style_context(); /* remove styles added by previous setMode calls */ style->remove_class("mode-"s + last_mode_); auto it = configured_modes.find(mode); if (it != configured_modes.end()) { last_mode_ = mode; style->add_class("mode-"s + last_mode_); setMode(it->second); } else { spdlog::warn("Unknown mode \"{}\" requested", mode); last_mode_ = MODE_DEFAULT; style->add_class("mode-"s + last_mode_); setMode(configured_modes.at(MODE_DEFAULT)); } } void waybar::Bar::setMode(const struct bar_mode& mode) { surface_impl_->setLayer(mode.layer); surface_impl_->setExclusiveZone(mode.exclusive); surface_impl_->setPassThrough(mode.passthrough); if (mode.visible) { window.get_style_context()->remove_class("hidden"); window.set_opacity(1); } else { window.get_style_context()->add_class("hidden"); window.set_opacity(0); } surface_impl_->commit(); } void waybar::Bar::onMap(GdkEventAny*) { /* * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). */ auto gdk_window = window.get_window()->gobj(); surface = gdk_wayland_window_get_wl_surface(gdk_window); } void waybar::Bar::setVisible(bool value) { visible = value; setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE); } void waybar::Bar::toggle() { setVisible(!visible); } // Converting string to button code rn as to avoid doing it later void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) { if (config.isMember(module_name)) { Json::Value& module = config[module_name]; if (module.isMember("format-alt")) { if (module.isMember("format-alt-click")) { Json::Value& click = module["format-alt-click"]; if (click.isString()) { if (click == "click-right") { module["format-alt-click"] = 3U; } else if (click == "click-middle") { module["format-alt-click"] = 2U; } else if (click == "click-backward") { module["format-alt-click"] = 8U; } else if (click == "click-forward") { module["format-alt-click"] = 9U; } else { module["format-alt-click"] = 1U; // default click-left } } else { module["format-alt-click"] = 1U; } } else { module["format-alt-click"] = 1U; } } } } void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) { if (config.isMember(module_list_name)) { Json::Value& modules = config[module_list_name]; for (const Json::Value& module_name : modules) { if (module_name.isString()) { setupAltFormatKeyForModule(module_name.asString()); } } } } void waybar::Bar::handleSignal(int signal) { for (auto& module : modules_all_) { auto* custom = dynamic_cast(module.get()); if (custom != nullptr) { custom->refresh(signal); } } } void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) { auto module_list = group ? config[pos]["modules"] : config[pos]; if (module_list.isArray()) { for (const auto& name : module_list) { try { auto ref = name.asString(); AModule* module; if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { auto group_module = new waybar::Group(ref, *this, config[ref]); getModules(factory, ref, &group_module->box); module = group_module; } else { module = factory.makeModule(ref); } std::shared_ptr module_sp(module); modules_all_.emplace_back(module_sp); if (group) { group->pack_start(*module, false, false); } else { if (pos == "modules-left") { modules_left_.emplace_back(module_sp); } if (pos == "modules-center") { modules_center_.emplace_back(module_sp); } if (pos == "modules-right") { modules_right_.emplace_back(module_sp); } } module->dp.connect([module, &name] { try { module->update(); } catch (const std::exception& e) { spdlog::error("{}: {}", name.asString(), e.what()); } }); } catch (const std::exception& e) { spdlog::warn("module {}: {}", name.asString(), e.what()); } } } } auto waybar::Bar::setupWidgets() -> void { window.add(box_); box_.pack_start(left_, false, false); if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { box_.set_center_widget(center_); } else { box_.pack_start(center_, true, false); } box_.pack_end(right_, false, false); // Convert to button code for every module that is used. setupAltFormatKeyForModuleList("modules-left"); setupAltFormatKeyForModuleList("modules-right"); setupAltFormatKeyForModuleList("modules-center"); Factory factory(*this, config); getModules(factory, "modules-left"); getModules(factory, "modules-center"); getModules(factory, "modules-right"); for (auto const& module : modules_left_) { left_.pack_start(*module, false, false); } for (auto const& module : modules_center_) { center_.pack_start(*module, false, false); } std::reverse(modules_right_.begin(), modules_right_.end()); for (auto const& module : modules_right_) { right_.pack_end(*module, false, false); } } waybar-0.9.9/src/client.cpp000066400000000000000000000231461416677246500156170ustar00rootroot00000000000000#include "client.hpp" #include #include #include #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/clara.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" waybar::Client *waybar::Client::inst() { static auto c = new Client(); return c; } void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { auto client = static_cast(data); if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { // limit version to a highest supported by the client protocol file version = std::min(version, zwlr_layer_shell_v1_interface.version); client->layer_shell = static_cast( wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { client->xdg_output_manager = static_cast(wl_registry_bind( registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); } } void waybar::Client::handleGlobalRemove(void * data, struct wl_registry * /*registry*/, uint32_t name) { // Nothing here } void waybar::Client::handleOutput(struct waybar_output &output) { static const struct zxdg_output_v1_listener xdgOutputListener = { .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, .done = &handleOutputDone, .name = &handleOutputName, .description = &handleOutputDescription, }; // owned by output->monitor; no need to destroy auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); output.xdg_output.reset(zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, wl_output)); zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); } struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { auto it = std::find_if( outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); if (it == outputs_.end()) { throw std::runtime_error("Unable to find valid output"); } return *it; } std::vector waybar::Client::getOutputConfigs(struct waybar_output &output) { return config.getOutputConfigs(output.name, output.identifier); } void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { auto client = waybar::Client::inst(); try { auto &output = client->getOutput(data); /** * Multiple .done events may arrive in batch. In this case libwayland would queue * xdg_output.destroy and dispatch all pending events, triggering this callback several times * for the same output. .done events can also arrive after that for a scale or position changes. * We wouldn't want to draw a duplicate bar for each such event either. * * All the properties we care about are immutable so it's safe to delete the xdg_output object * on the first event and use the ptr value to check that the callback was already invoked. */ if (output.xdg_output) { output.xdg_output.reset(); spdlog::debug("Output detection done: {} ({})", output.name, output.identifier); auto configs = client->getOutputConfigs(output); if (!configs.empty()) { for (const auto &config : configs) { client->bars.emplace_back(std::make_unique(&output, config)); } } } } catch (const std::exception &e) { std::cerr << e.what() << std::endl; } } void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/, const char *name) { auto client = waybar::Client::inst(); try { auto &output = client->getOutput(data); output.name = name; } catch (const std::exception &e) { std::cerr << e.what() << std::endl; } } void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/, const char *description) { auto client = waybar::Client::inst(); try { auto & output = client->getOutput(data); const char *open_paren = strrchr(description, '('); // Description format: "identifier (name)" size_t identifier_length = open_paren - description; output.identifier = std::string(description, identifier_length - 1); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; } } void waybar::Client::handleMonitorAdded(Glib::RefPtr monitor) { auto &output = outputs_.emplace_back(); output.monitor = monitor; handleOutput(output); } void waybar::Client::handleMonitorRemoved(Glib::RefPtr monitor) { spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model()); /* This event can be triggered from wl_display_roundtrip called by GTK or our code. * Defer destruction of bars for the output to the next iteration of the event loop to avoid * deleting objects referenced by currently executed code. */ Glib::signal_idle().connect_once( sigc::bind(sigc::mem_fun(*this, &Client::handleDeferredMonitorRemoval), monitor), Glib::PRIORITY_HIGH_IDLE); } void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr monitor) { for (auto it = bars.begin(); it != bars.end();) { if ((*it)->output->monitor == monitor) { auto output_name = (*it)->output->name; (*it)->window.hide(); gtk_app->remove_window((*it)->window); it = bars.erase(it); spdlog::info("Bar removed from output: {}", output_name); } else { ++it; } } outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); } const std::string waybar::Client::getStyle(const std::string &style) { auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style; if (!css_file) { throw std::runtime_error("Missing required resource files"); } spdlog::info("Using CSS file {}", css_file.value()); return css_file.value(); }; auto waybar::Client::setupCss(const std::string &css_file) -> void { css_provider_ = Gtk::CssProvider::create(); style_context_ = Gtk::StyleContext::create(); // Load our css file, wherever that may be hiding if (!css_provider_->load_from_path(css_file)) { throw std::runtime_error("Can't open style file"); } // there's always only one screen style_context_->add_provider_for_screen( Gdk::Screen::get_default(), css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER); } void waybar::Client::bindInterfaces() { registry = wl_display_get_registry(wl_display); static const struct wl_registry_listener registry_listener = { .global = handleGlobal, .global_remove = handleGlobalRemove, }; wl_registry_add_listener(registry, ®istry_listener, this); wl_display_roundtrip(wl_display); if (layer_shell == nullptr || xdg_output_manager == nullptr) { throw std::runtime_error("Failed to acquire required resources."); } // add existing outputs and subscribe to updates for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { auto monitor = gdk_display->get_monitor(i); handleMonitorAdded(monitor); } gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); gdk_display->signal_monitor_removed().connect( sigc::mem_fun(*this, &Client::handleMonitorRemoved)); } int waybar::Client::main(int argc, char *argv[]) { bool show_help = false; bool show_version = false; std::string config_opt; std::string style_opt; std::string log_level; auto cli = clara::detail::Help(show_help) | clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") | clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") | clara::detail::Opt( log_level, "trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") | clara::detail::Opt(bar_id, "id")["-b"]["--bar"]("Bar id"); auto res = cli.parse(clara::detail::Args(argc, argv)); if (!res) { spdlog::error("Error in command line: {}", res.errorMessage()); return 1; } if (show_help) { std::cout << cli << std::endl; return 0; } if (show_version) { std::cout << "Waybar v" << VERSION << std::endl; return 0; } if (!log_level.empty()) { spdlog::set_level(spdlog::level::from_str(log_level)); } gtk_app = Gtk::Application::create( argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE); gdk_display = Gdk::Display::get_default(); if (!gdk_display) { throw std::runtime_error("Can't find display"); } if (!GDK_IS_WAYLAND_DISPLAY(gdk_display->gobj())) { throw std::runtime_error("Bar need to run under Wayland"); } wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); config.load(config_opt); auto css_file = getStyle(style_opt); setupCss(css_file); bindInterfaces(); gtk_app->hold(); gtk_app->run(); bars.clear(); return 0; } void waybar::Client::reset() { gtk_app->quit(); } waybar-0.9.9/src/config.cpp000066400000000000000000000114531416677246500156040ustar00rootroot00000000000000#include "config.hpp" #include #include #include #include #include #include #include "util/json.hpp" namespace waybar { const std::vector Config::CONFIG_DIRS = { "$XDG_CONFIG_HOME/waybar/", "$HOME/.config/waybar/", "$HOME/waybar/", "/etc/xdg/waybar/", SYSCONFDIR "/xdg/waybar/", "./resources/", }; std::optional tryExpandPath(const std::string &path) { wordexp_t p; if (wordexp(path.c_str(), &p, 0) == 0) { if (access(*p.we_wordv, F_OK) == 0) { std::string result = *p.we_wordv; wordfree(&p); return result; } wordfree(&p); } return std::nullopt; } std::optional Config::findConfigPath(const std::vector &names, const std::vector &dirs) { std::vector paths; for (const auto &dir : dirs) { for (const auto &name : names) { if (auto res = tryExpandPath(dir + name); res) { return res; } } } return std::nullopt; } void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) { if (depth > 100) { throw std::runtime_error("Aborting due to likely recursive include in config files"); } std::ifstream file(config_file); if (!file.is_open()) { throw std::runtime_error("Can't open config file"); } std::string str((std::istreambuf_iterator(file)), std::istreambuf_iterator()); util::JsonParser parser; Json::Value tmp_config = parser.parse(str); if (tmp_config.isArray()) { for (auto &config_part : tmp_config) { resolveConfigIncludes(config_part, depth); } } else { resolveConfigIncludes(tmp_config, depth); } mergeConfig(dst, tmp_config); } void Config::resolveConfigIncludes(Json::Value &config, int depth) { Json::Value includes = config["include"]; if (includes.isArray()) { for (const auto &include : includes) { spdlog::info("Including resource file: {}", include.asString()); setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth); } } else if (includes.isString()) { spdlog::info("Including resource file: {}", includes.asString()); setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth); } } void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) { if (!a_config_) { // For the first config a_config_ = b_config_; } else if (a_config_.isObject() && b_config_.isObject()) { for (const auto &key : b_config_.getMemberNames()) { // [] creates key with default value. Use `get` to avoid that. if (a_config_.get(key, Json::Value::nullSingleton()).isObject() && b_config_[key].isObject()) { mergeConfig(a_config_[key], b_config_[key]); } else if (!a_config_.isMember(key)) { // do not allow overriding value set by top or previously included config a_config_[key] = b_config_[key]; } else { spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]); } } } else { spdlog::error("Cannot merge config, conflicting or invalid JSON types"); } } bool isValidOutput(const Json::Value &config, const std::string &name, const std::string &identifier) { if (config["output"].isArray()) { for (auto const &output_conf : config["output"]) { if (output_conf.isString() && (output_conf.asString() == name || output_conf.asString() == identifier)) { return true; } } return false; } else if (config["output"].isString()) { auto config_output = config["output"].asString(); if (!config_output.empty()) { if (config_output.substr(0, 1) == "!") { return config_output.substr(1) != name && config_output.substr(1) != identifier; } return config_output == name || config_output == identifier; } } return true; } void Config::load(const std::string &config) { auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config; if (!file) { throw std::runtime_error("Missing required resource files"); } config_file_ = file.value(); spdlog::info("Using configuration file {}", config_file_); setupConfig(config_, config_file_, 0); } std::vector Config::getOutputConfigs(const std::string &name, const std::string &identifier) { std::vector configs; if (config_.isArray()) { for (auto const &config : config_) { if (config.isObject() && isValidOutput(config, name, identifier)) { configs.push_back(config); } } } else if (isValidOutput(config_, name, identifier)) { configs.push_back(config_); } return configs; } } // namespace waybar waybar-0.9.9/src/factory.cpp000066400000000000000000000073101416677246500160030ustar00rootroot00000000000000#include "factory.hpp" waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {} waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { try { auto hash_pos = name.find('#'); auto ref = name.substr(0, hash_pos); auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : ""; #if defined(__linux__) && !defined(NO_FILESYSTEM) if (ref == "battery") { return new waybar::modules::Battery(id, config_[name]); } #endif #ifdef HAVE_SWAY if (ref == "sway/mode") { return new waybar::modules::sway::Mode(id, config_[name]); } if (ref == "sway/workspaces") { return new waybar::modules::sway::Workspaces(id, bar_, config_[name]); } if (ref == "sway/window") { return new waybar::modules::sway::Window(id, bar_, config_[name]); } if (ref == "sway/language") { return new waybar::modules::sway::Language(id, config_[name]); } #endif #ifdef HAVE_WLR if (ref == "wlr/taskbar") { return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } #ifdef USE_EXPERIMENTAL if (ref == "wlr/workspaces") { return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); } #endif #endif #ifdef HAVE_RIVER if (ref == "river/tags") { return new waybar::modules::river::Tags(id, bar_, config_[name]); } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); } #if defined(HAVE_MEMORY_LINUX) || defined(HAVE_MEMORY_BSD) if (ref == "memory") { return new waybar::modules::Memory(id, config_[name]); } #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) if (ref == "cpu") { return new waybar::modules::Cpu(id, config_[name]); } #endif if (ref == "clock") { return new waybar::modules::Clock(id, config_[name]); } if (ref == "disk") { return new waybar::modules::Disk(id, config_[name]); } #ifdef HAVE_DBUSMENU if (ref == "tray") { return new waybar::modules::SNI::Tray(id, bar_, config_[name]); } #endif #ifdef HAVE_LIBNL if (ref == "network") { return new waybar::modules::Network(id, config_[name]); } #endif #ifdef HAVE_LIBUDEV if (ref == "backlight") { return new waybar::modules::Backlight(id, config_[name]); } #endif #ifdef HAVE_LIBEVDEV if (ref == "keyboard-state") { return new waybar::modules::KeyboardState(id, bar_, config_[name]); } #endif #ifdef HAVE_LIBPULSE if (ref == "pulseaudio") { return new waybar::modules::Pulseaudio(id, config_[name]); } #endif #ifdef HAVE_LIBMPDCLIENT if (ref == "mpd") { return new waybar::modules::MPD(id, config_[name]); } #endif #ifdef HAVE_LIBSNDIO if (ref == "sndio") { return new waybar::modules::Sndio(id, config_[name]); } #endif #ifdef HAVE_GIO_UNIX if (ref == "inhibitor") { return new waybar::modules::Inhibitor(id, bar_, config_[name]); } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); } #if defined(__linux__) # ifdef WANT_RFKILL if (ref == "bluetooth") { return new waybar::modules::Bluetooth(id, config_[name]); } # endif #endif if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name]); } } catch (const std::exception& e) { auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); throw std::runtime_error(err); } catch (...) { auto err = fmt::format("Disabling module \"{}\", Unknown reason", name); throw std::runtime_error(err); } throw std::runtime_error("Unknown module: " + name); } waybar-0.9.9/src/group.cpp000066400000000000000000000006571416677246500154770ustar00rootroot00000000000000#include "group.hpp" #include #include namespace waybar { Group::Group(const std::string& name, const Bar& bar, const Json::Value& config) : AModule(config, name, "", false, false), box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0} { } auto Group::update() -> void { // noop } Group::operator Gtk::Widget&() { return box; } } // namespace waybar waybar-0.9.9/src/main.cpp000066400000000000000000000052671416677246500152710ustar00rootroot00000000000000#include #include #include #include #include #include #include "client.hpp" std::mutex reap_mtx; std::list reap; volatile bool reload; void* signalThread(void* args) { int err, signum; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); while (true) { err = sigwait(&mask, &signum); if (err != 0) { spdlog::error("sigwait failed: {}", strerror(errno)); continue; } switch (signum) { case SIGCHLD: spdlog::debug("Received SIGCHLD in signalThread"); if (!reap.empty()) { reap_mtx.lock(); for (auto it = reap.begin(); it != reap.end(); ++it) { if (waitpid(*it, nullptr, WNOHANG) == *it) { spdlog::debug("Reaped child with PID: {}", *it); it = reap.erase(it); } } reap_mtx.unlock(); } break; default: spdlog::debug("Received signal with number {}, but not handling", signum); break; } } } void startSignalThread(void) { int err; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); // Block SIGCHLD so it can be handled by the signal thread // Any threads created by this one (the main thread) should not // modify their signal mask to unblock SIGCHLD err = pthread_sigmask(SIG_BLOCK, &mask, nullptr); if (err != 0) { spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err)); exit(1); } pthread_t thread_id; err = pthread_create(&thread_id, nullptr, signalThread, nullptr); if (err != 0) { spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err)); exit(1); } } int main(int argc, char* argv[]) { try { auto client = waybar::Client::inst(); std::signal(SIGUSR1, [](int /*signal*/) { for (auto& bar : waybar::Client::inst()->bars) { bar->toggle(); } }); std::signal(SIGUSR2, [](int /*signal*/) { spdlog::info("Reloading..."); reload = true; waybar::Client::inst()->reset(); }); for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { std::signal(sig, [](int sig) { for (auto& bar : waybar::Client::inst()->bars) { bar->handleSignal(sig); } }); } startSignalThread(); auto ret = 0; do { reload = false; ret = client->main(argc, argv); } while (reload); delete client; return ret; } catch (const std::exception& e) { spdlog::error("{}", e.what()); return 1; } catch (const Glib::Exception& e) { spdlog::error("{}", static_cast(e.what())); return 1; } } waybar-0.9.9/src/modules/000077500000000000000000000000001416677246500152775ustar00rootroot00000000000000waybar-0.9.9/src/modules/backlight.cpp000066400000000000000000000204631416677246500177400ustar00rootroot00000000000000#include "modules/backlight.hpp" #include #include #include #include #include #include #include namespace { class FileDescriptor { public: explicit FileDescriptor(int fd) : fd_(fd) {} FileDescriptor(const FileDescriptor &other) = delete; FileDescriptor(FileDescriptor &&other) noexcept = delete; FileDescriptor &operator=(const FileDescriptor &other) = delete; FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete; ~FileDescriptor() { if (fd_ != -1) { if (close(fd_) != 0) { fmt::print(stderr, "Failed to close fd: {}\n", errno); } } } int get() const { return fd_; } private: int fd_; }; struct UdevDeleter { void operator()(udev *ptr) { udev_unref(ptr); } }; struct UdevDeviceDeleter { void operator()(udev_device *ptr) { udev_device_unref(ptr); } }; struct UdevEnumerateDeleter { void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); } }; struct UdevMonitorDeleter { void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); } }; void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { throw std::runtime_error(fmt::format(message, rc)); } } void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") { if (rc == bad_rc) { throw std::runtime_error(fmt::format(message, rc)); } } void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); } void check_gte(int rc, int gte, const char *message = "rc was: ") { if (rc < gte) { throw std::runtime_error(fmt::format(message, rc)); } } void check_nn(const void *ptr, const char *message = "ptr was null") { if (ptr == nullptr) { throw std::runtime_error(message); } } } // namespace waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max) : name_(std::move(name)), actual_(actual), max_(max) {} std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; } int waybar::modules::Backlight::BacklightDev::get_actual() const { return actual_; } void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { actual_ = actual; } int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; } void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; } waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config) : ALabel(config, "backlight", id, "{percent}%", 2), preferred_device_(config["device"].isString() ? config["device"].asString() : "") { // Get initial state { std::unique_ptr udev_check{udev_new()}; check_nn(udev_check.get(), "Udev check new failed"); enumerate_devices( devices_.begin(), devices_.end(), std::back_inserter(devices_), udev_check.get()); if (devices_.empty()) { throw std::runtime_error("No backlight found"); } dp.emit(); } udev_thread_ = [this] { std::unique_ptr udev{udev_new()}; check_nn(udev.get(), "Udev new failed"); std::unique_ptr mon{ udev_monitor_new_from_netlink(udev.get(), "udev")}; check_nn(mon.get(), "udev monitor new failed"); check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0, "udev failed to add monitor filter: "); udev_monitor_enable_receiving(mon.get()); auto udev_fd = udev_monitor_get_fd(mon.get()); auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)}; check_neq(epoll_fd.get(), -1, "epoll init failed: "); epoll_event ctl_event{}; ctl_event.events = EPOLLIN; ctl_event.data.fd = udev_fd; check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event), "epoll_ctl failed: {}"); epoll_event events[EPOLL_MAX_EVENTS]; while (udev_thread_.isRunning()) { const int event_count = epoll_wait( epoll_fd.get(), events, EPOLL_MAX_EVENTS, std::chrono::milliseconds{interval_}.count()); if (!udev_thread_.isRunning()) { break; } decltype(devices_) devices; { std::scoped_lock lock(udev_thread_mutex_); devices = devices_; } for (int i = 0; i < event_count; ++i) { const auto &event = events[i]; check_eq(event.data.fd, udev_fd, "unexpected udev fd"); std::unique_ptr dev{udev_monitor_receive_device(mon.get())}; check_nn(dev.get(), "epoll dev was null"); upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get()); } // Refresh state if timed out if (event_count == 0) { enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get()); } { std::scoped_lock lock(udev_thread_mutex_); devices_ = devices; } dp.emit(); } }; } waybar::modules::Backlight::~Backlight() = default; auto waybar::modules::Backlight::update() -> void { decltype(devices_) devices; { std::scoped_lock lock(udev_thread_mutex_); devices = devices_; } const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_); if (best != nullptr) { if (previous_best_.has_value() && previous_best_.value() == *best && !previous_format_.empty() && previous_format_ == format_) { return; } const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); label_.set_markup(fmt::format( format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); getState(percent); } else { if (!previous_best_.has_value()) { return; } label_.set_markup(""); } previous_best_ = best == nullptr ? std::nullopt : std::optional{*best}; previous_format_ = format_; // Call parent update ALabel::update(); } template const waybar::modules::Backlight::BacklightDev *waybar::modules::Backlight::best_device( ForwardIt first, ForwardIt last, std::string_view preferred_device) { const auto found = std::find_if( first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; }); if (found != last) { return &(*found); } const auto max = std::max_element( first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); }); return max == last ? nullptr : &(*max); } template void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev) { const char *name = udev_device_get_sysname(dev); check_nn(name); const char *actual_brightness_attr = strncmp(name, "amdgpu_bl", 9) == 0 ? "brightness" : "actual_brightness"; const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr); check_nn(actual); const int actual_int = std::stoi(actual); const char *max = udev_device_get_sysattr_value(dev, "max_brightness"); check_nn(max); const int max_int = std::stoi(max); auto found = std::find_if(first, last, [name](const auto &device) { return device.name() == name; }); if (found != last) { found->set_actual(actual_int); found->set_max(max_int); } else { *inserter = BacklightDev{name, actual_int, max_int}; ++inserter; } } template void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev) { std::unique_ptr enumerate{udev_enumerate_new(udev)}; udev_enumerate_add_match_subsystem(enumerate.get(), "backlight"); udev_enumerate_scan_devices(enumerate.get()); udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get()); udev_list_entry *dev_list_entry; udev_list_entry_foreach(dev_list_entry, enum_devices) { const char * path = udev_list_entry_get_name(dev_list_entry); std::unique_ptr dev{udev_device_new_from_syspath(udev, path)}; check_nn(dev.get(), "dev new failed"); upsert_device(first, last, inserter, dev.get()); } } waybar-0.9.9/src/modules/battery.cpp000066400000000000000000000311771416677246500174660ustar00rootroot00000000000000#include "modules/battery.hpp" #include waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) : ALabel(config, "battery", id, "{capacity}%", 60) { battery_watch_fd_ = inotify_init1(IN_CLOEXEC); if (battery_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } global_watch_fd_ = inotify_init1(IN_CLOEXEC); if (global_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } // Watch the directory for any added or removed batteries global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); if (global_watch < 0) { throw std::runtime_error("Could not watch for battery plug/unplug"); } refreshBatteries(); worker(); } waybar::modules::Battery::~Battery() { std::lock_guard guard(battery_list_mutex_); if (global_watch >= 0) { inotify_rm_watch(global_watch_fd_, global_watch); } close(global_watch_fd_); for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) { auto watch_id = (*it).second; if (watch_id >= 0) { inotify_rm_watch(battery_watch_fd_, watch_id); } batteries_.erase(it); } close(battery_watch_fd_); } void waybar::modules::Battery::worker() { thread_timer_ = [this] { // Make sure we eventually update the list of batteries even if we miss an // inotify event for some reason refreshBatteries(); dp.emit(); thread_timer_.sleep_for(interval_); }; thread_ = [this] { struct inotify_event event = {0}; int nbytes = read(battery_watch_fd_, &event, sizeof(event)); if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { thread_.stop(); return; } dp.emit(); }; thread_battery_update_ = [this] { struct inotify_event event = {0}; int nbytes = read(global_watch_fd_, &event, sizeof(event)); if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { thread_.stop(); return; } refreshBatteries(); dp.emit(); }; } void waybar::modules::Battery::refreshBatteries() { std::lock_guard guard(battery_list_mutex_); // Mark existing list of batteries as not necessarily found std::map check_map; for (auto const& bat : batteries_) { check_map[bat.first] = false; } try { for (auto& node : fs::directory_iterator(data_dir_)) { if (!fs::is_directory(node)) { continue; } auto dir_name = node.path().filename(); auto bat_defined = config_["bat"].isString(); if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) && fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") && fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) { std::string type; std::ifstream(node.path() / "type") >> type; if (!type.compare("Battery")){ check_map[node.path()] = true; auto search = batteries_.find(node.path()); if (search == batteries_.end()) { // We've found a new battery save it and start listening for events auto event_path = (node.path() / "uevent"); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); if (wd < 0) { throw std::runtime_error("Could not watch events for " + node.path().string()); } batteries_[node.path()] = wd; } } } auto adap_defined = config_["adapter"].isString(); if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) && fs::exists(node.path() / "online")) { adapter_ = node.path(); } } } catch (fs::filesystem_error& e) { throw std::runtime_error(e.what()); } if (batteries_.empty()) { if (config_["bat"].isString()) { throw std::runtime_error("No battery named " + config_["bat"].asString()); } throw std::runtime_error("No batteries."); } // Remove any batteries that are no longer present and unwatch them for (auto const& check : check_map) { if (!check.second) { auto watch_id = batteries_[check.first]; if (watch_id >= 0) { inotify_rm_watch(battery_watch_fd_, watch_id); } batteries_.erase(check.first); } } } // Unknown > Full > Not charging > Discharging > Charging static bool status_gt(const std::string& a, const std::string& b) { if (a == b) return false; else if (a == "Unknown") return true; else if (a == "Full" && b != "Unknown") return true; else if (a == "Not charging" && b != "Unknown" && b != "Full") return true; else if (a == "Discharging" && b != "Unknown" && b != "Full" && b != "Not charging") return true; return false; } const std::tuple waybar::modules::Battery::getInfos() { std::lock_guard guard(battery_list_mutex_); try { uint32_t total_power = 0; // μW uint32_t total_energy = 0; // μWh uint32_t total_energy_full = 0; uint32_t total_energy_full_design = 0; std::string status = "Unknown"; for (auto const& item : batteries_) { auto bat = item.first; uint32_t power_now; uint32_t energy_full; uint32_t energy_now; uint32_t energy_full_design; std::string _status; std::ifstream(bat / "status") >> _status; // Some battery will report current and charge in μA/μAh. // Scale these by the voltage to get μW/μWh. if (fs::exists(bat / "current_now")) { uint32_t voltage_now; uint32_t current_now; uint32_t charge_now; uint32_t charge_full; uint32_t charge_full_design; std::ifstream(bat / "voltage_now") >> voltage_now; std::ifstream(bat / "current_now") >> current_now; std::ifstream(bat / "charge_full") >> charge_full; std::ifstream(bat / "charge_full_design") >> charge_full_design; if (fs::exists(bat / "charge_now")) std::ifstream(bat / "charge_now") >> charge_now; else { // charge_now is missing on some systems, estimate using capacity. uint32_t capacity; std::ifstream(bat / "capacity") >> capacity; charge_now = (capacity * charge_full) / 100; } power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000; energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000; energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000; energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000; } else { std::ifstream(bat / "power_now") >> power_now; std::ifstream(bat / "energy_now") >> energy_now; std::ifstream(bat / "energy_full") >> energy_full; std::ifstream(bat / "energy_full_design") >> energy_full_design; } // Show the "smallest" status among all batteries if (status_gt(status, _status)) { status = _status; } total_power += power_now; total_energy += energy_now; total_energy_full += energy_full; total_energy_full_design += energy_full_design; } if (!adapter_.empty() && status == "Discharging") { bool online; std::ifstream(adapter_ / "online") >> online; if (online) { status = "Plugged"; } } float time_remaining = 0; if (status == "Discharging" && total_power != 0) { time_remaining = (float)total_energy / total_power; } else if (status == "Charging" && total_power != 0) { time_remaining = -(float)(total_energy_full - total_energy) / total_power; if (time_remaining > 0.0f) { // If we've turned positive it means the battery is past 100% and so // just report that as no time remaining time_remaining = 0.0f; } } float capacity = ((float)total_energy * 100.0f / (float) total_energy_full); // Handle design-capacity if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) { capacity = ((float)total_energy * 100.0f / (float) total_energy_full_design); } // Handle full-at if (config_["full-at"].isUInt()) { auto full_at = config_["full-at"].asUInt(); if (full_at < 100) { capacity = 100.f * capacity / full_at; } } if (capacity > 100.f) { // This can happen when the battery is calibrating and goes above 100% // Handle it gracefully by clamping at 100% capacity = 100.f; } uint8_t cap = round(capacity); if (cap == 100 && status == "Charging") { // If we've reached 100% just mark as full as some batteries can stay // stuck reporting they're still charging but not yet done status = "Full"; } return {cap, time_remaining, status, total_power / 1e6}; } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); return {0, 0, "Unknown", 0}; } } const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const { if (!adapter_.empty()) { bool online; std::ifstream(adapter_ / "online") >> online; if (capacity == 100) { return "Full"; } if (online) { return "Plugged"; } return "Discharging"; } return "Unknown"; } const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) { hoursRemaining = std::fabs(hoursRemaining); uint16_t full_hours = static_cast(hoursRemaining); uint16_t minutes = static_cast(60 * (hoursRemaining - full_hours)); auto format = std::string("{H} h {M} min"); if (full_hours == 0 && minutes == 0) { // Migh as well not show "0h 0min" return ""; } if (config_["format-time"].isString()) { format = config_["format-time"].asString(); } return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes)); } auto waybar::modules::Battery::update() -> void { auto [capacity, time_remaining, status, power] = getInfos(); if (status == "Unknown") { status = getAdapterStatus(capacity); } auto status_pretty = status; // Transform to lowercase and replace space with dash std::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); auto format = format_; auto state = getState(capacity, true); auto time_remaining_formatted = formatTimeRemaining(time_remaining); if (tooltipEnabled()) { std::string tooltip_text_default; std::string tooltip_format = "{timeTo}"; if (time_remaining != 0) { std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full"); tooltip_text_default = time_to + ": " + time_remaining_formatted; } else { tooltip_text_default = status_pretty; } if (!state.empty() && config_["tooltip-format-" + status + "-" + state].isString()) { tooltip_format = config_["tooltip-format-" + status + "-" + state].asString(); } else if (config_["tooltip-format-" + status].isString()) { tooltip_format = config_["tooltip-format-" + status].asString(); } else if (!state.empty() && config_["tooltip-format-" + state].isString()) { tooltip_format = config_["tooltip-format-" + state].asString(); } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted))); } if (!old_status_.empty()) { label_.get_style_context()->remove_class(old_status_); } label_.get_style_context()->add_class(status); old_status_ = status; if (!state.empty() && config_["format-" + status + "-" + state].isString()) { format = config_["format-" + status + "-" + state].asString(); } else if (config_["format-" + status].isString()) { format = config_["format-" + status].asString(); } else if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } if (format.empty()) { event_box_.hide(); } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted))); } // Call parent update ALabel::update(); } waybar-0.9.9/src/modules/bluetooth.cpp000066400000000000000000000016011416677246500200060ustar00rootroot00000000000000#include "modules/bluetooth.hpp" #include waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config) : ALabel(config, "bluetooth", id, "{icon}", 10), rfkill_{RFKILL_TYPE_BLUETOOTH} { rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); } auto waybar::modules::Bluetooth::update() -> void { std::string status = rfkill_.getState() ? "disabled" : "enabled"; label_.set_markup( fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status)))); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status)); label_.set_tooltip_text(tooltip_text); } else { label_.set_tooltip_text(status); } } } waybar-0.9.9/src/modules/clock.cpp000066400000000000000000000165131416677246500171040ustar00rootroot00000000000000#include "modules/clock.hpp" #include #if FMT_VERSION < 60000 #include #else #include #endif #include #include #include #include "util/ustring_clen.hpp" #include "util/waybar_time.hpp" #ifdef HAVE_LANGINFO_1STDAY #include #include #endif using waybar::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), current_time_zone_idx_(0), is_calendar_in_tooltip_(false) { if (config_["timezones"].isArray() && !config_["timezones"].empty()) { for (const auto& zone_name: config_["timezones"]) { if (!zone_name.isString() || zone_name.asString().empty()) { time_zones_.push_back(nullptr); continue; } time_zones_.push_back( date::locate_zone( zone_name.asString() ) ); } } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) { time_zones_.push_back( date::locate_zone( config_["timezone"].asString() ) ); } // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown. if (!time_zones_.size()) { time_zones_.push_back(nullptr); } if (!is_timezone_fixed()) { spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018."); } // Check if a particular placeholder is present in the tooltip format, to know what to calculate on update. if (config_["tooltip-format"].isString()) { std::string trimmed_format = config_["tooltip-format"].asString(); trimmed_format.erase(std::remove_if(trimmed_format.begin(), trimmed_format.end(), [](unsigned char x){return std::isspace(x);}), trimmed_format.end()); if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) { is_calendar_in_tooltip_ = true; } } if (config_["locale"].isString()) { locale_ = std::locale(config_["locale"].asString()); } else { locale_ = std::locale(""); } thread_ = [this] { dp.emit(); auto now = std::chrono::system_clock::now(); auto timeout = std::chrono::floor(now + interval_); auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count()); thread_.sleep_until(timeout - diff); }; } const date::time_zone* waybar::modules::Clock::current_timezone() { return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone(); } bool waybar::modules::Clock::is_timezone_fixed() { return time_zones_[current_time_zone_idx_] != nullptr; } auto waybar::modules::Clock::update() -> void { auto time_zone = current_timezone(); auto now = std::chrono::system_clock::now(); waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor(now))}; std::string text = ""; if (!is_timezone_fixed()) { // As date dep is not fully compatible, prefer fmt tzset(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); text = fmt::format(format_, localtime); } else { text = fmt::format(format_, wtime); } label_.set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { std::string calendar_lines = ""; if (is_calendar_in_tooltip_) { calendar_lines = calendar_text(wtime); } auto tooltip_format = config_["tooltip-format"].asString(); text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines)); } } label_.set_tooltip_markup(text); // Call parent update ALabel::update(); } bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { // defer to user commands if set if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { return AModule::handleScroll(e); } auto dir = AModule::getScrollDir(e); if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { return true; } if (time_zones_.size() == 1) { return true; } auto nr_zones = time_zones_.size(); if (dir == SCROLL_DIR::UP) { size_t new_idx = current_time_zone_idx_ + 1; current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; } else { current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1; } update(); return true; } auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { const auto daypoint = date::floor(wtime.ztime.get_local_time()); const auto ymd = date::year_month_day(daypoint); if (cached_calendar_ymd_ == ymd) { return cached_calendar_text_; } const date::year_month ym(ymd.year(), ymd.month()); const auto curr_day = ymd.day(); std::stringstream os; const auto first_dow = first_day_of_week(); weekdays_header(first_dow, os); // First week prefixed with spaces if needed. auto wd = date::weekday(ym / 1); auto empty_days = (wd - first_dow).count(); if (empty_days > 0) { os << std::string(empty_days * 3 - 1, ' '); } auto last_day = (ym / date::literals::last).day(); for (auto d = date::day(1); d <= last_day; ++d, ++wd) { if (wd != first_dow) { os << ' '; } else if (unsigned(d) != 1) { os << '\n'; } if (d == curr_day) { if (config_["today-format"].isString()) { auto today_format = config_["today-format"].asString(); os << fmt::format(today_format, date::format("%e", d)); } else { os << "" << date::format("%e", d) << ""; } } else { os << date::format("%e", d); } } auto result = os.str(); cached_calendar_ymd_ = ymd; cached_calendar_text_ = result; return result; } auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void { auto wd = first_dow; do { if (wd != first_dow) os << ' '; Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); auto clen = ustring_clen(wd_ustring); auto wd_len = wd_ustring.length(); while (clen > 2) { wd_ustring = wd_ustring.substr(0, wd_len-1); wd_len--; clen = ustring_clen(wd_ustring); } const std::string pad(2 - clen, ' '); os << pad << wd_ustring; } while (++wd != first_dow); os << "\n"; } #ifdef HAVE_LANGINFO_1STDAY template using deleter_from_fn = std::integral_constant; template using deleting_unique_ptr = std::unique_ptr>; #endif // Computations done similarly to Linux cal utility. auto waybar::modules::Clock::first_day_of_week() -> date::weekday { #ifdef HAVE_LANGINFO_1STDAY deleting_unique_ptr::type, freelocale> posix_locale{ newlocale(LC_ALL, locale_.name().c_str(), nullptr)}; if (posix_locale) { const int i = (std::intptr_t)nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get()); auto ymd = date::year(i / 10000) / (i / 100 % 100) / (i % 100); auto wd = date::weekday(ymd); uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get()); return wd + date::days(j - 1); } #endif return date::Sunday; } waybar-0.9.9/src/modules/cpu/000077500000000000000000000000001416677246500160665ustar00rootroot00000000000000waybar-0.9.9/src/modules/cpu/bsd.cpp000066400000000000000000000060701416677246500173450ustar00rootroot00000000000000#include "modules/cpu.hpp" #include #include #include #include // malloc #include // sysconf #include // NAN #if defined(__NetBSD__) || defined(__OpenBSD__) # include #else # include #endif #if defined(__NetBSD__) typedef uint64_t cp_time_t; #else typedef long cp_time_t; #endif #if defined(__NetBSD__) || defined(__OpenBSD__) typedef uint64_t pcp_time_t; #else typedef long pcp_time_t; #endif std::vector> waybar::modules::Cpu::parseCpuinfo() { cp_time_t sum_cp_time[CPUSTATES]; size_t sum_sz = sizeof(sum_cp_time); int ncpu = sysconf(_SC_NPROCESSORS_CONF); size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t); pcp_time_t *cp_time = static_cast(malloc(sz)), *pcp_time = cp_time; #if defined(__NetBSD__) int mib[] = { CTL_KERN, KERN_CP_TIME, }; if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_time failed"); } for (int state = 0; state < CPUSTATES; state++) { cp_time[state] = sum_cp_time[state]; } pcp_time += CPUSTATES; if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), pcp_time, &sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_time failed"); } #elif defined(__OpenBSD__) { int mib[] = { CTL_KERN, KERN_CPTIME, }; if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), sum_cp_time, &sum_sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_time failed"); } } for (int state = 0; state < CPUSTATES; state++) { cp_time[state] = sum_cp_time[state]; } pcp_time = cp_time; sz /= ncpu + 1; { int mib[] = { CTL_KERN, KERN_CPTIME2, 0, }; for (int cpu = 0; cpu < ncpu; cpu++) { mib[2] = cpu; pcp_time += CPUSTATES; if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), pcp_time, &sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_time2 failed"); } } } #else if (sysctlbyname("kern.cp_time", sum_cp_time, &sum_sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_time failed"); } for (int state = 0; state < CPUSTATES; state++) { cp_time[state] = sum_cp_time[state]; } pcp_time += CPUSTATES; if (sysctlbyname("kern.cp_times", pcp_time, &sz, NULL, 0)) { throw std::runtime_error("sysctl kern.cp_times failed"); } #endif std::vector> cpuinfo; for (int cpu = 0; cpu < ncpu + 1; cpu++) { pcp_time_t total = 0, *single_cp_time = &cp_time[cpu * CPUSTATES]; for (int state = 0; state < CPUSTATES; state++) { total += single_cp_time[state]; } cpuinfo.emplace_back(single_cp_time[CP_IDLE], total); } free(cp_time); return cpuinfo; } std::vector waybar::modules::Cpu::parseCpuFrequencies() { static std::vector frequencies; if (frequencies.empty()) { spdlog::warn("cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}"); frequencies.push_back(NAN); } return frequencies; } waybar-0.9.9/src/modules/cpu/common.cpp000066400000000000000000000074501416677246500200700ustar00rootroot00000000000000#include "modules/cpu.hpp" // In the 80000 version of fmt library authors decided to optimize imports // and moved declarations required for fmt::dynamic_format_arg_store in new // header fmt/args.h #if (FMT_VERSION >= 80000) #include #else #include #endif waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) : ALabel(config, "cpu", id, "{usage}%", 10) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; } auto waybar::modules::Cpu::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto cpu_load = getCpuLoad(); auto [cpu_usage, tooltip] = getCpuUsage(); auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency(); if (tooltipEnabled()) { label_.set_tooltip_text(tooltip); } auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } if (format.empty()) { event_box_.hide(); } else { event_box_.show(); auto icons = std::vector{state}; fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("load", cpu_load)); store.push_back(fmt::arg("load", cpu_load)); store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency)); for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; auto core_format = fmt::format("usage{}", core_i); store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); auto icon_format = fmt::format("icon{}", core_i); store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); } // Call parent update ALabel::update(); } double waybar::modules::Cpu::getCpuLoad() { double load[1]; if (getloadavg(load, 1) != -1) { return load[0]; } throw std::runtime_error("Can't get Cpu load"); } std::tuple, std::string> waybar::modules::Cpu::getCpuUsage() { if (prev_times_.empty()) { prev_times_ = parseCpuinfo(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::vector> curr_times = parseCpuinfo(); std::string tooltip; std::vector usage; for (size_t i = 0; i < curr_times.size(); ++i) { auto [curr_idle, curr_total] = curr_times[i]; auto [prev_idle, prev_total] = prev_times_[i]; const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; uint16_t tmp = 100 * (1 - delta_idle / delta_total); if (i == 0) { tooltip = fmt::format("Total: {}%", tmp); } else { tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); } usage.push_back(tmp); } prev_times_ = curr_times; return {usage, tooltip}; } std::tuple waybar::modules::Cpu::getCpuFrequency() { std::vector frequencies = parseCpuFrequencies(); auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies)); float avg_frequency = std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size(); // Round frequencies with double decimal precision to get GHz float max_frequency = std::ceil(*max / 10.0) / 100.0; float min_frequency = std::ceil(*min / 10.0) / 100.0; avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0; return { max_frequency, min_frequency, avg_frequency }; } waybar-0.9.9/src/modules/cpu/linux.cpp000066400000000000000000000045431416677246500177370ustar00rootroot00000000000000#include #include "modules/cpu.hpp" std::vector> waybar::modules::Cpu::parseCpuinfo() { const std::string data_dir_ = "/proc/stat"; std::ifstream info(data_dir_); if (!info.is_open()) { throw std::runtime_error("Can't open " + data_dir_); } std::vector> cpuinfo; std::string line; while (getline(info, line)) { if (line.substr(0, 3).compare("cpu") != 0) { break; } std::stringstream sline(line.substr(5)); std::vector times; for (size_t time = 0; sline >> time; times.push_back(time)) ; size_t idle_time = 0; size_t total_time = 0; if (times.size() >= 4) { idle_time = times[3]; total_time = std::accumulate(times.begin(), times.end(), 0); } cpuinfo.emplace_back(idle_time, total_time); } return cpuinfo; } std::vector waybar::modules::Cpu::parseCpuFrequencies() { const std::string file_path_ = "/proc/cpuinfo"; std::ifstream info(file_path_); if (!info.is_open()) { throw std::runtime_error("Can't open " + file_path_); } std::vector frequencies; std::string line; while (getline(info, line)) { if (line.substr(0, 7).compare("cpu MHz") != 0) { continue; } std::string frequency_str = line.substr(line.find(":") + 2); float frequency = std::strtol(frequency_str.c_str(), nullptr, 10); frequencies.push_back(frequency); } info.close(); if (frequencies.size() <= 0) { std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq"; if (std::filesystem::exists(cpufreq_dir)) { std::vector frequency_files = { "/cpuinfo_min_freq", "/cpuinfo_max_freq" }; for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) { for (auto freq_file: frequency_files) { std::string freq_file_path = p.path().string() + freq_file; if (std::filesystem::exists(freq_file_path)) { std::string freq_value; std::ifstream freq(freq_file_path); if (freq.is_open()) { getline(freq, freq_value); float frequency = std::strtol(freq_value.c_str(), nullptr, 10); frequencies.push_back(frequency / 1000); freq.close(); } } } } } } return frequencies; } waybar-0.9.9/src/modules/custom.cpp000066400000000000000000000133521416677246500173210ustar00rootroot00000000000000#include "modules/custom.hpp" #include waybar::modules::Custom::Custom(const std::string& name, const std::string& id, const Json::Value& config) : ALabel(config, "custom-" + name, id, "{}"), name_(name), fp_(nullptr), pid_(-1) { dp.emit(); if (interval_.count() > 0) { delayWorker(); } else if (config_["exec"].isString()) { continuousWorker(); } } waybar::modules::Custom::~Custom() { if (pid_ != -1) { killpg(pid_, SIGTERM); pid_ = -1; } } void waybar::modules::Custom::delayWorker() { thread_ = [this] { bool can_update = true; if (config_["exec-if"].isString()) { output_ = util::command::execNoRead(config_["exec-if"].asString()); if (output_.exit_code != 0) { can_update = false; dp.emit(); } } if (can_update) { if (config_["exec"].isString()) { output_ = util::command::exec(config_["exec"].asString()); } dp.emit(); } thread_.sleep_for(interval_); }; } void waybar::modules::Custom::continuousWorker() { auto cmd = config_["exec"].asString(); pid_ = -1; fp_ = util::command::open(cmd, pid_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); } thread_ = [this, cmd] { char* buff = nullptr; size_t len = 0; if (getline(&buff, &len, fp_) == -1) { int exit_code = 1; if (fp_) { exit_code = WEXITSTATUS(util::command::close(fp_, pid_)); fp_ = nullptr; } if (exit_code != 0) { output_ = {exit_code, ""}; dp.emit(); spdlog::error("{} stopped unexpectedly, is it endless?", name_); } if (config_["restart-interval"].isUInt()) { pid_ = -1; thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); fp_ = util::command::open(cmd, pid_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); } } else { thread_.stop(); return; } } else { std::string output = buff; // Remove last newline if (!output.empty() && output[output.length() - 1] == '\n') { output.erase(output.length() - 1); } output_ = {0, output}; dp.emit(); } }; } void waybar::modules::Custom::refresh(int sig) { if (sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } } void waybar::modules::Custom::handleEvent() { if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) { thread_.wake_up(); } } bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) { auto ret = ALabel::handleScroll(e); handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { auto ret = ALabel::handleToggle(e); handleEvent(); return ret; } auto waybar::modules::Custom::update() -> void { // Hide label if output is empty if ((config_["exec"].isString() || config_["exec-if"].isString()) && (output_.out.empty() || output_.exit_code != 0)) { event_box_.hide(); } else { if (config_["return-type"].asString() == "json") { parseOutputJson(); } else { parseOutputRaw(); } auto str = fmt::format(format_, text_, fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); if (str.empty()) { event_box_.hide(); } else { label_.set_markup(str); if (tooltipEnabled()) { if (text_ == tooltip_) { if (label_.get_tooltip_markup() != str) { label_.set_tooltip_markup(str); } } else { if (label_.get_tooltip_markup() != tooltip_) { label_.set_tooltip_markup(tooltip_); } } } auto classes = label_.get_style_context()->list_classes(); for (auto const& c : classes) { label_.get_style_context()->remove_class(c); } for (auto const& c : class_) { label_.get_style_context()->add_class(c); } event_box_.show(); } } // Call parent update ALabel::update(); } void waybar::modules::Custom::parseOutputRaw() { std::istringstream output(output_.out); std::string line; int i = 0; while (getline(output, line)) { if (i == 0) { if (config_["escape"].isBool() && config_["escape"].asBool()) { text_ = Glib::Markup::escape_text(line); } else { text_ = line; } tooltip_ = line; class_.clear(); } else if (i == 1) { tooltip_ = line; } else if (i == 2) { class_.push_back(line); } else { break; } i++; } } void waybar::modules::Custom::parseOutputJson() { std::istringstream output(output_.out); std::string line; class_.clear(); while (getline(output, line)) { auto parsed = parser_.parse(line); if (config_["escape"].isBool() && config_["escape"].asBool()) { text_ = Glib::Markup::escape_text(parsed["text"].asString()); } else { text_ = parsed["text"].asString(); } if (config_["escape"].isBool() && config_["escape"].asBool()) { alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); } else { alt_ = parsed["alt"].asString(); } tooltip_ = parsed["tooltip"].asString(); if (parsed["class"].isString()) { class_.push_back(parsed["class"].asString()); } else if (parsed["class"].isArray()) { for (auto const& c : parsed["class"]) { class_.push_back(c.asString()); } } if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { percentage_ = parsed["percentage"].asUInt(); } else { percentage_ = 0; } break; } } waybar-0.9.9/src/modules/disk.cpp000066400000000000000000000056721416677246500167470ustar00rootroot00000000000000#include "modules/disk.hpp" using namespace waybar::util; waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config) : ALabel(config, "disk", id, "{}%", 30) , path_("/") { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; if (config["path"].isString()) { path_ = config["path"].asString(); } } auto waybar::modules::Disk::update() -> void { struct statvfs /* { unsigned long f_bsize; // filesystem block size unsigned long f_frsize; // fragment size fsblkcnt_t f_blocks; // size of fs in f_frsize units fsblkcnt_t f_bfree; // # free blocks fsblkcnt_t f_bavail; // # free blocks for unprivileged users fsfilcnt_t f_files; // # inodes fsfilcnt_t f_ffree; // # free inodes fsfilcnt_t f_favail; // # free inodes for unprivileged users unsigned long f_fsid; // filesystem ID unsigned long f_flag; // mount flags unsigned long f_namemax; // maximum filename length }; */ stats; int err = statvfs(path_.c_str(), &stats); /* Conky options fs_bar - Bar that shows how much space is used fs_free - Free space on a file system fs_free_perc - Free percentage of space fs_size - File system size fs_used - File system used space */ if (err != 0) { event_box_.hide(); return; } auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks; auto format = format_; auto state = getState(percentage_used); if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } if (format.empty()) { event_box_.hide(); } else { event_box_.show(); label_.set_markup(fmt::format(format , stats.f_bavail * 100 / stats.f_blocks , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); } if (tooltipEnabled()) { std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)"; if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } label_.set_tooltip_text(fmt::format(tooltip_format , stats.f_bavail * 100 / stats.f_blocks , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); } // Call parent update ALabel::update(); } waybar-0.9.9/src/modules/idle_inhibitor.cpp000066400000000000000000000047701416677246500207770ustar00rootroot00000000000000#include "modules/idle_inhibitor.hpp" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/command.hpp" std::list waybar::modules::IdleInhibitor::modules; bool waybar::modules::IdleInhibitor::status = false; waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "idle_inhibitor", id, "{status}"), bar_(bar), idle_inhibitor_(nullptr), pid_(-1) { if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { throw std::runtime_error("idle-inhibit not available"); } event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); // Add this to the modules list waybar::modules::IdleInhibitor::modules.push_back(this); dp.emit(); } waybar::modules::IdleInhibitor::~IdleInhibitor() { if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; } // Remove this from the modules list waybar::modules::IdleInhibitor::modules.remove(this); if (pid_ != -1) { kill(-pid_, 9); pid_ = -1; } } auto waybar::modules::IdleInhibitor::update() -> void { // Check status if (status) { label_.get_style_context()->remove_class("deactivated"); if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( waybar::Client::inst()->idle_inhibit_manager, bar_.surface); } } else { label_.get_style_context()->remove_class("activated"); if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; } } std::string status_text = status ? "activated" : "deactivated"; label_.set_markup( fmt::format(format_, fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { label_.set_tooltip_text(status_text); } // Call parent update ALabel::update(); } bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { status = !status; // Make all other idle inhibitor modules update for (auto const& module : waybar::modules::IdleInhibitor::modules) { if (module != this) { module->update(); } } } ALabel::handleToggle(e); return true; } waybar-0.9.9/src/modules/inhibitor.cpp000066400000000000000000000103121416677246500177670ustar00rootroot00000000000000#include "modules/inhibitor.hpp" #include #include #include namespace { using DBus = std::unique_ptr; auto dbus() -> DBus { GError *error = nullptr; GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (error) { spdlog::error("g_bus_get_sync() failed: {}", error->message); g_error_free(error); connection = nullptr; } auto destructor = [](GDBusConnection* connection) { GError *error = nullptr; g_dbus_connection_close_sync(connection, nullptr, &error); if (error) { spdlog::error( "g_bus_connection_close_sync failed(): {}", error->message); g_error_free(error); } }; return DBus{connection, destructor}; } auto getLocks(const DBus& bus, const std::string& inhibitors) -> int { GError *error = nullptr; GUnixFDList* fd_list; int handle; auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(), "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Inhibit", g_variant_new( "(ssss)", inhibitors.c_str(), "waybar", "Asked by user", "block"), G_VARIANT_TYPE("(h)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &fd_list, nullptr, &error); if (error) { spdlog::error( "g_dbus_connection_call_with_unix_fd_list_sync() failed: {}", error->message); g_error_free(error); handle = -1; } else { gint index; g_variant_get(reply, "(h)", &index); g_variant_unref(reply); handle = g_unix_fd_list_get(fd_list, index, nullptr); g_object_unref(fd_list); } return handle; } auto checkInhibitor(const std::string& inhibitor) -> const std::string& { static const auto inhibitors = std::array{ "idle", "shutdown", "sleep", "handle-power-key", "handle-suspend-key", "handle-hibernate-key", "handle-lid-switch" }; if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor) == inhibitors.end()) { throw std::runtime_error("invalid logind inhibitor " + inhibitor); } return inhibitor; } auto getInhibitors(const Json::Value& config) -> std::string { std::string inhibitors = "idle"; if (config["what"].empty()) { return inhibitors; } if (config["what"].isString()) { return checkInhibitor(config["what"].asString()); } if (config["what"].isArray()) { inhibitors = checkInhibitor(config["what"][0].asString()); for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { inhibitors += ":" + checkInhibitor(config["what"][i].asString()); } return inhibitors; } return inhibitors; } } namespace waybar::modules { Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "inhibitor", id, "{status}", true), dbus_(::dbus()), inhibitors_(::getInhibitors(config)) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &Inhibitor::handleToggle)); dp.emit(); } Inhibitor::~Inhibitor() { if (handle_ != -1) { ::close(handle_); } } auto Inhibitor::activated() -> bool { return handle_ != -1; } auto Inhibitor::update() -> void { std::string status_text = activated() ? "activated" : "deactivated"; label_.get_style_context()->remove_class( activated() ? "deactivated" : "activated"); label_.set_markup( fmt::format(format_, fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { label_.set_tooltip_text(status_text); } return ALabel::update(); } auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool { if (e->button == 1) { if (activated()) { ::close(handle_); handle_ = -1; } else { handle_ = ::getLocks(dbus_, inhibitors_); if (handle_ == -1) { spdlog::error("cannot get inhibitor locks"); } } } return ALabel::handleToggle(e); } } // waybar::modules waybar-0.9.9/src/modules/keyboard_state.cpp000066400000000000000000000126041416677246500210060ustar00rootroot00000000000000#include "modules/keyboard_state.hpp" #include #include extern "C" { #include #include #include } waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), numlock_label_(""), capslock_label_(""), numlock_format_(config_["format"].isString() ? config_["format"].asString() : config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString() : "{name} {icon}"), capslock_format_(config_["format"].isString() ? config_["format"].asString() : config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString() : "{name} {icon}"), scrolllock_format_(config_["format"].isString() ? config_["format"].asString() : config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString() : "{name} {icon}"), interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)), icon_locked_(config_["format-icons"]["locked"].isString() ? config_["format-icons"]["locked"].asString() : "locked"), icon_unlocked_(config_["format-icons"]["unlocked"].isString() ? config_["format-icons"]["unlocked"].asString() : "unlocked"), fd_(0), dev_(nullptr) { box_.set_name("keyboard-state"); if (config_["numlock"].asBool()) { box_.pack_end(numlock_label_, false, false, 0); } if (config_["capslock"].asBool()) { box_.pack_end(capslock_label_, false, false, 0); } if (config_["scrolllock"].asBool()) { box_.pack_end(scrolllock_label_, false, false, 0); } if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); if (config_["device-path"].isString()) { std::string dev_path = config_["device-path"].asString(); std::tie(fd_, dev_) = openDevice(dev_path); } else { DIR* dev_dir = opendir("/dev/input"); if (dev_dir == nullptr) { throw std::runtime_error("Failed to open /dev/input"); } dirent *ep; while ((ep = readdir(dev_dir))) { if (ep->d_type != DT_CHR) continue; std::string dev_path = std::string("/dev/input/") + ep->d_name; try { std::tie(fd_, dev_) = openDevice(dev_path); spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path); break; } catch (const std::runtime_error& e) { continue; } } if (dev_ == nullptr) { throw std::runtime_error("Failed to find keyboard device"); } } thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; } waybar::modules::KeyboardState::~KeyboardState() { libevdev_free(dev_); int err = close(fd_); if (err < 0) { // Not much we can do, so ignore it. } } auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair { int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY); if (fd < 0) { throw std::runtime_error("Can't open " + path); } libevdev* dev; int err = libevdev_new_from_fd(fd, &dev); if (err < 0) { throw std::runtime_error("Can't create libevdev device"); } if (!libevdev_has_event_type(dev, EV_LED)) { throw std::runtime_error("Device doesn't support LED events"); } if (!libevdev_has_event_code(dev, EV_LED, LED_NUML) || !libevdev_has_event_code(dev, EV_LED, LED_CAPSL) || !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) { throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events"); } return std::make_pair(fd, dev); } auto waybar::modules::KeyboardState::update() -> void { int err = LIBEVDEV_READ_STATUS_SUCCESS; while (err == LIBEVDEV_READ_STATUS_SUCCESS) { input_event ev; err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev); while (err == LIBEVDEV_READ_STATUS_SYNC) { err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev); } } if (err != -EAGAIN) { throw std::runtime_error("Failed to sync evdev device"); } int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML); int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL); int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL); struct { bool state; Gtk::Label& label; const std::string& format; const char* name; } label_states[] = { {(bool) numl, numlock_label_, numlock_format_, "Num"}, {(bool) capsl, capslock_label_, capslock_format_, "Caps"}, {(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"}, }; for (auto& label_state : label_states) { std::string text; text = fmt::format(label_state.format, fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_), fmt::arg("name", label_state.name)); label_state.label.set_markup(text); if (label_state.state) { label_state.label.get_style_context()->add_class("locked"); } else { label_state.label.get_style_context()->remove_class("locked"); } } AModule::update(); } waybar-0.9.9/src/modules/memory/000077500000000000000000000000001416677246500166075ustar00rootroot00000000000000waybar-0.9.9/src/modules/memory/bsd.cpp000066400000000000000000000047321416677246500200710ustar00rootroot00000000000000#include "modules/memory.hpp" #include #include #include // getpagesize #if defined(__DragonFly__) # include // struct vmstats #elif defined(__NetBSD__) # include // struct uvmexp_sysctl #elif defined(__OpenBSD__) # include // struct uvmexp #endif static uint64_t get_total_memory() { #if defined(HW_MEMSIZE) || defined(HW_PHYSMEM64) uint64_t physmem; #else u_long physmem; #endif int mib[] = { CTL_HW, #if defined(HW_MEMSIZE) HW_MEMSIZE, #elif defined(HW_PHYSMEM64) HW_PHYSMEM64, #else HW_PHYSMEM, #endif }; u_int miblen = sizeof(mib) / sizeof(mib[0]); size_t sz = sizeof(physmem); if (sysctl(mib, miblen, &physmem, &sz, NULL, 0)) { throw std::runtime_error("sysctl hw.physmem failed"); } return physmem; } static uint64_t get_free_memory() { #if defined(__DragonFly__) struct vmstats vms; size_t sz = sizeof(vms); if (sysctlbyname("vm.vmstats", &vms, &sz, NULL, 0)) { throw std::runtime_error("sysctl vm.vmstats failed"); } return static_cast (vms.v_free_count + vms.v_inactive_count + vms.v_cache_count) * getpagesize(); #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) u_int v_free_count = 0, v_inactive_count = 0, v_cache_count = 0; size_t sz = sizeof(u_int); sysctlbyname("vm.stats.vm.v_free_count", &v_free_count, &sz, NULL, 0); sysctlbyname("vm.stats.vm.v_inactive_count", &v_inactive_count, &sz, NULL, 0); sysctlbyname("vm.stats.vm.v_cache_count", &v_cache_count, &sz, NULL, 0); return static_cast (v_free_count + v_inactive_count + v_cache_count) * getpagesize(); #elif defined(__NetBSD__) || defined(__OpenBSD__) #ifdef VM_UVMEXP2 # undef VM_UVMEXP # define VM_UVMEXP VM_UVMEXP2 # define uvmexp uvmexp_sysctl #else # define filepages vnodepages # define execpages vtextpages #endif int mib[] = { CTL_VM, VM_UVMEXP, }; u_int miblen = sizeof(mib) / sizeof(mib[0]); struct uvmexp uvmexp; size_t sz = sizeof(uvmexp); if (sysctl(mib, miblen, &uvmexp, &sz, NULL, 0)) { throw std::runtime_error("sysctl vm.uvmexp failed"); } return static_cast (uvmexp.free + uvmexp.inactive + uvmexp.filepages + uvmexp.execpages) * uvmexp.pagesize; #endif } void waybar::modules::Memory::parseMeminfo() { meminfo_["MemTotal"] = get_total_memory() / 1024; meminfo_["MemAvailable"] = get_free_memory() / 1024; } waybar-0.9.9/src/modules/memory/common.cpp000066400000000000000000000053351416677246500206110ustar00rootroot00000000000000#include "modules/memory.hpp" waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) : ALabel(config, "memory", id, "{}%", 30) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; } auto waybar::modules::Memory::update() -> void { parseMeminfo(); unsigned long memtotal = meminfo_["MemTotal"]; unsigned long memfree; if (meminfo_.count("MemAvailable")) { // New kernels (3.4+) have an accurate available memory field. memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; } else { // Old kernel; give a best-effort approximation of available memory. memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"]; } if (memtotal > 0 && memfree >= 0) { auto total_ram_gigabytes = memtotal / std::pow(1024, 2); int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); auto available_ram_gigabytes = memfree / std::pow(1024, 2); auto format = format_; auto state = getState(used_ram_percentage); if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } if (format.empty()) { event_box_.hide(); } else { event_box_.show(); auto icons = std::vector{state}; label_.set_markup(fmt::format(format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("avail", available_ram_gigabytes))); } if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_text(fmt::format(tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::arg("percentage", used_ram_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("avail", available_ram_gigabytes))); } else { label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); } } } else { event_box_.hide(); } // Call parent update ALabel::update(); } waybar-0.9.9/src/modules/memory/linux.cpp000066400000000000000000000020011416677246500204430ustar00rootroot00000000000000#include "modules/memory.hpp" static unsigned zfsArcSize() { std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"}; if (zfs_arc_stats.is_open()) { std::string name; std::string type; unsigned long data{0}; std::string line; while (std::getline(zfs_arc_stats, line)) { std::stringstream(line) >> name >> type >> data; if (name == "size") { return data / 1024; // convert to kB } } } return 0; } void waybar::modules::Memory::parseMeminfo() { const std::string data_dir_ = "/proc/meminfo"; std::ifstream info(data_dir_); if (!info.is_open()) { throw std::runtime_error("Can't open " + data_dir_); } std::string line; while (getline(info, line)) { auto posDelim = line.find(':'); if (posDelim == std::string::npos) { continue; } std::string name = line.substr(0, posDelim); int64_t value = std::stol(line.substr(posDelim + 1)); meminfo_[name] = value; } meminfo_["zfs_size"] = zfsArcSize(); } waybar-0.9.9/src/modules/mpd/000077500000000000000000000000001416677246500160575ustar00rootroot00000000000000waybar-0.9.9/src/modules/mpd/mpd.cpp000066400000000000000000000274621416677246500173560ustar00rootroot00000000000000#include "modules/mpd/mpd.hpp" #include #include #include #include "modules/mpd/state.hpp" #if defined(MPD_NOINLINE) namespace waybar::modules { #include "modules/mpd/state.inl.hpp" } // namespace waybar::modules #endif waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), module_name_(id.empty() ? "mpd" : "mpd#" + id), server_(nullptr), port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), password_(config_["password"].empty() ? "" : config_["password"].asString()), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), song_(nullptr, &mpd_song_free) { if (!config_["port"].isNull() && !config_["port"].isUInt()) { spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_); } if (!config_["timeout"].isNull() && !config_["timeout"].isUInt()) { spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_); } if (!config["server"].isNull()) { if (!config_["server"].isString()) { spdlog::warn("{}:`server` configuration should be a string", module_name_); } server_ = config["server"].asCString(); } event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause)); } auto waybar::modules::MPD::update() -> void { context_.update(); // Call parent update ALabel::update(); } void waybar::modules::MPD::queryMPD() { if (connection_ != nullptr) { spdlog::debug("{}: fetching state information", module_name_); try { fetchState(); spdlog::debug("{}: fetch complete", module_name_); } catch (std::exception const& e) { spdlog::error("{}: {}", module_name_, e.what()); state_ = MPD_STATE_UNKNOWN; } dp.emit(); } } std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; const char* tag = mpd_song_get_tag(song_.get(), type, idx); // mpd_song_get_tag can return NULL, so make sure it's valid before setting if (tag) result = tag; return result; } void waybar::modules::MPD::setLabel() { if (connection_ == nullptr) { label_.get_style_context()->add_class("disconnected"); label_.get_style_context()->remove_class("stopped"); label_.get_style_context()->remove_class("playing"); label_.get_style_context()->remove_class("paused"); auto format = config_["format-disconnected"].isString() ? config_["format-disconnected"].asString() : "disconnected"; label_.set_markup(format); if (tooltipEnabled()) { std::string tooltip_format; tooltip_format = config_["tooltip-format-disconnected"].isString() ? config_["tooltip-format-disconnected"].asString() : "MPD (disconnected)"; // Nothing to format label_.set_tooltip_text(tooltip_format); } return; } else { label_.get_style_context()->remove_class("disconnected"); } auto format = format_; Glib::ustring artist, album_artist, album, title; std::string date; int song_pos = 0, queue_length = 0, volume = 0; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; if (stopped()) { format = config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped"; label_.get_style_context()->add_class("stopped"); label_.get_style_context()->remove_class("playing"); label_.get_style_context()->remove_class("paused"); } else { label_.get_style_context()->remove_class("stopped"); if (playing()) { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); } else if (paused()) { format = config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } stateIcon = getStateIcon(); artist = getTag(MPD_TAG_ARTIST); album_artist = getTag(MPD_TAG_ALBUM_ARTIST); album = getTag(MPD_TAG_ALBUM); title = getTag(MPD_TAG_TITLE); date = getTag(MPD_TAG_DATE); song_pos = mpd_status_get_song_pos(status_.get()); volume = mpd_status_get_volume(status_.get()); if (volume < 0) { volume = 0; } queue_length = mpd_status_get_queue_length(status_.get()); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); } bool consumeActivated = mpd_status_get_consume(status_.get()); std::string consumeIcon = getOptionIcon("consume", consumeActivated); bool randomActivated = mpd_status_get_random(status_.get()); std::string randomIcon = getOptionIcon("random", randomActivated); bool repeatActivated = mpd_status_get_repeat(status_.get()); std::string repeatIcon = getOptionIcon("repeat", repeatActivated); bool singleActivated = mpd_status_get_single(status_.get()); std::string singleIcon = getOptionIcon("single", singleActivated); if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt()); if (config_["album-artist-len"].isInt()) album_artist = album_artist.substr(0, config_["album-artist-len"].asInt()); if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt()); if (config_["title-len"].isInt()) title = title.substr(0,config_["title-len"].asInt()); try { label_.set_markup( fmt::format(format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()), fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()), fmt::arg("album", Glib::Markup::escape_text(album).raw()), fmt::arg("title", Glib::Markup::escape_text(title).raw()), fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon))); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error: {}", e.what()); } if (tooltipEnabled()) { std::string tooltip_format; tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString() : "MPD (connected)"; try { auto tooltip_text = fmt::format(tooltip_format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon)); label_.set_tooltip_text(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); } } } std::string waybar::modules::MPD::getStateIcon() const { if (!config_["state-icons"].isObject()) { return ""; } if (connection_ == nullptr) { spdlog::warn("{}: Trying to fetch state icon while disconnected", module_name_); return ""; } if (stopped()) { spdlog::warn("{}: Trying to fetch state icon while stopped", module_name_); return ""; } if (playing()) { return config_["state-icons"]["playing"].asString(); } else { return config_["state-icons"]["paused"].asString(); } } std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { if (!config_[optionName + "-icons"].isObject()) { return ""; } if (connection_ == nullptr) { spdlog::warn("{}: Trying to fetch option icon while disconnected", module_name_); return ""; } if (activated) { return config_[optionName + "-icons"]["on"].asString(); } else { return config_[optionName + "-icons"]["off"].asString(); } } void waybar::modules::MPD::tryConnect() { if (connection_ != nullptr) { return; } connection_ = detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); if (connection_ == nullptr) { spdlog::error("{}: Failed to connect to MPD", module_name_); connection_.reset(); return; } try { checkErrors(connection_.get()); spdlog::debug("{}: Connected to MPD", module_name_); if (!password_.empty()) { bool res = mpd_run_password(connection_.get(), password_.c_str()); if (!res) { spdlog::error("{}: Wrong MPD password", module_name_); connection_.reset(); return; } checkErrors(connection_.get()); } } catch (std::runtime_error& e) { spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what()); connection_.reset(); } } void waybar::modules::MPD::checkErrors(mpd_connection* conn) { switch (mpd_connection_get_error(conn)) { case MPD_ERROR_SUCCESS: mpd_connection_clear_error(conn); return; case MPD_ERROR_TIMEOUT: case MPD_ERROR_CLOSED: mpd_connection_clear_error(conn); connection_.reset(); state_ = MPD_STATE_UNKNOWN; throw std::runtime_error("Connection to MPD closed"); default: if (conn) { auto error_message = mpd_connection_get_error_message(conn); std::string error(error_message); mpd_connection_clear_error(conn); throw std::runtime_error(error); } throw std::runtime_error("Invalid connection"); } } void waybar::modules::MPD::fetchState() { if (connection_ == nullptr) { spdlog::error("{}: Not connected to MPD", module_name_); return; } auto conn = connection_.get(); status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free); checkErrors(conn); state_ = mpd_status_get_state(status_.get()); checkErrors(conn); song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free); checkErrors(conn); } bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) { if (e->type == GDK_2BUTTON_PRESS || e->type == GDK_3BUTTON_PRESS || connection_ == nullptr) { return false; } if (e->button == 1) { if (state_ == MPD_STATE_PLAY) context_.pause(); else context_.play(); } else if (e->button == 3) { context_.stop(); } return true; } waybar-0.9.9/src/modules/mpd/state.cpp000066400000000000000000000235371416677246500177150ustar00rootroot00000000000000#include "modules/mpd/state.hpp" #include #include #include "modules/mpd/mpd.hpp" #if defined(MPD_NOINLINE) namespace waybar::modules { #include "modules/mpd/state.inl.hpp" } // namespace waybar::modules #endif namespace waybar::modules::detail { #define IDLE_RUN_NOIDLE_AND_CMD(...) \ if (idle_connection_.connected()) { \ idle_connection_.disconnect(); \ auto conn = ctx_->connection().get(); \ if (!mpd_run_noidle(conn)) { \ if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \ spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \ ctx_->checkErrors(conn); \ } \ } \ __VA_ARGS__; \ } void Idle::play() { IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); ctx_->setState(std::make_unique(ctx_)); } void Idle::pause() { IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); ctx_->setState(std::make_unique(ctx_)); } void Idle::stop() { IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); ctx_->setState(std::make_unique(ctx_)); } #undef IDLE_RUN_NOIDLE_AND_CMD void Idle::update() noexcept { // This is intentionally blank. } void Idle::entry() noexcept { auto conn = ctx_->connection().get(); assert(conn != nullptr); if (!mpd_send_idle_mask( conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { ctx_->checkErrors(conn); spdlog::error("mpd: Idle: failed to register for IDLE events"); } else { spdlog::debug("mpd: Idle: watching FD"); sigc::slot idle_slot = sigc::mem_fun(*this, &Idle::on_io); idle_connection_ = Glib::signal_io().connect(idle_slot, mpd_connection_get_fd(conn), Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP); } } void Idle::exit() noexcept { if (idle_connection_.connected()) { idle_connection_.disconnect(); spdlog::debug("mpd: Idle: unwatching FD"); } } bool Idle::on_io(Glib::IOCondition const&) { auto conn = ctx_->connection().get(); // callback should do this: enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false); spdlog::debug("mpd: Idle: recv_idle events -> {}", events); mpd_response_finish(conn); try { ctx_->checkErrors(conn); } catch (std::exception const& e) { spdlog::warn("mpd: Idle: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); return false; } ctx_->fetchState(); mpd_state state = ctx_->state(); if (state == MPD_STATE_STOP) { ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else if (state == MPD_STATE_PLAY) { ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else if (state == MPD_STATE_PAUSE) { ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else { ctx_->emit(); // self transition ctx_->setState(std::make_unique(ctx_)); } return false; } void Playing::entry() noexcept { sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000); spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); } void Playing::exit() noexcept { if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); } } bool Playing::on_timer() { // Attempt to connect with MPD. try { ctx_->tryConnect(); // Success? if (!ctx_->is_connected()) { ctx_->setState(std::make_unique(ctx_)); return false; } ctx_->fetchState(); if (!ctx_->is_playing()) { if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); } else { ctx_->setState(std::make_unique(ctx_)); } return false; } ctx_->queryMPD(); ctx_->emit(); } catch (std::exception const& e) { spdlog::warn("mpd: Playing: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); return false; } return true; } void Playing::stop() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_stop(ctx_->connection().get()); } ctx_->setState(std::make_unique(ctx_)); } void Playing::pause() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_pause(ctx_->connection().get(), true); } ctx_->setState(std::make_unique(ctx_)); } void Playing::update() noexcept { ctx_->do_update(); } void Paused::entry() noexcept { sigc::slot timer_slot = sigc::mem_fun(*this, &Paused::on_timer); timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); spdlog::debug("mpd: Paused: enabled 200 ms periodic timer."); } void Paused::exit() noexcept { if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Paused: disabled 200 ms periodic timer."); } } bool Paused::on_timer() { bool rc = true; // Attempt to connect with MPD. try { ctx_->tryConnect(); // Success? if (!ctx_->is_connected()) { ctx_->setState(std::make_unique(ctx_)); return false; } ctx_->fetchState(); ctx_->emit(); if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } else if (ctx_->is_playing()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } else if (ctx_->is_stopped()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } } catch (std::exception const& e) { spdlog::warn("mpd: Paused: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); rc = false; } return rc; } void Paused::play() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_play(ctx_->connection().get()); } ctx_->setState(std::make_unique(ctx_)); } void Paused::stop() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_stop(ctx_->connection().get()); } ctx_->setState(std::make_unique(ctx_)); } void Paused::update() noexcept { ctx_->do_update(); } void Stopped::entry() noexcept { sigc::slot timer_slot = sigc::mem_fun(*this, &Stopped::on_timer); timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer."); } void Stopped::exit() noexcept { if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer."); } } bool Stopped::on_timer() { bool rc = true; // Attempt to connect with MPD. try { ctx_->tryConnect(); // Success? if (!ctx_->is_connected()) { ctx_->setState(std::make_unique(ctx_)); return false; } ctx_->fetchState(); ctx_->emit(); if (ctx_->is_stopped()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } else if (ctx_->is_playing()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } else if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); rc = false; } } catch (std::exception const& e) { spdlog::warn("mpd: Stopped: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); rc = false; } return rc; } void Stopped::play() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_play(ctx_->connection().get()); } ctx_->setState(std::make_unique(ctx_)); } void Stopped::pause() { if (timer_connection_.connected()) { timer_connection_.disconnect(); mpd_run_pause(ctx_->connection().get(), true); } ctx_->setState(std::make_unique(ctx_)); } void Stopped::update() noexcept { ctx_->do_update(); } void Disconnected::arm_timer(int interval) noexcept { // unregister timer, if present disarm_timer(); // register timer sigc::slot timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer); timer_connection_ = Glib::signal_timeout().connect(timer_slot, interval); spdlog::debug("mpd: Disconnected: enabled interval timer."); } void Disconnected::disarm_timer() noexcept { // unregister timer, if present if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Disconnected: disabled interval timer."); } } void Disconnected::entry() noexcept { ctx_->emit(); arm_timer(1'000); } void Disconnected::exit() noexcept { disarm_timer(); } bool Disconnected::on_timer() { // Attempt to connect with MPD. try { ctx_->tryConnect(); // Success? if (ctx_->is_connected()) { ctx_->fetchState(); ctx_->emit(); if (ctx_->is_playing()) { ctx_->setState(std::make_unique(ctx_)); } else if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); } else { ctx_->setState(std::make_unique(ctx_)); } return false; // do not rearm timer } } catch (std::exception const& e) { spdlog::warn("mpd: Disconnected: error: {}", e.what()); } arm_timer(ctx_->interval() * 1'000); return false; } void Disconnected::update() noexcept { ctx_->do_update(); } } // namespace waybar::modules::detail waybar-0.9.9/src/modules/network.cpp000066400000000000000000000723311416677246500175020ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "modules/network.hpp" #include "util/format.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" #endif namespace { using namespace waybar::util; constexpr const char *DEFAULT_FORMAT = "{ifname}"; } // namespace constexpr const char *NETDEV_FILE = "/proc/net/dev"; // std::ifstream does not take std::string_view as param std::optional> waybar::modules::Network::readBandwidthUsage() { std::ifstream netdev(NETDEV_FILE); if (!netdev) { spdlog::warn("Failed to open netdev file {}", NETDEV_FILE); return {}; } std::string line; // skip the headers (first two lines) std::getline(netdev, line); std::getline(netdev, line); unsigned long long receivedBytes = 0ull; unsigned long long transmittedBytes = 0ull; while (std::getline(netdev, line)) { std::istringstream iss(line); std::string ifacename; iss >> ifacename; // ifacename contains "eth0:" ifacename.pop_back(); // remove trailing ':' if (!checkInterface(ifacename)) { continue; } // The rest of the line consists of whitespace separated counts divided // into two groups (receive and transmit). Each group has the following // columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast // // We only care about the bytes count, so we'll just ignore the 7 other // columns. unsigned long long r = 0ull; unsigned long long t = 0ull; // Read received bytes iss >> r; // Skip all the other columns in the received group for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) { // skip whitespace between columns while (iss.peek() == ' ') { iss.ignore(); } // skip the irrelevant column while (iss.peek() != ' ') { iss.ignore(); } } // Read transmit bytes iss >> t; receivedBytes += r; transmittedBytes += t; } return {{receivedBytes, transmittedBytes}}; } waybar::modules::Network::Network(const std::string &id, const Json::Value &config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), want_route_dump_(false), want_link_dump_(false), want_addr_dump_(false), dump_in_progress_(false), cidr_(0), signal_strength_dbm_(0), signal_strength_(0), #ifdef WANT_RFKILL rfkill_{RFKILL_TYPE_WLAN}, #endif frequency_(0.0) { // Start with some "text" in the module's label_, update() will then // update it. Since the text should be different, update() will be able // to show or hide the event_box_. This is to work around the case where // the module start with no text, but the the event_box_ is shown. label_.set_markup(""); auto bandwidth = readBandwidthUsage(); if (bandwidth.has_value()) { bandwidth_down_total_ = (*bandwidth).first; bandwidth_up_total_ = (*bandwidth).second; } else { bandwidth_down_total_ = 0; bandwidth_up_total_ = 0; } if (!config_["interface"].isString()) { // "interface" isn't configure, then try to guess the external // interface currently used for internet. want_route_dump_ = true; } else { // Look for an interface that match "interface" // and then find the address associated with it. want_link_dump_ = true; want_addr_dump_ = true; } createEventSocket(); createInfoSocket(); dp.emit(); // Ask for a dump of interfaces and then addresses to populate our // information. First the interface dump, and once done, the callback // will be called again which will ask for addresses dump. askForStateDump(); worker(); } waybar::modules::Network::~Network() { if (ev_fd_ > -1) { close(ev_fd_); } if (efd_ > -1) { close(efd_); } if (ev_sock_ != nullptr) { nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK); if (family_ == AF_INET) { nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); } else { nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } nl_close(ev_sock_); nl_socket_free(ev_sock_); } if (sock_ != nullptr) { nl_close(sock_); nl_socket_free(sock_); } } void waybar::modules::Network::createEventSocket() { ev_sock_ = nl_socket_alloc(); nl_socket_disable_seq_check(ev_sock_); nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this); nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this); auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR); nl_join_groups(ev_sock_, groups); // Deprecated if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) { throw std::runtime_error("Can't connect network socket"); } if (nl_socket_set_nonblocking(ev_sock_)) { throw std::runtime_error("Can't set non-blocking on network socket"); } nl_socket_add_membership(ev_sock_, RTNLGRP_LINK); if (family_ == AF_INET) { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } if (!config_["interface"].isString()) { if (family_ == AF_INET) { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE); } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE); } } efd_ = epoll_create1(EPOLL_CLOEXEC); if (efd_ < 0) { throw std::runtime_error("Can't create epoll"); } { ev_fd_ = eventfd(0, EFD_NONBLOCK); struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLET; event.data.fd = ev_fd_; if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) { throw std::runtime_error("Can't add epoll event"); } } { auto fd = nl_socket_get_fd(ev_sock_); struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; event.data.fd = fd; if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) { throw std::runtime_error("Can't add epoll event"); } } } void waybar::modules::Network::createInfoSocket() { sock_ = nl_socket_alloc(); if (genl_connect(sock_) != 0) { throw std::runtime_error("Can't connect to netlink socket"); } if (nl_socket_modify_cb(sock_, NL_CB_VALID, NL_CB_CUSTOM, handleScan, this) < 0) { throw std::runtime_error("Can't set callback"); } nl80211_id_ = genl_ctrl_resolve(sock_, "nl80211"); if (nl80211_id_ < 0) { spdlog::warn("Can't resolve nl80211 interface"); } } void waybar::modules::Network::worker() { // update via here not working thread_timer_ = [this] { { std::lock_guard lock(mutex_); if (ifid_ > 0) { getInfo(); dp.emit(); } } thread_timer_.sleep_for(interval_); }; #ifdef WANT_RFKILL rfkill_.on_update.connect([this](auto &) { /* If we are here, it's likely that the network thread already holds the mutex and will be * holding it for a next few seconds. * Let's delegate the update to the timer thread instead of blocking the main thread. */ thread_timer_.wake_up(); }); #else spdlog::warn("Waybar has been built without rfkill support."); #endif thread_ = [this] { std::array events{}; int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1); if (ec > 0) { for (auto i = 0; i < ec; i++) { if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) { int rc = 0; // Read as many message as possible, until the socket blocks while (true) { errno = 0; rc = nl_recvmsgs_default(ev_sock_); if (rc == -NLE_AGAIN || errno == EAGAIN) { rc = 0; break; } } if (rc < 0) { spdlog::error("nl_recvmsgs_default error: {}", nl_geterror(-rc)); thread_.stop(); break; } } else { thread_.stop(); break; } } } }; } const std::string waybar::modules::Network::getNetworkState() const { if (ifid_ == -1) { #ifdef WANT_RFKILL if (rfkill_.getState()) return "disabled"; #endif return "disconnected"; } if (!carrier_) return "disconnected"; if (ipaddr_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; } auto waybar::modules::Network::update() -> void { std::lock_guard lock(mutex_); std::string tooltip_format; auto bandwidth = readBandwidthUsage(); auto bandwidth_down = 0ull; auto bandwidth_up = 0ull; if (bandwidth.has_value()) { auto down_octets = (*bandwidth).first; auto up_octets = (*bandwidth).second; bandwidth_down = down_octets - bandwidth_down_total_; bandwidth_down_total_ = down_octets; bandwidth_up = up_octets - bandwidth_up_total_; bandwidth_up_total_ = up_octets; } if (!alt_) { auto state = getNetworkState(); if (!state_.empty() && label_.get_style_context()->has_class(state_)) { label_.get_style_context()->remove_class(state_); } if (config_["format-" + state].isString()) { default_format_ = config_["format-" + state].asString(); } else if (config_["format"].isString()) { default_format_ = config_["format"].asString(); } else { default_format_ = DEFAULT_FORMAT; } if (config_["tooltip-format-" + state].isString()) { tooltip_format = config_["tooltip-format-" + state].asString(); } if (!label_.get_style_context()->has_class(state)) { label_.get_style_context()->add_class(state); } format_ = default_format_; state_ = state; } getState(signal_strength_); auto text = fmt::format( format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s"))); if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); if (text.empty()) { event_box_.hide(); } else { event_box_.show(); } } if (tooltipEnabled()) { if (tooltip_format.empty() && config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { auto tooltip_text = fmt::format( tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s"))); if (label_.get_tooltip_text() != tooltip_text) { label_.set_tooltip_text(tooltip_text); } } else if (label_.get_tooltip_text() != text) { label_.set_tooltip_text(text); } } // Call parent update ALabel::update(); } bool waybar::modules::Network::checkInterface(std::string name) { if (config_["interface"].isString()) { return config_["interface"].asString() == name || wildcardMatch(config_["interface"].asString(), name); } return false; } void waybar::modules::Network::clearIface() { ifid_ = -1; ifname_.clear(); essid_.clear(); ipaddr_.clear(); gwaddr_.clear(); netmask_.clear(); carrier_ = false; cidr_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; signal_strength_app_.clear(); frequency_ = 0.0; } int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { auto net = static_cast(data); std::lock_guard lock(net->mutex_); auto nh = nlmsg_hdr(msg); bool is_del_event = false; switch (nh->nlmsg_type) { case RTM_DELLINK: is_del_event = true; case RTM_NEWLINK: { struct ifinfomsg *ifi = static_cast(NLMSG_DATA(nh)); ssize_t attrlen = IFLA_PAYLOAD(nh); struct rtattr *ifla = IFLA_RTA(ifi); const char *ifname = NULL; size_t ifname_len = 0; std::optional carrier; if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) { return NL_OK; } // Check if the interface goes "down" and if we want to detect the // external interface. if (net->ifid_ != -1 && !(ifi->ifi_flags & IFF_UP) && !net->config_["interface"].isString()) { // The current interface is now down, all the routes associated with // it have been deleted, so start looking for a new default route. spdlog::debug("network: if{} down", net->ifid_); net->clearIface(); net->dp.emit(); net->want_route_dump_ = true; net->askForStateDump(); return NL_OK; } for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) { switch (ifla->rta_type) { case IFLA_IFNAME: ifname = static_cast(RTA_DATA(ifla)); ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0 break; case IFLA_CARRIER: { carrier = *(char*)RTA_DATA(ifla) == 1; break; } } } if (!is_del_event && ifi->ifi_index == net->ifid_) { // Update interface information if (net->ifname_.empty() && ifname != NULL) { std::string new_ifname (ifname, ifname_len); net->ifname_ = new_ifname; } if (carrier.has_value()) { if (net->carrier_ != *carrier) { if (*carrier) { // Ask for WiFi information net->thread_timer_.wake_up(); } else { // clear state related to WiFi connection net->essid_.clear(); net->signal_strength_dbm_ = 0; net->signal_strength_ = 0; net->signal_strength_app_.clear(); net->frequency_ = 0.0; } } net->carrier_ = carrier.value(); } } else if (!is_del_event && net->ifid_ == -1) { // Checking if it's an interface we care about. std::string new_ifname (ifname, ifname_len); if (net->checkInterface(new_ifname)) { spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); net->ifname_ = new_ifname; net->ifid_ = ifi->ifi_index; if (carrier.has_value()) { net->carrier_ = carrier.value(); } net->thread_timer_.wake_up(); /* An address for this new interface should be received via an * RTM_NEWADDR event either because we ask for a dump of both links * and addrs, or because this interface has just been created and * the addr will be sent after the RTM_NEWLINK event. * So we don't need to do anything. */ } } else if (is_del_event && net->ifid_ >= 0) { // Our interface has been deleted, start looking/waiting for one we care. spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_); net->clearIface(); net->dp.emit(); } break; } case RTM_DELADDR: is_del_event = true; case RTM_NEWADDR: { struct ifaddrmsg *ifa = static_cast(NLMSG_DATA(nh)); ssize_t attrlen = IFA_PAYLOAD(nh); struct rtattr *ifa_rta = IFA_RTA(ifa); if ((int)ifa->ifa_index != net->ifid_) { return NL_OK; } if (ifa->ifa_family != net->family_) { return NL_OK; } // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { return NL_OK; } for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { switch (ifa_rta->rta_type) { case IFA_ADDRESS: { char ipaddr[INET6_ADDRSTRLEN]; if (!is_del_event) { net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof (ipaddr)); net->cidr_ = ifa->ifa_prefixlen; switch (ifa->ifa_family) { case AF_INET: { struct in_addr netmask; netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen)); net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof (ipaddr)); } case AF_INET6: { struct in6_addr netmask; for (int i = 0; i < 16; i++) { int v = (i + 1) * 8 - ifa->ifa_prefixlen; if (v < 0) v = 0; if (v > 8) v = 8; netmask.s6_addr[i] = ~0 << v; } net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof (ipaddr)); } } spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); } else { net->ipaddr_.clear(); net->cidr_ = 0; net->netmask_.clear(); spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof (ipaddr)), ifa->ifa_prefixlen); } net->dp.emit(); break; } } } break; } char temp_gw_addr[INET6_ADDRSTRLEN]; case RTM_DELROUTE: is_del_event = true; case RTM_NEWROUTE: { // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 // to find the interface used to reach the outside world struct rtmsg *rtm = static_cast(NLMSG_DATA(nh)); ssize_t attrlen = RTM_PAYLOAD(nh); struct rtattr *attr = RTM_RTA(rtm); bool has_gateway = false; bool has_destination = false; int temp_idx = -1; uint32_t priority = 0; /* Find the message(s) concerting the main routing table, each message * corresponds to a single routing table entry. */ if (rtm->rtm_table != RT_TABLE_MAIN) { return NL_OK; } /* Parse all the attributes for a single routing table entry. */ for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) { /* Determine if this routing table entry corresponds to the default * route by seeing if it has a gateway, and if a destination addr is * set, that it is all 0s. */ switch(attr->rta_type) { case RTA_GATEWAY: /* The gateway of the route. * * If someone ever needs to figure out the gateway address as well, * it's here as the attribute payload. */ inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr)); has_gateway = true; break; case RTA_DST: { /* The destination address. * Should be either missing, or maybe all 0s. Accept both. */ const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16; unsigned char c = 0; size_t dstlen = RTA_PAYLOAD(attr); if (dstlen != nr_zeroes) { break; } for (uint32_t i = 0; i < dstlen; i += 1) { c |= *((unsigned char *)RTA_DATA(attr) + i); } has_destination = (c == 0); break; } case RTA_OIF: /* The output interface index. */ temp_idx = *static_cast(RTA_DATA(attr)); break; case RTA_PRIORITY: priority = *(uint32_t*)RTA_DATA(attr); break; default: break; } } // Check if we have a default route. if (has_gateway && !has_destination && temp_idx != -1) { // Check if this is the first default route we see, or if this new // route have a higher priority. if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) { // Clear if's state for the case were there is a higher priority // route on a different interface. net->clearIface(); net->ifid_ = temp_idx; net->route_priority = priority; net->gwaddr_ = temp_gw_addr; spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr, temp_idx, priority); /* Ask ifname associated with temp_idx as well as carrier status */ struct ifinfomsg ifinfo_hdr = { .ifi_family = AF_UNSPEC, .ifi_index = temp_idx, }; int err; err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST, &ifinfo_hdr, sizeof (ifinfo_hdr)); if (err < 0) { spdlog::error("network: failed to ask link info: {}", err); /* Ask for a dump of all links instead */ net->want_link_dump_ = true; } /* Also ask for the address. Asking for a addresses of a specific * interface doesn't seems to work so ask for a dump of all * addresses. */ net->want_addr_dump_ = true; net->askForStateDump(); net->thread_timer_.wake_up(); } else if (is_del_event && temp_idx == net->ifid_ && net->route_priority == priority) { spdlog::debug("network: default route deleted {}/if{} metric {}", net->ifname_, temp_idx, priority); net->clearIface(); net->dp.emit(); /* Ask for a dump of all routes in case another one is already * setup. If there's none, there'll be an event with new one * later. */ net->want_route_dump_ = true; net->askForStateDump(); } } break; } } return NL_OK; } void waybar::modules::Network::askForStateDump(void) { /* We need to wait until the current dump is done before sending new * messages. handleEventsDone() is called when a dump is done. */ if (dump_in_progress_) return; struct rtgenmsg rt_hdr = { .rtgen_family = AF_UNSPEC, }; if (want_route_dump_) { rt_hdr.rtgen_family = family_; nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, &rt_hdr, sizeof (rt_hdr)); want_route_dump_ = false; dump_in_progress_ = true; } else if (want_link_dump_) { nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP, &rt_hdr, sizeof (rt_hdr)); want_link_dump_ = false; dump_in_progress_ = true; } else if (want_addr_dump_) { rt_hdr.rtgen_family = family_; nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, &rt_hdr, sizeof (rt_hdr)); want_addr_dump_ = false; dump_in_progress_ = true; } } int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) { auto net = static_cast(data); net->dump_in_progress_ = false; net->askForStateDump(); return NL_OK; } int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { auto net = static_cast(data); auto gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); struct nlattr * tb[NL80211_ATTR_MAX + 1]; struct nlattr * bss[NL80211_BSS_MAX + 1]; struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{}; bss_policy[NL80211_BSS_TSF].type = NLA_U64; bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32; bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC; bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16; bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16; bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC; bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32; bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8; bss_policy[NL80211_BSS_STATUS].type = NLA_U32; if (nla_parse( tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) { return NL_SKIP; } if (tb[NL80211_ATTR_BSS] == nullptr) { return NL_SKIP; } if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) { return NL_SKIP; } if (!net->associatedOrJoined(bss)) { return NL_SKIP; } net->parseEssid(bss); net->parseSignal(bss); net->parseFreq(bss); return NL_OK; } void waybar::modules::Network::parseEssid(struct nlattr **bss) { if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) { auto ies = static_cast(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]); const auto hdr_len = 2; while (ies_len > hdr_len && ies[0] != 0) { ies_len -= ies[1] + hdr_len; ies += ies[1] + hdr_len; } if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) { auto essid_begin = ies + hdr_len; auto essid_end = essid_begin + ies[1]; std::string essid_raw; std::copy(essid_begin, essid_end, std::back_inserter(essid_raw)); essid_ = Glib::Markup::escape_text(essid_raw); } } } void waybar::modules::Network::parseSignal(struct nlattr **bss) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; // WiFi-hardware usually operates in the range -90 to -30dBm. // If a signal is too strong, it can overwhelm receiving circuity that is designed // to pick up and process a certain signal level. The following percentage is scaled to // punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm). const int hardwareOptimum = -45; const int hardwareMin = -90; const int strength = 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100); signal_strength_ = std::clamp(strength, 0, 100); if (signal_strength_dbm_ >= -50) { signal_strength_app_ = "Great Connectivity"; } else if (signal_strength_dbm_ >= -60) { signal_strength_app_ = "Good Connectivity"; } else if (signal_strength_dbm_ >= -67) { signal_strength_app_ = "Streaming"; } else if (signal_strength_dbm_ >= -70) { signal_strength_app_ = "Web Surfing"; } else if (signal_strength_dbm_ >= -80) { signal_strength_app_ = "Basic Connectivity"; } else { signal_strength_app_ = "Poor Connectivity"; } } if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); } } void waybar::modules::Network::parseFreq(struct nlattr **bss) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) { // in GHz frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; } } bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) { if (bss[NL80211_BSS_STATUS] == nullptr) { return false; } auto status = nla_get_u32(bss[NL80211_BSS_STATUS]); switch (status) { case NL80211_BSS_STATUS_ASSOCIATED: case NL80211_BSS_STATUS_IBSS_JOINED: case NL80211_BSS_STATUS_AUTHENTICATED: return true; default: return false; } } auto waybar::modules::Network::getInfo() -> void { struct nl_msg *nl_msg = nlmsg_alloc(); if (nl_msg == nullptr) { return; } if (genlmsg_put( nl_msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id_, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) == nullptr || nla_put_u32(nl_msg, NL80211_ATTR_IFINDEX, ifid_) < 0) { nlmsg_free(nl_msg); return; } nl_send_sync(sock_, nl_msg); } // https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495 bool waybar::modules::Network::wildcardMatch(const std::string &pattern, const std::string &text) const { auto P = int(pattern.size()); auto T = int(text.size()); auto p = 0, fallback_p = -1; auto t = 0, fallback_t = -1; while (t < T) { // Wildcard match: if (p < P && pattern[p] == '*') { fallback_p = p++; // starting point after failures fallback_t = t; // starting point after failures } // Simple match: else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) { p++; t++; } // Failure, fall back just after last matched '*': else if (fallback_p >= 0) { p = fallback_p + 1; // position just after last matched '*" t = ++fallback_t; // re-try to match text from here } // There were no '*' before, so we fail here: else { return false; } } // Consume all '*' at the end of pattern: while (p < P && pattern[p] == '*') p++; return p == P; } waybar-0.9.9/src/modules/pulseaudio.cpp000066400000000000000000000254361416677246500201670ustar00rootroot00000000000000#include "modules/pulseaudio.hpp" waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config) : ALabel(config, "pulseaudio", id, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr), context_(nullptr), sink_idx_(0), volume_(0), muted_(false), source_idx_(0), source_volume_(0), source_muted_(false) { mainloop_ = pa_threaded_mainloop_new(); if (mainloop_ == nullptr) { throw std::runtime_error("pa_mainloop_new() failed."); } pa_threaded_mainloop_lock(mainloop_); mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_); context_ = pa_context_new(mainloop_api_, "waybar"); if (context_ == nullptr) { throw std::runtime_error("pa_context_new() failed."); } if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { auto err = fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_))); throw std::runtime_error(err); } pa_context_set_state_callback(context_, contextStateCb, this); if (pa_threaded_mainloop_start(mainloop_) < 0) { throw std::runtime_error("pa_mainloop_run() failed."); } pa_threaded_mainloop_unlock(mainloop_); event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll)); } waybar::modules::Pulseaudio::~Pulseaudio() { mainloop_api_->quit(mainloop_api_, 0); pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_free(mainloop_); } void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) { auto pa = static_cast(data); switch (pa_context_get_state(c)) { case PA_CONTEXT_TERMINATED: pa->mainloop_api_->quit(pa->mainloop_api_, 0); break; case PA_CONTEXT_READY: pa_context_get_server_info(c, serverInfoCb, data); pa_context_set_subscribe_callback(c, subscribeCb, data); pa_context_subscribe( c, static_cast(static_cast(PA_SUBSCRIPTION_MASK_SERVER) | static_cast(PA_SUBSCRIPTION_MASK_SINK) | static_cast(PA_SUBSCRIPTION_MASK_SOURCE)), nullptr, nullptr); break; case PA_CONTEXT_FAILED: pa->mainloop_api_->quit(pa->mainloop_api_, 1); break; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: default: break; } } bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { // change the pulse volume only when no user provided // events are configured if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { return AModule::handleScroll(e); } auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; } if (config_["reverse-scrolling"].asInt() == 1){ if (dir == SCROLL_DIR::UP) { dir = SCROLL_DIR::DOWN; } else if (dir == SCROLL_DIR::DOWN) { dir = SCROLL_DIR::UP; } } double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; // isDouble returns true for integers as well, just in case if (config_["scroll-step"].isDouble()) { change = round(config_["scroll-step"].asDouble() * volume_tick); } if (dir == SCROLL_DIR::UP) { if (volume_ + 1 <= 100) { pa_cvolume_inc(&pa_volume, change); } } else if (dir == SCROLL_DIR::DOWN) { if (volume_ - 1 >= 0) { pa_cvolume_dec(&pa_volume, change); } } pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); return true; } /* * Called when an event we subscribed to occurs. */ void waybar::modules::Pulseaudio::subscribeCb(pa_context * context, pa_subscription_event_type_t type, uint32_t idx, void *data) { unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) { return; } if (facility == PA_SUBSCRIPTION_EVENT_SERVER) { pa_context_get_server_info(context, serverInfoCb, data); } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); } } /* * Called in response to a volume change request */ void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) { auto pa = static_cast(data); if (success != 0) { pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data); } } /* * Called when the requested source information is ready. */ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i, int /*eol*/, void *data) { auto pa = static_cast(data); if (i != nullptr && pa->default_source_name_ == i->name) { auto source_volume = static_cast(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM}; pa->source_volume_ = std::round(source_volume * 100.0F); pa->source_idx_ = i->index; pa->source_muted_ = i->mute != 0; pa->source_desc_ = i->description; pa->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; pa->dp.emit(); } } /* * Called when the requested sink information is ready. */ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, int /*eol*/, void *data) { if (i == nullptr) return; auto pa = static_cast(data); if (pa->current_sink_name_ == i->name) { if (i->state != PA_SINK_RUNNING) { pa->current_sink_running_ = false; } else { pa->current_sink_running_ = true; } } if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) { pa->current_sink_name_ = i->name; pa->current_sink_running_ = true; } if (pa->current_sink_name_ == i->name) { pa->pa_volume_ = i->volume; float volume = static_cast(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM}; pa->sink_idx_ = i->index; pa->volume_ = std::round(volume * 100.0F); pa->muted_ = i->mute != 0; pa->desc_ = i->description; pa->monitor_ = i->monitor_source_name; pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) { pa->form_factor_ = ff; } pa->dp.emit(); } } /* * Called when the requested information on the server is ready. This is * used to find the default PulseAudio sink. */ void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) { auto pa = static_cast(data); pa->current_sink_name_ = i->default_sink_name; pa->default_source_name_ = i->default_source_name; pa_context_get_sink_info_list(context, sinkInfoCb, data); pa_context_get_source_info_list(context, sourceInfoCb, data); } static const std::array ports = { "headphone", "speaker", "hdmi", "headset", "hands-free", "portable", "car", "hifi", "phone", }; const std::vector waybar::modules::Pulseaudio::getPulseIcon() const { std::vector res = {current_sink_name_, default_source_name_}; std::string nameLC = port_name_ + form_factor_; std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { if (nameLC.find(port) != std::string::npos) { res.push_back(port); return res; } } return res; } auto waybar::modules::Pulseaudio::update() -> void { auto format = format_; std::string tooltip_format; if (!alt_) { std::string format_name = "format"; if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire format_name = format_name + "-bluetooth"; label_.get_style_context()->add_class("bluetooth"); } else { label_.get_style_context()->remove_class("bluetooth"); } if (muted_) { // Check muted bluetooth format exist, otherwise fallback to default muted format if (format_name != "format" && !config_[format_name + "-muted"].isString()) { format_name = "format"; } format_name = format_name + "-muted"; label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("sink-muted"); } else { label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("sink-muted"); } format = config_[format_name].isString() ? config_[format_name].asString() : format; } // TODO: find a better way to split source/sink std::string format_source = "{volume}%"; if (source_muted_) { label_.get_style_context()->add_class("source-muted"); if (config_["format-source-muted"].isString()) { format_source = config_["format-source-muted"].asString(); } } else { label_.get_style_context()->remove_class("source-muted"); if (config_["format-source-muted"].isString()) { format_source = config_["format-source"].asString(); } } format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); label_.set_markup(fmt::format(format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); getState(volume_); if (tooltipEnabled()) { if (tooltip_format.empty() && config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { label_.set_tooltip_text(fmt::format( tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); } else { label_.set_tooltip_text(desc_); } } // Call parent update ALabel::update(); } waybar-0.9.9/src/modules/river/000077500000000000000000000000001416677246500164265ustar00rootroot00000000000000waybar-0.9.9/src/modules/river/tags.cpp000066400000000000000000000174741416677246500201050ustar00rootroot00000000000000#include #include #include #include #include #include "client.hpp" #include "modules/river/tags.hpp" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar::modules::river { static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags) { static_cast(data)->handle_focused_tags(tags); } static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, struct wl_array *tags) { static_cast(data)->handle_view_tags(tags); } static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, uint32_t tags) { static_cast(data)->handle_urgent_tags(tags); } static const zriver_output_status_v1_listener output_status_listener_impl{ .focused_tags = listen_focused_tags, .view_tags = listen_view_tags, .urgent_tags = listen_urgent_tags, }; static void listen_command_success(void *data, struct zriver_command_callback_v1 *zriver_command_callback_v1, const char *output) { // Do nothing but keep listener to avoid crashing when command was successful } static void listen_command_failure(void *data, struct zriver_command_callback_v1 *zriver_command_callback_v1, const char *output) { spdlog::error("failure when selecting/toggling tags {}", output); } static const zriver_command_callback_v1_listener command_callback_listener_impl { .success = listen_command_success, .failure = listen_command_failure, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { version = std::min(version, 2); if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) { spdlog::warn("river server does not support urgent tags"); } static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) { version = std::min(version, 1); static_cast(data)->control_ = static_cast( wl_registry_bind(registry, name, &zriver_control_v1_interface, version)); } if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1); static_cast(data)->seat_ = static_cast( wl_registry_bind(registry, name, &wl_seat_interface, version)); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { /* Ignore event */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) : waybar::AModule(config, "tags", id, false, false), status_manager_{nullptr}, control_{nullptr}, seat_{nullptr}, bar_(bar), box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, output_status_{nullptr} { struct wl_display * display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); if (!status_manager_) { spdlog::error("river_status_manager_v1 not advertised"); return; } if (!control_) { spdlog::error("river_control_v1 not advertised"); } if (!seat_) { spdlog::error("wl_seat not advertised"); } box_.set_name("tags"); if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); // Default to 9 tags, cap at 32 const uint32_t num_tags = config["num-tags"].isUInt() ? std::min(32, config_["num-tags"].asUInt()) : 9; std::vector tag_labels(num_tags); for (uint32_t tag = 0; tag < num_tags; ++tag) { tag_labels[tag] = std::to_string(tag+1); } const Json::Value custom_labels = config["tag-labels"]; if (custom_labels.isArray() && !custom_labels.empty()) { for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) { tag_labels[tag] = custom_labels[tag].asString(); } } uint32_t i = 1; for (const auto &tag_label : tag_labels) { Gtk::Button &button = buttons_.emplace_back(tag_label); button.set_relief(Gtk::RELIEF_NONE); box_.pack_start(button, false, false, 0); if (!config_["disable-click"].asBool()) { button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i)); button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i)); } button.show(); i <<= 1; } struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); zriver_status_manager_v1_destroy(status_manager_); } Tags::~Tags() { if (output_status_) { zriver_output_status_v1_destroy(output_status_); } if (control_) { zriver_control_v1_destroy(control_); } } void Tags::handle_primary_clicked(uint32_t tag) { // Send river command to select tag on left mouse click zriver_command_callback_v1 *callback; zriver_control_v1_add_argument(control_, "set-focused-tags"); zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); callback = zriver_control_v1_run_command(control_, seat_); zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); } bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { // Send river command to toggle tag on right mouse click zriver_command_callback_v1 *callback; zriver_control_v1_add_argument(control_, "toggle-focused-tags"); zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); callback = zriver_control_v1_run_command(control_, seat_); zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); } return true; } void Tags::handle_focused_tags(uint32_t tags) { uint32_t i = 0; for (auto &button : buttons_) { if ((1 << i) & tags) { button.get_style_context()->add_class("focused"); } else { button.get_style_context()->remove_class("focused"); } ++i; } } void Tags::handle_view_tags(struct wl_array *view_tags) { // First clear all occupied state for (auto &button : buttons_) { button.get_style_context()->remove_class("occupied"); } // Set tags with a view to occupied uint32_t *start = static_cast(view_tags->data); for (uint32_t *tags = start; tags < start + view_tags->size / sizeof(uint32_t); ++tags) { uint32_t i = 0; for (auto &button : buttons_) { if (*tags & (1 << i)) { button.get_style_context()->add_class("occupied"); } ++i; } } } void Tags::handle_urgent_tags(uint32_t tags) { uint32_t i = 0; for (auto &button : buttons_) { if ((1 << i) & tags) { button.get_style_context()->add_class("urgent"); } else { button.get_style_context()->remove_class("urgent"); } ++i; } } } /* namespace waybar::modules::river */ waybar-0.9.9/src/modules/simpleclock.cpp000066400000000000000000000021631416677246500203120ustar00rootroot00000000000000#include "modules/simpleclock.hpp" #include waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) : ALabel(config, "clock", id, "{:%H:%M}", 60) { thread_ = [this] { dp.emit(); auto now = std::chrono::system_clock::now(); auto timeout = std::chrono::floor(now + interval_); auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count()); thread_.sleep_until(timeout - diff); }; } auto waybar::modules::Clock::update() -> void { tzset(); // Update timezone information auto now = std::chrono::system_clock::now(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); auto text = fmt::format(format_, localtime); label_.set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_text = fmt::format(tooltip_format, localtime); label_.set_tooltip_text(tooltip_text); } else { label_.set_tooltip_text(text); } } // Call parent update ALabel::update(); } waybar-0.9.9/src/modules/sndio.cpp000066400000000000000000000115351416677246500171240ustar00rootroot00000000000000#include "modules/sndio.hpp" #include #include #include #include #include namespace waybar::modules { void ondesc(void *arg, struct sioctl_desc *d, int curval) { auto self = static_cast(arg); if (d == NULL) { // d is NULL when the list is done return; } self->set_desc(d, curval); } void onval(void *arg, unsigned int addr, unsigned int val) { auto self = static_cast(arg); self->put_val(addr, val); } auto Sndio::connect_to_sndio() -> void { hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0); if (hdl_ == nullptr) { throw std::runtime_error("sioctl_open() failed."); } if (sioctl_ondesc(hdl_, ondesc, this) == 0) { throw std::runtime_error("sioctl_ondesc() failed."); } if (sioctl_onval(hdl_, onval, this) == 0) { throw std::runtime_error("sioctl_onval() failed."); } pfds_.reserve(sioctl_nfds(hdl_)); } Sndio::Sndio(const std::string &id, const Json::Value &config) : ALabel(config, "sndio", id, "{volume}%", 1), hdl_(nullptr), pfds_(0), addr_(0), volume_(0), old_volume_(0), maxval_(0), muted_(false) { connect_to_sndio(); event_box_.show(); event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK); event_box_.signal_scroll_event().connect( sigc::mem_fun(*this, &Sndio::handleScroll)); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &Sndio::handleToggle)); thread_ = [this] { dp.emit(); int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN); if (nfds == 0) { throw std::runtime_error("sioctl_pollfd() failed."); } while (poll(pfds_.data(), nfds, -1) < 0) { if (errno != EINTR) { throw std::runtime_error("poll() failed."); } } int revents = sioctl_revents(hdl_, pfds_.data()); if (revents & POLLHUP) { spdlog::warn("sndio disconnected!"); sioctl_close(hdl_); hdl_ = nullptr; // reconnection loop while (thread_.isRunning()) { try { connect_to_sndio(); } catch(std::runtime_error const& e) { // avoid leaking hdl_ if (hdl_) { sioctl_close(hdl_); hdl_ = nullptr; } // rate limiting for the retries thread_.sleep_for(interval_); continue; } spdlog::warn("sndio reconnected!"); break; } } }; } Sndio::~Sndio() { sioctl_close(hdl_); } auto Sndio::update() -> void { auto format = format_; unsigned int vol = 100. * static_cast(volume_) / static_cast(maxval_); if (volume_ == 0) { label_.get_style_context()->add_class("muted"); } else { label_.get_style_context()->remove_class("muted"); } label_.set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_))); ALabel::update(); } auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void { std::string name{d->func}; std::string node_name{d->node0.name}; if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) { // store addr for output.level value, used in put_val addr_ = d->addr; maxval_ = d->maxval; volume_ = val; } } auto Sndio::put_val(unsigned int addr, unsigned int val) -> void { if (addr == addr_) { volume_ = val; } } bool Sndio::handleScroll(GdkEventScroll *e) { // change the volume only when no user provided // events are configured if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { return AModule::handleScroll(e); } // only try to talk to sndio if connected if (hdl_ == nullptr) return true; auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; } int step = 5; if (config_["scroll-step"].isInt()) { step = config_["scroll-step"].asInt(); } int new_volume = volume_; if (muted_) { new_volume = old_volume_; } if (dir == SCROLL_DIR::UP) { new_volume += step; } else if (dir == SCROLL_DIR::DOWN) { new_volume -= step; } new_volume = std::clamp(new_volume, 0, static_cast(maxval_)); // quits muted mode if volume changes muted_ = false; sioctl_setval(hdl_, addr_, new_volume); return true; } bool Sndio::handleToggle(GdkEventButton* const& e) { // toggle mute only when no user provided events are configured if (config_["on-click"].isString()) { return AModule::handleToggle(e); } // only try to talk to sndio if connected if (hdl_ == nullptr) return true; muted_ = !muted_; if (muted_) { // store old volume to be able to restore it later old_volume_ = volume_; sioctl_setval(hdl_, addr_, 0); } else { sioctl_setval(hdl_, addr_, old_volume_); } return true; } } /* namespace waybar::modules */ waybar-0.9.9/src/modules/sni/000077500000000000000000000000001416677246500160705ustar00rootroot00000000000000waybar-0.9.9/src/modules/sni/host.cpp000066400000000000000000000120741416677246500175550ustar00rootroot00000000000000#include "modules/sni/host.hpp" #include #include namespace waybar::modules::SNI { Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, const std::function&)>& on_add, const std::function&)>& on_remove) : bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + std::to_string(id)), object_path_("/StatusNotifierHost/" + std::to_string(id)), bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_, sigc::mem_fun(*this, &Host::busAcquired))), config_(config), bar_(bar), on_add_(on_add), on_remove_(on_remove) {} Host::~Host() { if (bus_name_id_ > 0) { Gio::DBus::unwatch_name(bus_name_id_); bus_name_id_ = 0; } if (watcher_id_ > 0) { Gio::DBus::unwatch_name(watcher_id_); watcher_id_ = 0; } g_cancellable_cancel(cancellable_); g_clear_object(&cancellable_); g_clear_object(&watcher_); } void Host::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Host::nameAppeared), sigc::mem_fun(*this, &Host::nameVanished)); } void Host::nameAppeared(const Glib::RefPtr& conn, const Glib::ustring name, const Glib::ustring& name_owner) { if (cancellable_ != nullptr) { // TODO return; } cancellable_ = g_cancellable_new(); sn_watcher_proxy_new(conn->gobj(), G_DBUS_PROXY_FLAGS_NONE, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", cancellable_, &Host::proxyReady, this); } void Host::nameVanished(const Glib::RefPtr& conn, const Glib::ustring name) { g_cancellable_cancel(cancellable_); g_clear_object(&cancellable_); g_clear_object(&watcher_); items_.clear(); } void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { GError* error = nullptr; SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { spdlog::error("Host: {}", error->message); g_error_free(error); return; } auto host = static_cast(data); host->watcher_ = watcher; if (error != nullptr) { spdlog::error("Host: {}", error->message); g_error_free(error); return; } sn_watcher_call_register_host( host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data); } void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { GError* error = nullptr; sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { spdlog::error("Host: {}", error->message); g_error_free(error); return; } auto host = static_cast(data); if (error != nullptr) { spdlog::error("Host: {}", error->message); g_error_free(error); return; } g_signal_connect(host->watcher_, "item-registered", G_CALLBACK(&Host::itemRegistered), data); g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data); auto items = sn_watcher_dup_registered_items(host->watcher_); if (items != nullptr) { for (uint32_t i = 0; items[i] != nullptr; i += 1) { host->addRegisteredItem(items[i]); } } g_strfreev(items); } void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) { auto host = static_cast(data); host->addRegisteredItem(service); } void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) { auto host = static_cast(data); auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); for (auto it = host->items_.begin(); it != host->items_.end(); ++it) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) { host->on_remove_(*it); host->items_.erase(it); break; } } } std::tuple Host::getBusNameAndObjectPath(const std::string service) { auto it = service.find('/'); if (it != std::string::npos) { return {service.substr(0, it), service.substr(it)}; } return {service, "/StatusNotifierItem"}; } void Host::addRegisteredItem(std::string service) { std::string bus_name, object_path; std::tie(bus_name, object_path) = getBusNameAndObjectPath(service); auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) { return bus_name == item->bus_name && object_path == item->object_path; }); if (it == items_.end()) { items_.emplace_back(new Item(bus_name, object_path, config_, bar_)); on_add_(items_.back()); } } } // namespace waybar::modules::SNI waybar-0.9.9/src/modules/sni/item.cpp000066400000000000000000000421241416677246500175350ustar00rootroot00000000000000#include "modules/sni/item.hpp" #include #include #include #include #include #include template <> struct fmt::formatter : formatter { template auto format(const Glib::ustring& value, FormatContext& ctx) { return formatter::format(value, ctx); } }; template <> struct fmt::formatter : formatter { bool is_printable(const Glib::VariantBase& value) { auto type = value.get_type_string(); /* Print only primitive (single character excluding 'v') and short complex types */ return (type.length() == 1 && islower(type[0]) && type[0] != 'v') || value.get_size() <= 32; } template auto format(const Glib::VariantBase& value, FormatContext& ctx) { if (is_printable(value)) { return formatter::format(value.print(), ctx); } else { return formatter::format(value.get_type_string(), ctx); } } }; namespace waybar::modules::SNI { static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name; static const unsigned UPDATE_DEBOUNCE_TIME = 10; Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar) : bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0), icon_theme(Gtk::IconTheme::create()) { if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } if (config["smooth-scrolling-threshold"].isNumeric()) { scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble(); } if (config["show-passive-items"].isBool()) { show_passive_ = config["show-passive-items"].asBool(); } auto &window = const_cast(bar).window; window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Item::onConfigure)); event_box.add(image); event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); // initial visibility event_box.show_all(); event_box.set_visible(show_passive_); cancellable_ = Gio::Cancellable::create(); auto interface = Glib::wrap(sn_item_interface_info(), true); Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name, object_path, SNI_INTERFACE_NAME, sigc::mem_fun(*this, &Item::proxyReady), cancellable_, interface); } void Item::onConfigure(GdkEventConfigure* ev) { this->updateImage(); } void Item::proxyReady(Glib::RefPtr& result) { try { this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result); /* Properties are already cached during object creation */ auto cached_properties = this->proxy_->get_cached_property_names(); for (const auto& name : cached_properties) { Glib::VariantBase value; this->proxy_->get_cached_property(value, name); setProperty(name, value); } this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal)); if (this->id.empty() || this->category.empty()) { spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); return; } this->updateImage(); } catch (const Glib::Error& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); } catch (const std::exception& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); } } template T get_variant(const Glib::VariantBase& value) { return Glib::VariantBase::cast_dynamic>(value).get(); } template <> ToolTip get_variant(const Glib::VariantBase& value) { ToolTip result; // Unwrap (sa(iiay)ss) auto container = value.cast_dynamic(value); result.icon_name = get_variant(container.get_child(0)); result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { result.text = fmt::format("{}\n{}", result.text, description); } return result; } void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); if (name == "Category") { category = get_variant(value); } else if (name == "Id") { id = get_variant(value); } else if (name == "Title") { title = get_variant(value); if (tooltip.text.empty()) { event_box.set_tooltip_markup(title); } } else if (name == "Status") { setStatus(get_variant(value)); } else if (name == "IconName") { icon_name = get_variant(value); } else if (name == "IconPixmap") { icon_pixmap = this->extractPixBuf(value.gobj()); } else if (name == "OverlayIconName") { overlay_icon_name = get_variant(value); } else if (name == "OverlayIconPixmap") { // TODO: overlay_icon_pixmap } else if (name == "AttentionIconName") { attention_icon_name = get_variant(value); } else if (name == "AttentionIconPixmap") { // TODO: attention_icon_pixmap } else if (name == "AttentionMovieName") { attention_movie_name = get_variant(value); } else if (name == "ToolTip") { tooltip = get_variant(value); if (!tooltip.text.empty()) { event_box.set_tooltip_markup(tooltip.text); } } else if (name == "IconThemePath") { icon_theme_path = get_variant(value); if (!icon_theme_path.empty()) { icon_theme->set_search_path({icon_theme_path}); } } else if (name == "Menu") { menu = get_variant(value); makeMenu(); } else if (name == "ItemIsMenu") { item_is_menu = get_variant(value); } } catch (const Glib::Error& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", id.empty() ? bus_name : id, name, value, err.what()); } catch (const std::exception& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", id.empty() ? bus_name : id, name, value, err.what()); } } void Item::setStatus(const Glib::ustring& value) { Glib::ustring lower = value.lowercase(); event_box.set_visible(show_passive_ || lower.compare("passive") != 0); auto style = event_box.get_style_context(); for (const auto& class_name : style->list_classes()) { style->remove_class(class_name); } if (lower.compare("needsattention") == 0) { // convert status to dash-case for CSS lower = "needs-attention"; } style->add_class(lower); } void Item::getUpdatedProperties() { auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)}); proxy_->call("org.freedesktop.DBus.Properties.GetAll", sigc::mem_fun(*this, &Item::processUpdatedProperties), params); }; void Item::processUpdatedProperties(Glib::RefPtr& _result) { try { auto result = proxy_->call_finish(_result); // extract "a{sv}" from VariantContainerBase Glib::Variant> properties_variant; result.get_child(properties_variant); auto properties = properties_variant.get(); for (const auto& [name, value] : properties) { if (update_pending_.count(name.raw())) { setProperty(name, const_cast(value)); } } this->updateImage(); } catch (const Glib::Error& err) { spdlog::warn("Failed to update properties: {}", err.what()); } catch (const std::exception& err) { spdlog::warn("Failed to update properties: {}", err.what()); } update_pending_.clear(); } /** * Mapping from a signal name to a set of possibly changed properties. * Commented signals are not handled by the tray module at the moment. */ static const std::map> signal2props = { {"NewTitle", {"Title"}}, {"NewIcon", {"IconName", "IconPixmap"}}, // {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, // {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, {"NewIconThemePath", {"IconThemePath"}}, {"NewToolTip", {"ToolTip"}}, {"NewStatus", {"Status"}}, // {"XAyatanaNewLabel", {"XAyatanaLabel"}}, }; void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { spdlog::trace("Tray item '{}' got signal {}", id, signal_name); auto changed = signal2props.find(signal_name.raw()); if (changed != signal2props.end()) { if (update_pending_.empty()) { /* Debounce signals and schedule update of all properties. * Based on behavior of Plasma dataengine for StatusNotifierItem. */ Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), UPDATE_DEBOUNCE_TIME); } update_pending_.insert(changed->second.begin(), changed->second.end()); } } static void pixbuf_data_deleter(const guint8* data) { g_free((void*)data); } Glib::RefPtr Item::extractPixBuf(GVariant* variant) { GVariantIter* it; g_variant_get(variant, "a(iiay)", &it); if (it == nullptr) { return Glib::RefPtr{}; } GVariant* val; gint lwidth = 0; gint lheight = 0; gint width; gint height; guchar* array = nullptr; while (g_variant_iter_loop(it, "(ii@ay)", &width, &height, &val)) { if (width > 0 && height > 0 && val != nullptr && width * height > lwidth * lheight) { auto size = g_variant_get_size(val); /* Sanity check */ if (size == 4U * width * height) { /* Find the largest image */ gconstpointer data = g_variant_get_data(val); if (data != nullptr) { if (array != nullptr) { g_free(array); } #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68 array = static_cast(g_memdup2(data, size)); #else array = static_cast(g_memdup(data, size)); #endif lwidth = width; lheight = height; } } } } g_variant_iter_free(it); if (array != nullptr) { /* argb to rgba */ for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) { guchar alpha = array[i]; array[i] = array[i + 1]; array[i + 1] = array[i + 2]; array[i + 2] = array[i + 3]; array[i + 3] = alpha; } return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, true, 8, lwidth, lheight, 4 * lwidth, &pixbuf_data_deleter); } return Glib::RefPtr{}; } void Item::updateImage() { auto pixbuf = getIconPixbuf(); auto scaled_icon_size = getScaledIconSize(); if (!pixbuf) { pixbuf = getIconByName("image-missing", getScaledIconSize()); } // If the loaded icon is not square, assume that the icon height should match the // requested icon size, but the width is allowed to be different. As such, if the // height of the image does not match the requested icon size, resize the icon such that // the aspect ratio is maintained, but the height matches the requested icon size. if (pixbuf->get_height() != scaled_icon_size) { int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window()); image.set(surface); } Glib::RefPtr Item::getIconPixbuf() { try { if (!icon_name.empty()) { std::ifstream temp(icon_name); if (temp.is_open()) { return Gdk::Pixbuf::create_from_file(icon_name); } return getIconByName(icon_name, getScaledIconSize()); } else if (icon_pixmap) { return icon_pixmap; } } catch (Glib::Error& e) { spdlog::error("Item '{}': {}", id, static_cast(e.what())); } return getIconByName("image-missing", getScaledIconSize()); } Glib::RefPtr Item::getIconByName(const std::string& name, int request_size) { int tmp_size = 0; icon_theme->rescan_if_needed(); auto sizes = icon_theme->get_icon_sizes(name.c_str()); for (auto const& size : sizes) { // -1 == scalable if (size == request_size || size == -1) { tmp_size = request_size; break; } else if (size < request_size) { tmp_size = size; } else if (size > tmp_size && tmp_size > 0) { tmp_size = request_size; break; } } if (tmp_size == 0) { tmp_size = request_size; } if (!icon_theme_path.empty() && icon_theme->lookup_icon( name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) { return icon_theme->load_icon( name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); } Glib::RefPtr default_theme = Gtk::IconTheme::get_default(); default_theme->rescan_if_needed(); return default_theme->load_icon( name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); } double Item::getScaledIconSize() { // apply the scale factor from the Gtk window to the requested icon size return icon_size * image.get_scale_factor(); } void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) { if (old_menu_pointer == reinterpret_cast(self->dbus_menu)) { self->gtk_menu = nullptr; self->dbus_menu = nullptr; } } void Item::makeMenu() { if (gtk_menu == nullptr && !menu.empty()) { dbus_menu = dbusmenu_gtkmenu_new(bus_name.data(), menu.data()); if (dbus_menu != nullptr) { g_object_ref_sink(G_OBJECT(dbus_menu)); g_object_weak_ref(G_OBJECT(dbus_menu), (GWeakNotify)onMenuDestroyed, this); gtk_menu = Glib::wrap(GTK_MENU(dbus_menu)); gtk_menu->attach_to_widget(event_box); } } } bool Item::handleClick(GdkEventButton* const& ev) { auto parameters = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(ev->x), Glib::Variant::create(ev->y)}); if ((ev->button == 1 && item_is_menu) || ev->button == 3) { makeMenu(); if (gtk_menu != nullptr) { #if GTK_CHECK_VERSION(3, 22, 0) gtk_menu->popup_at_pointer(reinterpret_cast(ev)); #else gtk_menu->popup(ev->button, ev->time); #endif return true; } else { proxy_->call("ContextMenu", parameters); return true; } } else if (ev->button == 1) { proxy_->call("Activate", parameters); return true; } else if (ev->button == 2) { proxy_->call("SecondaryActivate", parameters); return true; } return false; } bool Item::handleScroll(GdkEventScroll* const& ev) { int dx = 0, dy = 0; switch (ev->direction) { case GDK_SCROLL_UP: dy = -1; break; case GDK_SCROLL_DOWN: dy = 1; break; case GDK_SCROLL_LEFT: dx = -1; break; case GDK_SCROLL_RIGHT: dx = 1; break; case GDK_SCROLL_SMOOTH: distance_scrolled_x_ += ev->delta_x; distance_scrolled_y_ += ev->delta_y; // check against the configured threshold and ensure that the absolute value >= 1 if (distance_scrolled_x_ > scroll_threshold_) { dx = (int)lround(std::max(distance_scrolled_x_, 1.0)); distance_scrolled_x_ = 0; } else if (distance_scrolled_x_ < -scroll_threshold_) { dx = (int)lround(std::min(distance_scrolled_x_, -1.0)); distance_scrolled_x_ = 0; } if (distance_scrolled_y_ > scroll_threshold_) { dy = (int)lround(std::max(distance_scrolled_y_, 1.0)); distance_scrolled_y_ = 0; } else if (distance_scrolled_y_ < -scroll_threshold_) { dy = (int)lround(std::min(distance_scrolled_y_, -1.0)); distance_scrolled_y_ = 0; } break; } if (dx != 0) { auto parameters = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(dx), Glib::Variant::create("horizontal")}); proxy_->call("Scroll", parameters); } if (dy != 0) { auto parameters = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(dy), Glib::Variant::create("vertical")}); proxy_->call("Scroll", parameters); } return true; } } // namespace waybar::modules::SNI waybar-0.9.9/src/modules/sni/tray.cpp000066400000000000000000000024111416677246500175510ustar00rootroot00000000000000#include "modules/sni/tray.hpp" #include namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "tray", id), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), watcher_(SNI::Watcher::getInstance()), host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1), std::bind(&Tray::onRemove, this, std::placeholders::_1)) { spdlog::warn( "For a functional tray you must have libappindicator-* installed and export " "XDG_CURRENT_DESKTOP=Unity"); box_.set_name("tray"); event_box_.add(box_); if (!id.empty()) { box_.get_style_context()->add_class(id); } if (config_["spacing"].isUInt()) { box_.set_spacing(config_["spacing"].asUInt()); } nb_hosts_ += 1; dp.emit(); } void Tray::onAdd(std::unique_ptr& item) { box_.pack_start(item->event_box); dp.emit(); } void Tray::onRemove(std::unique_ptr& item) { box_.remove(item->event_box); dp.emit(); } auto Tray::update() -> void { // Show tray only when items are available box_.set_visible(!box_.get_children().empty()); // Call parent update AModule::update(); } } // namespace waybar::modules::SNI waybar-0.9.9/src/modules/sni/watcher.cpp000066400000000000000000000166211416677246500202370ustar00rootroot00000000000000#include "modules/sni/watcher.hpp" #include using namespace waybar::modules::SNI; Watcher::Watcher() : bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Watcher::busAcquired), Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(), Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)), watcher_(sn_watcher_skeleton_new()) {} Watcher::~Watcher() { if (hosts_ != nullptr) { g_slist_free_full(hosts_, gfWatchFree); hosts_ = nullptr; } if (items_ != nullptr) { g_slist_free_full(items_, gfWatchFree); items_ = nullptr; } Gio::DBus::unown_name(bus_name_id_); auto iface = G_DBUS_INTERFACE_SKELETON(watcher_); g_dbus_interface_skeleton_unexport(iface); } void Watcher::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { GError* error = nullptr; g_dbus_interface_skeleton_export( G_DBUS_INTERFACE_SKELETON(watcher_), conn->gobj(), "/StatusNotifierWatcher", &error); if (error != nullptr) { // Don't print an error when a watcher is already present if (error->code != 2) { spdlog::error("Watcher: {}", error->message); } g_error_free(error); return; } g_signal_connect_swapped( watcher_, "handle-register-item", G_CALLBACK(&Watcher::handleRegisterItem), this); g_signal_connect_swapped( watcher_, "handle-register-host", G_CALLBACK(&Watcher::handleRegisterHost), this); } gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invocation, const gchar* service) { const gchar* bus_name = service; const gchar* object_path = "/StatusNotifierHost"; if (*service == '/') { bus_name = g_dbus_method_invocation_get_sender(invocation); object_path = service; } if (g_dbus_is_name(bus_name) == FALSE) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); return TRUE; } auto watch = gfWatchFind(obj->hosts_, bus_name, object_path); if (watch != nullptr) { g_dbus_method_invocation_return_error( invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Status Notifier Host with bus name '%s' and object path '%s' is already registered", bus_name, object_path); return TRUE; } watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj); obj->hosts_ = g_slist_prepend(obj->hosts_, watch); if (!sn_watcher_get_is_host_registered(obj->watcher_)) { sn_watcher_set_is_host_registered(obj->watcher_, TRUE); sn_watcher_emit_host_registered(obj->watcher_); } sn_watcher_complete_register_host(obj->watcher_, invocation); return TRUE; } gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invocation, const gchar* service) { const gchar* bus_name = service; const gchar* object_path = "/StatusNotifierItem"; if (*service == '/') { bus_name = g_dbus_method_invocation_get_sender(invocation); object_path = service; } if (g_dbus_is_name(bus_name) == FALSE) { g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "D-Bus bus name '%s' is not valid", bus_name); return TRUE; } auto watch = gfWatchFind(obj->items_, bus_name, object_path); if (watch != nullptr) { g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered", bus_name, object_path); sn_watcher_complete_register_item(obj->watcher_, invocation); return TRUE; } watch = gfWatchNew(GF_WATCH_TYPE_ITEM, service, bus_name, object_path, obj); obj->items_ = g_slist_prepend(obj->items_, watch); obj->updateRegisteredItems(obj->watcher_); gchar* tmp = g_strdup_printf("%s%s", bus_name, object_path); sn_watcher_emit_item_registered(obj->watcher_, tmp); g_free(tmp); sn_watcher_complete_register_item(obj->watcher_, invocation); return TRUE; } Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name, const gchar* object_path) { for (GSList* l = list; l != nullptr; l = g_slist_next(l)) { auto watch = static_cast(l->data); if (g_strcmp0(watch->bus_name, bus_name) == 0 && g_strcmp0(watch->object_path, object_path) == 0) { return watch; } } return nullptr; } void Watcher::gfWatchFree(gpointer data) { auto watch = static_cast(data); if (watch->watch_id > 0) { g_bus_unwatch_name(watch->watch_id); } g_free(watch->service); g_free(watch->bus_name); g_free(watch->object_path); g_free(watch); } Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, const gchar* bus_name, const gchar* object_path, Watcher* watcher) { GfWatch* watch = g_new0(GfWatch, 1); watch->type = type; watch->watcher = watcher; watch->service = g_strdup(service); watch->bus_name = g_strdup(bus_name); watch->object_path = g_strdup(object_path); watch->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, bus_name, G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, &Watcher::nameVanished, watch, nullptr); return watch; } void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpointer data) { auto watch = static_cast(data); if (watch->type == GF_WATCH_TYPE_HOST) { watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); if (watch->watcher->hosts_ == nullptr) { sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE); sn_watcher_emit_host_registered(watch->watcher->watcher_); } } else if (watch->type == GF_WATCH_TYPE_ITEM) { watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch); watch->watcher->updateRegisteredItems(watch->watcher->watcher_); gchar* tmp = g_strdup_printf("%s%s", watch->bus_name, watch->object_path); sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp); g_free(tmp); } } void Watcher::updateRegisteredItems(SnWatcher* obj) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) { auto watch = static_cast(l->data); gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path); g_variant_builder_add(&builder, "s", item); g_free(item); } GVariant* variant = g_variant_builder_end(&builder); const gchar** items = g_variant_get_strv(variant, nullptr); sn_watcher_set_registered_items(obj, items); g_variant_unref(variant); g_free(items); } waybar-0.9.9/src/modules/sway/000077500000000000000000000000001416677246500162625ustar00rootroot00000000000000waybar-0.9.9/src/modules/sway/bar.cpp000066400000000000000000000062541416677246500175410ustar00rootroot00000000000000#include "modules/sway/bar.hpp" #include #include #include #include "bar.hpp" #include "modules/sway/ipc/ipc.hpp" namespace waybar::modules::sway { BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} { { sigc::connection handle = ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig)); ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id); handle.disconnect(); } signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate)); signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate)); ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent)); // Launch worker ipc_.setWorker([this] { try { ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("BarIpcClient::handleEvent {}", e.what()); } }); } struct swaybar_config parseConfig(const Json::Value& payload) { swaybar_config conf; if (auto id = payload["id"]; id.isString()) { conf.id = id.asString(); } if (auto mode = payload["mode"]; mode.isString()) { conf.mode = mode.asString(); } if (auto hs = payload["hidden_state"]; hs.isString()) { conf.hidden_state = hs.asString(); } return conf; } void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) { auto payload = parser_.parse(res.payload); if (auto success = payload.get("success", true); !success.asBool()) { auto err = payload.get("error", "Unknown error"); throw std::runtime_error(err.asString()); } auto config = parseConfig(payload); onConfigUpdate(config); } void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) { try { auto payload = parser_.parse(res.payload); if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) { spdlog::trace("swaybar ipc: ignore event for {}", id.asString()); return; } if (payload.isMember("visible_by_modifier")) { // visibility change for hidden bar signal_visible_(payload["visible_by_modifier"].asBool()); } else { // configuration update auto config = parseConfig(payload); signal_config_(std::move(config)); } } catch (const std::exception& e) { spdlog::error("BarIpcClient::onEvent {}", e.what()); } } void BarIpcClient::onConfigUpdate(const swaybar_config& config) { spdlog::info("config update for {}: id {}, mode {}, hidden_state {}", bar_.bar_id, config.id, config.mode, config.hidden_state); bar_config_ = config; update(); } void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) { spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier); visible_by_modifier_ = visible_by_modifier; update(); } void BarIpcClient::update() { bool visible = visible_by_modifier_; if (bar_config_.mode == "invisible") { visible = false; } else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") { visible = true; } bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE); } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/sway/ipc/000077500000000000000000000000001416677246500170355ustar00rootroot00000000000000waybar-0.9.9/src/modules/sway/ipc/client.cpp000066400000000000000000000101111416677246500210110ustar00rootroot00000000000000#include "modules/sway/ipc/client.hpp" #include namespace waybar::modules::sway { Ipc::Ipc() { const std::string& socketPath = getSocketPath(); fd_ = open(socketPath); fd_event_ = open(socketPath); } Ipc::~Ipc() { thread_.stop(); if (fd_ > 0) { // To fail the IPC header write(fd_, "close-sway-ipc", 14); close(fd_); fd_ = -1; } if (fd_event_ > 0) { write(fd_event_, "close-sway-ipc", 14); close(fd_event_); fd_event_ = -1; } } void Ipc::setWorker(std::function&& func) { thread_ = func; } const std::string Ipc::getSocketPath() const { const char* env = getenv("SWAYSOCK"); if (env != nullptr) { return std::string(env); } std::string str; { std::string str_buf; FILE* in; char buf[512] = {0}; if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) { throw std::runtime_error("Failed to get socket path"); } while (fgets(buf, sizeof(buf), in) != nullptr) { str_buf.append(buf, sizeof(buf)); } pclose(in); str = str_buf; if (str.empty()) { throw std::runtime_error("Socket path is empty"); } } if (str.back() == '\n') { str.pop_back(); } return str; } int Ipc::open(const std::string& socketPath) const { int32_t fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { throw std::runtime_error("Unable to open Unix socket"); } (void)fcntl(fd, F_SETFD, FD_CLOEXEC); struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; int l = sizeof(struct sockaddr_un); if (::connect(fd, reinterpret_cast(&addr), l) == -1) { throw std::runtime_error("Unable to connect to Sway"); } return fd; } struct Ipc::ipc_response Ipc::recv(int fd) { std::string header; header.resize(ipc_header_size_); auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); size_t total = 0; while (total < ipc_header_size_) { auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0); if (fd_event_ == -1 || fd_ == -1) { // IPC is closed so just return an empty response return {0, 0, ""}; } if (res <= 0) { throw std::runtime_error("Unable to receive IPC header"); } total += res; } auto magic = std::string(header.data(), header.data() + ipc_magic_.size()); if (magic != ipc_magic_) { throw std::runtime_error("Invalid IPC magic"); } total = 0; std::string payload; payload.resize(data32[0]); while (total < data32[0]) { auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0); if (res < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } throw std::runtime_error("Unable to receive IPC payload"); } total += res; } return {data32[0], data32[1], &payload.front()}; } struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) { std::string header; header.resize(ipc_header_size_); auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size()); data32[0] = payload.size(); data32[1] = type; if (::send(fd, header.data(), ipc_header_size_, 0) == -1) { throw std::runtime_error("Unable to send IPC header"); } if (::send(fd, payload.c_str(), payload.size(), 0) == -1) { throw std::runtime_error("Unable to send IPC payload"); } return Ipc::recv(fd); } void Ipc::sendCmd(uint32_t type, const std::string& payload) { std::lock_guard lock(mutex_); const auto res = Ipc::send(fd_, type, payload); signal_cmd.emit(res); } void Ipc::subscribe(const std::string& payload) { auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload); if (res.payload != "{\"success\": true}") { throw std::runtime_error("Unable to subscribe ipc event"); } } void Ipc::handleEvent() { const auto res = Ipc::recv(fd_event_); signal_event.emit(res); } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/sway/language.cpp000066400000000000000000000176501416677246500205620ustar00rootroot00000000000000#include "modules/sway/language.hpp" #include #include #include #include #include #include #include #include "modules/sway/ipc/ipc.hpp" #include "util/string.hpp" namespace waybar::modules::sway { const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names"; const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name"; Language::Language(const std::string& id, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, true) { is_variant_displayed = format_.find("{variant}") != std::string::npos; if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) { displayed_short_flag |= static_cast(DispayedShortFlag::ShortName); } if (format_.find("{shortDescription}") != std::string::npos) { displayed_short_flag |= static_cast(DispayedShortFlag::ShortDescription); } if (config.isMember("tooltip-format")) { tooltip_format_ = config["tooltip-format"].asString(); } ipc_.subscribe(R"(["input"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd)); ipc_.sendCmd(IPC_GET_INPUTS); // Launch worker ipc_.setWorker([this] { try { ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("Language: {}", e.what()); } }); dp.emit(); } void Language::onCmd(const struct Ipc::ipc_response& res) { if (res.type != static_cast(IPC_GET_INPUTS)) { return; } try { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); std::vector used_layouts; // Display current layout of a device with a maximum count of layouts, expecting that all will // be OK Json::ArrayIndex max_id = 0, max = 0; for (Json::ArrayIndex i = 0; i < payload.size(); i++) { auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size(); if (size > max) { max = size; max_id = i; } } for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) { used_layouts.push_back(layout.asString()); } init_layouts_map(used_layouts); set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); dp.emit(); } catch (const std::exception& e) { spdlog::error("Language: {}", e.what()); } } void Language::onEvent(const struct Ipc::ipc_response& res) { if (res.type != static_cast(IPC_EVENT_INPUT)) { return; } try { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload)["input"]; if (payload["type"].asString() == "keyboard") { set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); } dp.emit(); } catch (const std::exception& e) { spdlog::error("Language: {}", e.what()); } } auto Language::update() -> void { std::lock_guard lock(mutex_); auto display_layout = trim(fmt::format(format_, fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); label_.set_markup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim(fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); label_.set_tooltip_markup(tooltip_display_layout); } else { label_.set_tooltip_markup(display_layout); } } event_box_.show(); // Call parent update ALabel::update(); } auto Language::set_current_layout(std::string current_layout) -> void { layout_ = layouts_map_[current_layout]; } auto Language::init_layouts_map(const std::vector& used_layouts) -> void { std::map> found_by_short_names; XKBContext xkb_context; auto layout = xkb_context.next_layout(); for (; layout != nullptr; layout = xkb_context.next_layout()) { if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) == used_layouts.end()) { continue; } if (!is_variant_displayed) { auto short_name = layout->short_name; if (found_by_short_names.count(short_name) > 0) { found_by_short_names[short_name].push_back(layout); } else { found_by_short_names[short_name] = {layout}; } } layouts_map_.emplace(layout->full_name, *layout); } if (is_variant_displayed || found_by_short_names.size() == 0) { return; } std::map short_name_to_number_map; for (const auto& used_layout_name : used_layouts) { auto used_layout = &layouts_map_.find(used_layout_name)->second; auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name]; if (layouts_with_same_name_list.size() < 2) { continue; } if (short_name_to_number_map.count(used_layout->short_name) == 0) { short_name_to_number_map[used_layout->short_name] = 1; } if (displayed_short_flag != static_cast(0)) { int& number = short_name_to_number_map[used_layout->short_name]; used_layout->short_name = used_layout->short_name + std::to_string(number); used_layout->short_description = used_layout->short_description + std::to_string(number); ++number; } } } Language::XKBContext::XKBContext() { context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES); rxkb_context_include_path_append_default(context_); rxkb_context_parse_default_ruleset(context_); } auto Language::XKBContext::next_layout() -> Layout* { if (xkb_layout_ == nullptr) { xkb_layout_ = rxkb_layout_first(context_); } else { xkb_layout_ = rxkb_layout_next(xkb_layout_); } if (xkb_layout_ == nullptr) { return nullptr; } auto description = std::string(rxkb_layout_get_description(xkb_layout_)); auto name = std::string(rxkb_layout_get_name(xkb_layout_)); auto variant_ = rxkb_layout_get_variant(xkb_layout_); std::string variant = variant_ == nullptr ? "" : std::string(variant_); auto short_description_ = rxkb_layout_get_brief(xkb_layout_); std::string short_description; if (short_description_ != nullptr) { short_description = std::string(short_description_); base_layouts_by_name_.emplace(name, xkb_layout_); } else { auto base_layout = base_layouts_by_name_[name]; short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout)); } delete layout_; layout_ = new Layout{description, name, variant, short_description}; return layout_; } Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); delete layout_; } std::string Language::Layout::country_flag() const { if (short_name.size() != 2) return ""; unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00"; result[3] = short_name[0] + 0x45; result[7] = short_name[1] + 0x45; // Check if both emojis are in A-Z symbol bounds if (result[3] < 0xa6 || result[3] > 0xbf) return ""; if (result[7] < 0xa6 || result[7] > 0xbf) return ""; return std::string{reinterpret_cast(result)}; } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/sway/mode.cpp000066400000000000000000000025371416677246500177210ustar00rootroot00000000000000#include "modules/sway/mode.hpp" #include namespace waybar::modules::sway { Mode::Mode(const std::string& id, const Json::Value& config) : ALabel(config, "mode", id, "{}", 0, true) { ipc_.subscribe(R"(["mode"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent)); // Launch worker ipc_.setWorker([this] { try { ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("Mode: {}", e.what()); } }); dp.emit(); } void Mode::onEvent(const struct Ipc::ipc_response& res) { try { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); if (payload["change"] != "default") { if (payload["pango_markup"].asBool()) { mode_ = payload["change"].asString(); } else { mode_ = Glib::Markup::escape_text(payload["change"].asString()); } } else { mode_.clear(); } dp.emit(); } catch (const std::exception& e) { spdlog::error("Mode: {}", e.what()); } } auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); } else { label_.set_markup(fmt::format(format_, mode_)); if (tooltipEnabled()) { label_.set_tooltip_text(mode_); } event_box_.show(); } // Call parent update ALabel::update(); } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/sway/window.cpp000066400000000000000000000126301416677246500202770ustar00rootroot00000000000000#include "modules/sway/window.hpp" #include #include namespace waybar::modules::sway { Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { ipc_.subscribe(R"(["window","workspace"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd)); // Get Initial focused window getTree(); // Launch worker ipc_.setWorker([this] { try { ipc_.handleEvent(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); } }); } void Window::onEvent(const struct Ipc::ipc_response& res) { getTree(); } void Window::onCmd(const struct Ipc::ipc_response& res) { try { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); auto output = payload["output"].isString() ? payload["output"].asString() : ""; std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output); dp.emit(); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); } } auto Window::update() -> void { if (!old_app_id_.empty()) { bar_.window.get_style_context()->remove_class(old_app_id_); } if (app_nb_ == 0) { bar_.window.get_style_context()->remove_class("solo"); if (!bar_.window.get_style_context()->has_class("empty")) { bar_.window.get_style_context()->add_class("empty"); } } else if (app_nb_ == 1) { bar_.window.get_style_context()->remove_class("empty"); if (!bar_.window.get_style_context()->has_class("solo")) { bar_.window.get_style_context()->add_class("solo"); } if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { bar_.window.get_style_context()->add_class(app_id_); old_app_id_ = app_id_; } } else { bar_.window.get_style_context()->remove_class("solo"); bar_.window.get_style_context()->remove_class("empty"); } label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)), fmt::arg("app_id", app_id_))); if (tooltipEnabled()) { label_.set_tooltip_text(window_); } // Call parent update ALabel::update(); } int leafNodesInWorkspace(const Json::Value& node) { auto const& nodes = node["nodes"]; auto const& floating_nodes = node["floating_nodes"]; if(nodes.empty() && floating_nodes.empty()) { if(node["type"] == "workspace") return 0; else return 1; } int sum = 0; if (!nodes.empty()) { for(auto const& node : nodes) sum += leafNodesInWorkspace(node); } if (!floating_nodes.empty()) { for(auto const& node : floating_nodes) sum += leafNodesInWorkspace(node); } return sum; } std::tuple gfnWithWorkspace( const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_, Json::Value& parentWorkspace) { for(auto const& node : nodes) { if (node["output"].isString()) { output = node["output"].asString(); } // found node if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) { if ((!config_["all-outputs"].asBool() && output == bar_.output->name) || config_["all-outputs"].asBool()) { auto app_id = node["app_id"].isString() ? node["app_id"].asString() : node["window_properties"]["instance"].asString(); int nb = node.size(); if(parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace); return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()), app_id}; } } // iterate if(node["type"] == "workspace") parentWorkspace = node; auto [nb, id, name, app_id] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace); if (id > -1 && !name.empty()) { return {nb, id, name, app_id}; } // Search for floating node std::tie(nb, id, name, app_id) = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace); if (id > -1 && !name.empty()) { return {nb, id, name, app_id}; } } return {0, -1, "", ""}; } std::tuple Window::getFocusedNode( const Json::Value& nodes, std::string& output) { Json::Value placeholder = 0; return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); } void Window::getTree() { try { ipc_.sendCmd(IPC_GET_TREE); } catch (const std::exception& e) { spdlog::error("Window: {}", e.what()); } } std::string Window::rewriteTitle(const std::string& title) { const auto& rules = config_["rewrite"]; if (!rules.isObject()) { return title; } std::string res = title; for (auto it = rules.begin(); it != rules.end(); ++it) { if (it.key().isString() && it->isString()) { try { // malformated regexes will cause an exception. // in this case, log error and try the next rule. const std::regex rule{it.key().asString()}; if (std::regex_match(title, rule)) { res = std::regex_replace(res, rule, it->asString()); } } catch (const std::regex_error& e) { spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what()); } } } return res; } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/sway/workspaces.cpp000066400000000000000000000331321416677246500211510ustar00rootroot00000000000000#include "modules/sway/workspaces.hpp" #include #include #include #include namespace waybar::modules::sway { // Helper function to to assign a number to a workspace, just like sway. In fact // this is taken quite verbatim from `sway/ipc-json.c`. int Workspaces::convertWorkspaceNameToNum(std::string name) { if (isdigit(name[0])) { errno = 0; char * endptr = NULL; long long parsed_num = strtoll(name.c_str(), &endptr, 10); if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) { return -1; } else { return (int)parsed_num; } } return -1; } Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) : AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), bar_(bar), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); ipc_.subscribe(R"(["workspace"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd)); ipc_.sendCmd(IPC_GET_WORKSPACES); if (config["enable-bar-scroll"].asBool()) { auto &window = const_cast(bar_).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } // Launch worker ipc_.setWorker([this] { try { ipc_.handleEvent(); } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } }); } void Workspaces::onEvent(const struct Ipc::ipc_response &res) { try { ipc_.sendCmd(IPC_GET_WORKSPACES); } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } } void Workspaces::onCmd(const struct Ipc::ipc_response &res) { if (res.type == IPC_GET_WORKSPACES) { try { { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); workspaces_.clear(); std::copy_if(payload.begin(), payload.end(), std::back_inserter(workspaces_), [&](const auto &workspace) { return !config_["all-outputs"].asBool() ? workspace["output"].asString() == bar_.output->name : true; }); // adding persistent workspaces (as per the config file) if (config_["persistent_workspaces"].isObject()) { const Json::Value & p_workspaces = config_["persistent_workspaces"]; const std::vector p_workspaces_names = p_workspaces.getMemberNames(); for (const std::string &p_w_name : p_workspaces_names) { const Json::Value &p_w = p_workspaces[p_w_name]; auto it = std::find_if(payload.begin(), payload.end(), [&p_w_name](const Json::Value &node) { return node["name"].asString() == p_w_name; }); if (it != payload.end()) { continue; // already displayed by some bar } if (p_w.isArray() && !p_w.empty()) { // Adding to target outputs for (const Json::Value &output : p_w) { if (output.asString() == bar_.output->name) { Json::Value v; v["name"] = p_w_name; v["target_output"] = bar_.output->name; v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); break; } } } else { // Adding to all outputs Json::Value v; v["name"] = p_w_name; v["target_output"] = ""; v["num"] = convertWorkspaceNameToNum(p_w_name); workspaces_.emplace_back(std::move(v)); } } } // sway has a defined ordering of workspaces that should be preserved in // the representation displayed by waybar to ensure that commands such // as "workspace prev" or "workspace next" make sense when looking at // the workspace representation in the bar. // Due to waybar's own feature of persistent workspaces unknown to sway, // custom sorting logic is necessary to make these workspaces appear // naturally in the list of workspaces without messing up sway's // sorting. For this purpose, a custom numbering property is created // that preserves the order provided by sway while inserting numbered // persistent workspaces at their natural positions. // // All of this code assumes that sway provides numbered workspaces first // and other workspaces are sorted by their creation time. // // In a first pass, the maximum "num" value is computed to enqueue // unnumbered workspaces behind numbered ones when computing the sort // attribute. int max_num = -1; for (auto & workspace : workspaces_) { max_num = std::max(workspace["num"].asInt(), max_num); } for (auto & workspace : workspaces_) { auto workspace_num = workspace["num"].asInt(); if (workspace_num > -1) { workspace["sort"] = workspace_num; } else { workspace["sort"] = ++max_num; } } std::sort(workspaces_.begin(), workspaces_.end(), [](const Json::Value &lhs, const Json::Value &rhs) { auto lname = lhs["name"].asString(); auto rname = rhs["name"].asString(); int l = lhs["sort"].asInt(); int r = rhs["sort"].asInt(); if (l == r) { // In case both integers are the same, lexicographical // sort. The code above already ensure that this will only // happend in case of explicitly numbered workspaces. return lname < rname; } return l < r; }); } dp.emit(); } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } } } bool Workspaces::filterButtons() { bool needReorder = false; for (auto it = buttons_.begin(); it != buttons_.end();) { auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) { return node["name"].asString() == it->first; }); if (ws == workspaces_.end() || (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) { it = buttons_.erase(it); needReorder = true; } else { ++it; } } return needReorder; } auto Workspaces::update() -> void { std::lock_guard lock(mutex_); bool needReorder = filterButtons(); for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) { auto bit = buttons_.find((*it)["name"].asString()); if (bit == buttons_.end()) { needReorder = true; } auto &button = bit == buttons_.end() ? addButton(*it) : bit->second; if ((*it)["focused"].asBool()) { button.get_style_context()->add_class("focused"); } else { button.get_style_context()->remove_class("focused"); } if ((*it)["visible"].asBool()) { button.get_style_context()->add_class("visible"); } else { button.get_style_context()->remove_class("visible"); } if ((*it)["urgent"].asBool()) { button.get_style_context()->add_class("urgent"); } else { button.get_style_context()->remove_class("urgent"); } if ((*it)["target_output"].isString()) { button.get_style_context()->add_class("persistent"); } else { button.get_style_context()->remove_class("persistent"); } if ((*it)["output"].isString()) { if (((*it)["output"].asString()) == bar_.output->name) { button.get_style_context()->add_class("current_output"); } else { button.get_style_context()->remove_class("current_output"); } } else { button.get_style_context()->remove_class("current_output"); } if (needReorder) { box_.reorder_child(button, it - workspaces_.begin()); } std::string output = (*it)["name"].asString(); if (config_["format"].isString()) { auto format = config_["format"].asString(); output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString())); } if (!config_["disable-markup"].asBool()) { static_cast(button.get_children()[0])->set_markup(output); } else { button.set_label(output); } onButtonReady(*it, button); } // Call parent update AModule::update(); } Gtk::Button &Workspaces::addButton(const Json::Value &node) { auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString()); auto &&button = pair.first->second; box_.pack_start(button, false, false, 0); button.set_name("sway-workspace-" + node["name"].asString()); button.set_relief(Gtk::RELIEF_NONE); if (!config_["disable-click"].asBool()) { button.signal_pressed().connect([this, node] { try { if (node["target_output"].isString()) { ipc_.sendCmd( IPC_COMMAND, fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + workspace_switch_cmd_, "--no-auto-back-and-forth", node["name"].asString(), node["target_output"].asString(), "--no-auto-back-and-forth", node["name"].asString())); } else { ipc_.sendCmd( IPC_COMMAND, fmt::format("workspace {} \"{}\"", config_["disable-auto-back-and-forth"].asBool() ? "--no-auto-back-and-forth" : "", node["name"].asString())); } } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } }); } return button; } std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) { std::vector keys = {name, "urgent", "focused", "visible", "default"}; for (auto const &key : keys) { if (key == "focused" || key == "visible" || key == "urgent") { if (config_["format-icons"][key].isString() && node[key].asBool()) { return config_["format-icons"][key].asString(); } } else if (config_["format_icons"]["persistent"].isString() && node["target_output"].isString()) { return config_["format-icons"]["persistent"].asString(); } else if (config_["format-icons"][key].isString()) { return config_["format-icons"][key].asString(); } else if (config_["format-icons"][trimWorkspaceName(key)].isString()) { return config_["format-icons"][trimWorkspaceName(key)].asString(); } } return name; } bool Workspaces::handleScroll(GdkEventScroll *e) { if (gdk_event_get_pointer_emulated((GdkEvent *)e)) { /** * Ignore emulated scroll events on window */ return false; } auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; } std::string name; { std::lock_guard lock(mutex_); auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) { return workspace["focused"].asBool(); }); if (it == workspaces_.end()) { return true; } if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { name = getCycleWorkspace(it, false); } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { name = getCycleWorkspace(it, true); } else { return true; } if (name == (*it)["name"].asString()) { return true; } } try { ipc_.sendCmd( IPC_COMMAND, fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name)); } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); } return true; } const std::string Workspaces::getCycleWorkspace(std::vector::iterator it, bool prev) const { if (prev && it == workspaces_.begin() && !config_["disable-scroll-wraparound"].asBool()) { return (*(--workspaces_.end()))["name"].asString(); } if (prev && it != workspaces_.begin()) --it; else if (!prev && it != workspaces_.end()) ++it; if (!prev && it == workspaces_.end()) { if (config_["disable-scroll-wraparound"].asBool()) { --it; } else { return (*(workspaces_.begin()))["name"].asString(); } } return (*it)["name"].asString(); } std::string Workspaces::trimWorkspaceName(std::string name) { std::size_t found = name.find(':'); if (found != std::string::npos) { return name.substr(found + 1); } return name; } void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { if (config_["current-only"].asBool()) { if (node["focused"].asBool()) { button.show(); } else { button.hide(); } } else { button.show(); } } } // namespace waybar::modules::sway waybar-0.9.9/src/modules/temperature.cpp000066400000000000000000000060011416677246500203350ustar00rootroot00000000000000#include "modules/temperature.hpp" #include waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config) : ALabel(config, "temperature", id, "{temperatureC}°C", 10) { if (config_["hwmon-path"].isString()) { file_path_ = config_["hwmon-path"].asString(); } else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) { file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())).path().string() + "/" + config_["input-filename"].asString(); } else { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); } std::ifstream temp(file_path_); if (!temp.is_open()) { throw std::runtime_error("Can't open " + file_path_); } thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; } auto waybar::modules::Temperature::update() -> void { auto temperature = getTemperature(); uint16_t temperature_c = std::round(temperature); uint16_t temperature_f = std::round(temperature * 1.8 + 32); uint16_t temperature_k = std::round(temperature + 273.15); auto critical = isCritical(temperature_c); auto format = format_; if (critical) { format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format; label_.get_style_context()->add_class("critical"); } else { label_.get_style_context()->remove_class("critical"); } auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); if (tooltipEnabled()) { std::string tooltip_format = "{temperatureC}°C"; if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } // Call parent update ALabel::update(); } float waybar::modules::Temperature::getTemperature() { std::ifstream temp(file_path_); if (!temp.is_open()) { throw std::runtime_error("Can't open " + file_path_); } std::string line; if (temp.good()) { getline(temp, line); } temp.close(); auto temperature_c = std::strtol(line.c_str(), nullptr, 10) / 1000.0; return temperature_c; } bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); } waybar-0.9.9/src/modules/wlr/000077500000000000000000000000001416677246500161035ustar00rootroot00000000000000waybar-0.9.9/src/modules/wlr/taskbar.cpp000066400000000000000000000643111416677246500202430ustar00rootroot00000000000000#include "modules/wlr/taskbar.hpp" #include "glibmm/error.h" #include "glibmm/fileutils.h" #include "glibmm/refptr.h" #include "util/format.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace waybar::modules::wlr { /* String manipulation methods */ const std::string WHITESPACE = " \n\r\t\f\v"; static std::string ltrim(const std::string& s) { size_t start = s.find_first_not_of(WHITESPACE); return (start == std::string::npos) ? "" : s.substr(start); } static std::string rtrim(const std::string& s) { size_t end = s.find_last_not_of(WHITESPACE); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } static std::string trim(const std::string& s) { return rtrim(ltrim(s)); } /* Icon loading functions */ static std::vector search_prefix() { std::vector prefixes = {""}; auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); if (!xdg_data_dirs) { prefixes.emplace_back("/usr/share/"); prefixes.emplace_back("/usr/local/share/"); } else { std::string xdg_data_dirs_str(xdg_data_dirs); size_t start = 0, end = 0; do { end = xdg_data_dirs_str.find(':', start); auto p = xdg_data_dirs_str.substr(start, end-start); prefixes.push_back(trim(p) + "/"); start = end == std::string::npos ? end : end + 1; } while(end != std::string::npos); } std::string home_dir = std::getenv("HOME"); prefixes.push_back(home_dir + "/.local/share/"); for (auto& p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p); return prefixes; } static Glib::RefPtr load_icon_from_file(std::string icon_path, int size) { try { auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size); return pb; } catch(...) { return {}; } } static Glib::RefPtr get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); std::vector app_folders = { "", "applications/", "applications/kde/", "applications/org.kde." }; std::vector suffixes = { "", ".desktop" }; for (auto& prefix : prefixes) { for (auto& folder : app_folders) { for (auto& suffix : suffixes) { auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); if (!app_info_) { continue; } return app_info_; } } } return {}; } Glib::RefPtr get_desktop_app_info(const std::string &app_id) { auto app_info = get_app_info_by_name(app_id); if (app_info) { return app_info; } std::string desktop_file = ""; gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); if (desktop_list != nullptr && desktop_list[0] != nullptr) { for (size_t i=0; desktop_list[0][i]; i++) { if (desktop_file == "") { desktop_file = desktop_list[0][i]; } else { auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); auto startup_class = tmp_info->get_startup_wm_class(); if (startup_class == app_id) { desktop_file = desktop_list[0][i]; break; } } } g_strfreev(desktop_list[0]); } g_free(desktop_list); return get_app_info_by_name(desktop_file); } void Task::set_app_info_from_app_id_list(const std::string& app_id_list) { std::string app_id; std::istringstream stream(app_id_list); /* Wayfire sends a list of app-id's in space separated format, other compositors * send a single app-id, but in any case this works fine */ while (stream >> app_id) { app_info_ = get_desktop_app_info(app_id); if (app_info_) { return; } auto lower_app_id = app_id; std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), [](char c){ return std::tolower(c); }); app_info_ = get_desktop_app_info(lower_app_id); if (app_info_) { return; } size_t start = 0, end = app_id.size(); start = app_id.rfind(".", end); std::string app_name = app_id.substr(start+1, app_id.size()); app_info_ = get_desktop_app_info(app_name); if (app_info_) { return; } start = app_id.find("-"); app_name = app_id.substr(0, start); app_info_ = get_desktop_app_info(app_name); } } static std::string get_icon_name_from_icon_theme(const Glib::RefPtr& icon_theme, const std::string &app_id) { if (icon_theme->lookup_icon(app_id, 24)) return app_id; return ""; } bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size) { std::string ret_icon_name = "unknown"; if (app_info) { std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); if (!icon_name.empty()) { ret_icon_name = icon_name; } else { if (app_info->get_icon()) { ret_icon_name = app_info->get_icon()->to_string(); } } } Glib::RefPtr pixbuf; try { pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); } catch(...) { if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) pixbuf = load_icon_from_file(ret_icon_name, size); else pixbuf = {}; } if (pixbuf) { image.set(pixbuf); return true; } return false; } /* Task class implementation */ uint32_t Task::global_id = 0; static void tl_handle_title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title) { return static_cast(data)->handle_title(title); } static void tl_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id) { return static_cast(data)->handle_app_id(app_id); } static void tl_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { return static_cast(data)->handle_output_enter(output); } static void tl_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { return static_cast(data)->handle_output_leave(output); } static void tl_handle_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { return static_cast(data)->handle_state(state); } static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { return static_cast(data)->handle_done(); } static void tl_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) { /* This is explicitly left blank */ } static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { return static_cast(data)->handle_closed(); } static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_impl = { .title = tl_handle_title, .app_id = tl_handle_app_id, .output_enter = tl_handle_output_enter, .output_leave = tl_handle_output_leave, .state = tl_handle_state, .done = tl_handle_done, .closed = tl_handle_closed, .parent = tl_handle_parent, }; Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, id_{global_id++}, content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); button_.set_relief(Gtk::RELIEF_NONE); content_.add(text_before_); content_.add(icon_); content_.add(text_after_); content_.show(); button_.add(content_); format_before_.clear(); format_after_.clear(); if (config_["format"].isString()) { /* The user defined a format string, use it */ auto format = config_["format"].asString(); if (format.find("{name}") != std::string::npos) { with_name_ = true; } auto icon_pos = format.find("{icon}"); if (icon_pos == 0) { with_icon_ = true; format_after_ = format.substr(6); } else if (icon_pos == std::string::npos) { format_before_ = format; } else { with_icon_ = true; format_before_ = format.substr(0, icon_pos); format_after_ = format.substr(icon_pos + 6); } } else { /* The default is to only show the icon */ with_icon_ = true; } /* Strip spaces at the beginning and end of the format strings */ format_tooltip_.clear(); if (!config_["tooltip"].isBool() || config_["tooltip"].asBool()) { if (config_["tooltip-format"].isString()) format_tooltip_ = config_["tooltip-format"].asString(); else format_tooltip_ = "{title}"; } /* Handle click events if configured */ if (config_["on-click"].isString() || config_["on-click-middle"].isString() || config_["on-click-right"].isString()) { button_.add_events(Gdk::BUTTON_PRESS_MASK); button_.signal_button_press_event().connect( sigc::mem_fun(*this, &Task::handle_clicked), false); } } Task::~Task() { if (handle_) { zwlr_foreign_toplevel_handle_v1_destroy(handle_); handle_ = nullptr; } if (button_visible_) { tbar_->remove_button(button_); button_visible_ = false; } } std::string Task::repr() const { std::stringstream ss; ss << "Task (" << id_ << ") " << title_ << " [" << app_id_ << "] <" << (active() ? "A" : "a") << (maximized() ? "M" : "m") << (minimized() ? "I" : "i") << (fullscreen() ? "F" : "f") << ">"; return ss.str(); } std::string Task::state_string(bool shortened) const { std::stringstream ss; if (shortened) ss << (minimized() ? "m" : "") << (maximized() ? "M" : "") << (active() ? "A" : "") << (fullscreen() ? "F" : ""); else ss << (minimized() ? "minimized " : "") << (maximized() ? "maximized " : "") << (active() ? "active " : "") << (fullscreen() ? "fullscreen " : ""); std::string res = ss.str(); if (shortened || res.empty()) return res; else return res.substr(0, res.size() - 1); } void Task::handle_title(const char *title) { title_ = title; hide_if_ignored(); } void Task::hide_if_ignored() { if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { ignored_ = true; if (button_visible_) { auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); handle_output_leave(output); } } else { bool is_was_ignored = ignored_; ignored_ = false; if (is_was_ignored) { auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); handle_output_enter(output); } } } void Task::handle_app_id(const char *app_id) { app_id_ = app_id; hide_if_ignored(); auto ids_replace_map = tbar_->app_ids_replace_map(); if (ids_replace_map.count(app_id_)) { auto replaced_id = ids_replace_map[app_id_]; spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id)); app_id_ = replaced_id; } if (!with_icon_ && !with_name_) { return; } set_app_info_from_app_id_list(app_id_); name_ = app_info_ ? app_info_->get_display_name() : app_id; if (!with_icon_) { return; } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; bool found = false; for (auto& icon_theme : tbar_->icon_themes()) { if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { found = true; break; } } if (found) icon_.show(); else spdlog::debug("Couldn't find icon for {}", app_id_); } void Task::handle_output_enter(struct wl_output *output) { if (ignored_) { spdlog::debug("{} is ignored", repr()); return; } spdlog::debug("{} entered output {}", repr(), (void*)output); if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { /* The task entered the output of the current bar make the button visible */ tbar_->add_button(button_); button_.show(); button_visible_ = true; spdlog::debug("{} now visible on {}", repr(), bar_.output->name); } } void Task::handle_output_leave(struct wl_output *output) { spdlog::debug("{} left output {}", repr(), (void*)output); if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) { /* The task left the output of the current bar, make the button invisible */ tbar_->remove_button(button_); button_.hide(); button_visible_ = false; spdlog::debug("{} now invisible on {}", repr(), bar_.output->name); } } void Task::handle_state(struct wl_array *state) { state_ = 0; size_t size = state->size / sizeof(uint32_t); for (size_t i = 0; i < size; ++i) { auto entry = static_cast(state->data)[i]; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) state_ |= MAXIMIZED; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) state_ |= MINIMIZED; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) state_ |= ACTIVE; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) state_ |= FULLSCREEN; } } void Task::handle_done() { spdlog::debug("{} changed", repr()); if (state_ & MAXIMIZED) { button_.get_style_context()->add_class("maximized"); } else if (!(state_ & MAXIMIZED)) { button_.get_style_context()->remove_class("maximized"); } if (state_ & MINIMIZED) { button_.get_style_context()->add_class("minimized"); } else if (!(state_ & MINIMIZED)) { button_.get_style_context()->remove_class("minimized"); } if (state_ & ACTIVE) { button_.get_style_context()->add_class("active"); } else if (!(state_ & ACTIVE)) { button_.get_style_context()->remove_class("active"); } if (state_ & FULLSCREEN) { button_.get_style_context()->add_class("fullscreen"); } else if (!(state_ & FULLSCREEN)) { button_.get_style_context()->remove_class("fullscreen"); } if (config_["active-first"].isBool() && config_["active-first"].asBool() && active()) tbar_->move_button(button_, 0); tbar_->dp.emit(); } void Task::handle_closed() { spdlog::debug("{} closed", repr()); zwlr_foreign_toplevel_handle_v1_destroy(handle_); handle_ = nullptr; if (button_visible_) { tbar_->remove_button(button_); button_visible_ = false; } tbar_->remove_task(id_); } bool Task::handle_clicked(GdkEventButton *bt) { std::string action; if (config_["on-click"].isString() && bt->button == 1) action = config_["on-click"].asString(); else if (config_["on-click-middle"].isString() && bt->button == 2) action = config_["on-click-middle"].asString(); else if (config_["on-click-right"].isString() && bt->button == 3) action = config_["on-click-right"].asString(); if (action.empty()) return true; else if (action == "activate") activate(); else if (action == "minimize") minimize(!minimized()); else if (action == "minimize-raise"){ if (minimized()) minimize(false); else if (active()) minimize(true); else activate(); } else if (action == "maximize") maximize(!maximized()); else if (action == "fullscreen") fullscreen(!fullscreen()); else if (action == "close") close(); else spdlog::warn("Unknown action {}", action); return true; } bool Task::operator==(const Task &o) const { return o.id_ == id_; } bool Task::operator!=(const Task &o) const { return o.id_ != id_; } void Task::update() { bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; std::string title = title_; std::string name = name_; std::string app_id = app_id_; if (markup) { title = Glib::Markup::escape_text(title); name = Glib::Markup::escape_text(name); app_id = Glib::Markup::escape_text(app_id); } if (!format_before_.empty()) { auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) ); if (markup) text_before_.set_markup(txt); else text_before_.set_label(txt); text_before_.show(); } if (!format_after_.empty()) { auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) ); if (markup) text_after_.set_markup(txt); else text_after_.set_label(txt); text_after_.show(); } if (!format_tooltip_.empty()) { auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) ); if (markup) button_.set_tooltip_markup(txt); else button_.set_tooltip_text(txt); } } void Task::maximize(bool set) { if (set) zwlr_foreign_toplevel_handle_v1_set_maximized(handle_); else zwlr_foreign_toplevel_handle_v1_unset_maximized(handle_); } void Task::minimize(bool set) { if (set) zwlr_foreign_toplevel_handle_v1_set_minimized(handle_); else zwlr_foreign_toplevel_handle_v1_unset_minimized(handle_); } void Task::activate() { zwlr_foreign_toplevel_handle_v1_activate(handle_, seat_); } void Task::fullscreen(bool set) { if (zwlr_foreign_toplevel_handle_v1_get_version(handle_) < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) { spdlog::warn("Foreign toplevel manager server does not support for set/unset fullscreen."); return; } if (set) zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr); else zwlr_foreign_toplevel_handle_v1_unset_fullscreen(handle_); } void Task::close() { zwlr_foreign_toplevel_handle_v1_close(handle_); } /* Taskbar class implementation */ static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { static_cast(data)->register_manager(registry, name, version); } else if (std::strcmp(interface, wl_seat_interface.name) == 0) { static_cast(data)->register_seat(registry, name, version); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { /* Nothing to do here */ } static const wl_registry_listener registry_listener_impl = { .global = handle_global, .global_remove = handle_global_remove }; Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config) : waybar::AModule(config, "taskbar", id, false, false), bar_(bar), box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, manager_{nullptr}, seat_{nullptr} { box_.set_name("taskbar"); if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); if (!manager_) { spdlog::error("Failed to register as toplevel manager"); return; } if (!seat_) { spdlog::error("Failed to get wayland seat"); return; } /* Get the configured icon theme if specified */ if (config_["icon-theme"].isArray()) { for (auto& c : config_["icon-theme"]) { auto it_name = c.asString(); auto it = Gtk::IconTheme::create(); it->set_custom_theme(it_name); spdlog::debug("Use custom icon theme: {}", it_name); icon_themes_.push_back(it); } } else if (config_["icon-theme"].isString()) { auto it_name = config_["icon-theme"].asString(); auto it = Gtk::IconTheme::create(); it->set_custom_theme(it_name); spdlog::debug("Use custom icon theme: {}", it_name); icon_themes_.push_back(it); } // Load ignore-list if (config_["ignore-list"].isArray()) { for (auto& app_name : config_["ignore-list"]) { ignore_list_.emplace(app_name.asString()); } } // Load app_id remappings if (config_["app_ids-mapping"].isObject()) { const Json::Value& mapping = config_["app_ids-mapping"]; const std::vector app_ids = config_["app_ids-mapping"].getMemberNames(); for (auto& app_id : app_ids) { app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); } } icon_themes_.push_back(Gtk::IconTheme::get_default()); } Taskbar::~Taskbar() { if (manager_) { struct wl_display *display = Client::inst()->wl_display; /* * Send `stop` request and wait for one roundtrip. * This is not quite correct as the protocol encourages us to wait for the .finished event, * but it should work with wlroots foreign toplevel manager implementation. */ zwlr_foreign_toplevel_manager_v1_stop(manager_); wl_display_roundtrip(display); if (manager_) { spdlog::warn("Foreign toplevel manager destroyed before .finished event"); zwlr_foreign_toplevel_manager_v1_destroy(manager_); manager_ = nullptr; } } } void Taskbar::update() { for (auto& t : tasks_) { t->update(); } AModule::update(); } static void tm_handle_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, struct zwlr_foreign_toplevel_handle_v1 *tl_handle) { return static_cast(data)->handle_toplevel_create(tl_handle); } static void tm_handle_finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) { return static_cast(data)->handle_finished(); } static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { .toplevel = tm_handle_toplevel, .finished = tm_handle_finished, }; void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint32_t version) { if (manager_) { spdlog::warn("Register foreign toplevel manager again although already existing!"); return; } if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) { spdlog::warn("Foreign toplevel manager server does not have the appropriate version." " To be able to use all features, you need at least version 2, but server is version {}", version); } // limit version to a highest supported by the client protocol file version = std::min(version, zwlr_foreign_toplevel_manager_v1_interface.version); manager_ = static_cast(wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version)); if (manager_) zwlr_foreign_toplevel_manager_v1_add_listener(manager_, &toplevel_manager_impl, this); else spdlog::debug("Failed to register manager"); } void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_t version) { if (seat_) { spdlog::warn("Register seat again although already existing!"); return; } version = std::min(version, wl_seat_interface.version); seat_ = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } void Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *tl_handle) { tasks_.push_back(std::make_unique(bar_, config_, this, tl_handle, seat_)); } void Taskbar::handle_finished() { zwlr_foreign_toplevel_manager_v1_destroy(manager_); manager_ = nullptr; } void Taskbar::add_button(Gtk::Button &bt) { box_.pack_start(bt, false, false); } void Taskbar::move_button(Gtk::Button &bt, int pos) { box_.reorder_child(bt, pos); } void Taskbar::remove_button(Gtk::Button &bt) { box_.remove(bt); } void Taskbar::remove_task(uint32_t id) { auto it = std::find_if(std::begin(tasks_), std::end(tasks_), [id](const TaskPtr &p) { return p->id() == id; }); if (it == std::end(tasks_)) { spdlog::warn("Can't find task with id {}", id); return; } tasks_.erase(it); } bool Taskbar::show_output(struct wl_output *output) const { return output == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); } bool Taskbar::all_outputs() const { return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } const std::vector>& Taskbar::icon_themes() const { return icon_themes_; } const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } } /* namespace waybar::modules::wlr */ waybar-0.9.9/src/modules/wlr/workspace_manager.cpp000066400000000000000000000332661416677246500223110ustar00rootroot00000000000000#include "modules/wlr/workspace_manager.hpp" #include #include #include #include #include #include #include "gtkmm/widget.h" #include "modules/wlr/workspace_manager_binding.hpp" namespace waybar::modules::wlr { uint32_t WorkspaceGroup::workspace_global_id = 0; uint32_t WorkspaceManager::group_global_id = 0; std::map Workspace::icons_map_; WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config) : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { auto config_sort_by_name = config_["sort-by-name"]; if (config_sort_by_name.isBool()) { sort_by_name_ = config_sort_by_name.asBool(); } auto config_sort_by_coordinates = config_["sort-by-coordinates"]; if (config_sort_by_coordinates.isBool()) { sort_by_coordinates_ = config_sort_by_coordinates.asBool(); } auto config_all_outputs = config_["all-outputs"]; if (config_all_outputs.isBool()) { all_outputs_ = config_all_outputs.asBool(); } auto config_active_only = config_["active-only"]; if (config_active_only.isBool()) { active_only_ = config_active_only.asBool(); creation_delayed_ = active_only_; } box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); add_registry_listener(this); if (!workspace_manager_) { return; } } auto WorkspaceManager::workspace_comparator() const -> std::function &, std::unique_ptr &)> { return [=](std::unique_ptr &lhs, std::unique_ptr &rhs) { auto is_name_less = lhs->get_name() < rhs->get_name(); auto is_name_eq = lhs->get_name() == rhs->get_name(); auto is_coords_less = lhs->get_coords() < rhs->get_coords(); if (sort_by_name_) { if (sort_by_coordinates_) { return is_name_eq ? is_coords_less : is_name_less; } else { return is_name_less; } } if (sort_by_coordinates_) { return is_coords_less; } return lhs->id() < rhs->id(); }; } auto WorkspaceManager::sort_workspaces() -> void { std::vector>> all_workspaces; for (auto &group : groups_) { auto &group_workspaces = group->workspaces(); all_workspaces.reserve(all_workspaces.size() + std::distance(group_workspaces.begin(), group_workspaces.end())); if (!active_only()) { all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end()); continue; } for (auto &workspace : group_workspaces) { if (!workspace->is_active()) { continue; } all_workspaces.push_back(workspace); } } std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator()); for (size_t i = 0; i < all_workspaces.size(); ++i) { box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i); } } auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void { if (workspace_manager_) { spdlog::warn("Register workspace manager again although already registered!"); return; } if (version != 1) { spdlog::warn("Using different workspace manager protocol version: {}", version); } workspace_manager_ = workspace_manager_bind(registry, name, version, this); } auto WorkspaceManager::handle_workspace_group_create( zext_workspace_group_handle_v1 *workspace_group_handle) -> void { auto new_id = ++group_global_id; groups_.push_back( std::make_unique(bar_, box_, config_, *this, workspace_group_handle, new_id)); spdlog::debug("Workspace group {} created", new_id); } auto WorkspaceManager::handle_finished() -> void { zext_workspace_manager_v1_destroy(workspace_manager_); workspace_manager_ = nullptr; } auto WorkspaceManager::handle_done() -> void { for (auto &group : groups_) { group->handle_done(); } dp.emit(); } auto WorkspaceManager::update() -> void { for (auto &group : groups_) { group->update(); } if (creation_delayed()) { creation_delayed_ = false; sort_workspaces(); } AModule::update(); } WorkspaceManager::~WorkspaceManager() { if (!workspace_manager_) { return; } zext_workspace_manager_v1_destroy(workspace_manager_); workspace_manager_ = nullptr; } auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { auto it = std::find_if(groups_.begin(), groups_.end(), [id](const std::unique_ptr &g) { return g->id() == id; }); if (it == groups_.end()) { spdlog::warn("Can't find group with id {}", id); return; } groups_.erase(it); } auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); } WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config, WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) : bar_(bar), box_(box), config_(config), workspace_manager_(manager), workspace_group_handle_(workspace_group_handle), id_(id) { add_workspace_group_listener(workspace_group_handle, this); } auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); } auto WorkspaceGroup::creation_delayed() const -> bool { return workspace_manager_.creation_delayed(); } auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { box_.pack_start(button, false, false); } WorkspaceGroup::~WorkspaceGroup() { if (!workspace_group_handle_) { return; } zext_workspace_group_handle_v1_destroy(workspace_group_handle_); workspace_group_handle_ = nullptr; } auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void { auto new_id = ++workspace_global_id; workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace, new_id)); spdlog::debug("Workspace {} created", new_id); } auto WorkspaceGroup::handle_remove() -> void { zext_workspace_group_handle_v1_destroy(workspace_group_handle_); workspace_group_handle_ = nullptr; workspace_manager_.remove_workspace_group(id_); } auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { spdlog::debug("Output {} assigned to {} group", (void *)output, id_); output_ = output; if (!is_visible() || workspace_manager_.creation_delayed()) { return; } for (auto &workspace : workspaces_) { add_button(workspace->get_button_ref()); } } auto WorkspaceGroup::is_visible() const -> bool { return output_ != nullptr && (workspace_manager_.all_outputs() || output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())); } auto WorkspaceGroup::handle_output_leave() -> void { spdlog::debug("Output {} remove from {} group", (void *)output_, id_); output_ = nullptr; if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { return; } for (auto &workspace : workspaces_) { remove_button(workspace->get_button_ref()); } } auto WorkspaceGroup::update() -> void { for (auto &workspace : workspaces_) { if (workspace_manager_.creation_delayed()) { add_button(workspace->get_button_ref()); if (is_visible() && (workspace->is_active() || workspace->is_urgent())) { workspace->show(); } } workspace->update(); } } auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [id](const std::unique_ptr &w) { return w->id() == id; }); if (it == workspaces_.end()) { spdlog::warn("Can't find workspace with id {}", id); return; } workspaces_.erase(it); } auto WorkspaceGroup::handle_done() -> void { need_to_sort = false; if (!is_visible()) { return; } for (auto &workspace : workspaces_) { workspace->handle_done(); } if (creation_delayed()) { return; } if (!workspace_manager_.all_outputs()) { sort_workspaces(); } else { workspace_manager_.sort_workspaces(); } } auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } auto WorkspaceGroup::sort_workspaces() -> void { std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator()); for (size_t i = 0; i < workspaces_.size(); ++i) { box_.reorder_child(workspaces_[i]->get_button_ref(), i); } } auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); } Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, zext_workspace_handle_v1 *workspace, uint32_t id) : bar_(bar), config_(config), workspace_group_(workspace_group), workspace_handle_(workspace), id_(id) { add_workspace_listener(workspace, this); auto config_format = config["format"]; format_ = config_format.isString() ? config_format.asString() : "{name}"; with_icon_ = format_.find("{icon}") != std::string::npos; if (with_icon_ && icons_map_.empty()) { auto format_icons = config["format-icons"]; for (auto &name : format_icons.getMemberNames()) { icons_map_.emplace(name, format_icons[name].asString()); } } /* Handle click events if configured */ if (config_["on-click"].isString() || config_["on-click-middle"].isString() || config_["on-click-right"].isString()) { button_.add_events(Gdk::BUTTON_PRESS_MASK); button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), false); } button_.set_relief(Gtk::RELIEF_NONE); content_.set_center_widget(label_); button_.add(content_); if (!workspace_group.is_visible()) { return; } workspace_group.add_button(button_); button_.show_all(); } Workspace::~Workspace() { workspace_group_.remove_button(button_); if (!workspace_handle_) { return; } zext_workspace_handle_v1_destroy(workspace_handle_); workspace_handle_ = nullptr; } auto Workspace::update() -> void { label_.set_markup(fmt::format( format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : ""))); } auto Workspace::handle_state(const std::vector &state) -> void { state_ = 0; for (auto state_entry : state) { switch (state_entry) { case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE: state_ |= (uint32_t)State::ACTIVE; break; case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT: state_ |= (uint32_t)State::URGENT; break; case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN: state_ |= (uint32_t)State::HIDDEN; break; } } } auto Workspace::handle_remove() -> void { zext_workspace_handle_v1_destroy(workspace_handle_); workspace_handle_ = nullptr; workspace_group_.remove_workspace(id_); } auto add_or_remove_class(Glib::RefPtr context, bool condition, const std::string &class_name) { if (condition) { context->add_class(class_name); } else { context->remove_class(class_name); } } auto Workspace::handle_done() -> void { spdlog::debug("Workspace {} changed to state {}", id_, state_); auto style_context = button_.get_style_context(); add_or_remove_class(style_context, is_active(), "active"); add_or_remove_class(style_context, is_urgent(), "urgent"); add_or_remove_class(style_context, is_hidden(), "hidden"); if (workspace_group_.creation_delayed()) { return; } if (workspace_group_.active_only() && (is_active() || is_urgent())) { button_.show_all(); } else if (workspace_group_.active_only() && !(is_active() || is_urgent())) { button_.hide(); } } auto Workspace::get_icon() -> std::string { if (is_active()) { auto active_icon_it = icons_map_.find("active"); if (active_icon_it != icons_map_.end()) { return active_icon_it->second; } } auto named_icon_it = icons_map_.find(name_); if (named_icon_it != icons_map_.end()) { return named_icon_it->second; } auto default_icon_it = icons_map_.find("default"); if (default_icon_it != icons_map_.end()) { return default_icon_it->second; } return name_; } auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { std::string action; if (config_["on-click"].isString() && bt->button == 1) { action = config_["on-click"].asString(); } else if (config_["on-click-middle"].isString() && bt->button == 2) { action = config_["on-click-middle"].asString(); } else if (config_["on-click-right"].isString() && bt->button == 3) { action = config_["on-click-right"].asString(); } if (action.empty()) return true; else if (action == "activate") { zext_workspace_handle_v1_activate(workspace_handle_); } else if (action == "close") { zext_workspace_handle_v1_remove(workspace_handle_); } else { spdlog::warn("Unknown action {}", action); } workspace_group_.commit(); return true; } auto Workspace::show() -> void { button_.show_all(); } auto Workspace::hide() -> void { button_.hide(); } auto Workspace::handle_name(const std::string &name) -> void { if (name_ != name) { workspace_group_.set_need_to_sort(); } name_ = name; } auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { if (coordinates_ != coordinates) { workspace_group_.set_need_to_sort(); } coordinates_ = coordinates; } } // namespace waybar::modules::wlr waybar-0.9.9/src/modules/wlr/workspace_manager_binding.cpp000066400000000000000000000124711416677246500237760ustar00rootroot00000000000000#include "modules/wlr/workspace_manager_binding.hpp" #include #include #include "client.hpp" #include "modules/wlr/workspace_manager.hpp" namespace waybar::modules::wlr { static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) { static_cast(data)->register_manager(registry, name, version); } } static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) { /* Nothing to do here */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; void add_registry_listener(void *data) { wl_display * display = Client::inst()->wl_display; wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, data); wl_display_roundtrip(display); wl_display_roundtrip(display); } static void workspace_manager_handle_workspace_group( void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) { static_cast(data)->handle_workspace_group_create(workspace_group); } static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) { static_cast(data)->handle_done(); } static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) { static_cast(data)->handle_finished(); } static const zext_workspace_manager_v1_listener workspace_manager_impl = { .workspace_group = workspace_manager_handle_workspace_group, .done = workspace_manager_handle_done, .finished = workspace_manager_handle_finished, }; zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data) { auto *workspace_manager = static_cast( wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version)); if (workspace_manager) zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); else spdlog::error("Failed to register manager"); return workspace_manager; } static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_, wl_output *output) { static_cast(data)->handle_output_enter(output); } static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_, wl_output *output) { static_cast(data)->handle_output_leave(); } static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_, zext_workspace_handle_v1 *workspace) { static_cast(data)->handle_workspace_create(workspace); } static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) { static_cast(data)->handle_remove(); } static const zext_workspace_group_handle_v1_listener workspace_group_impl = { .output_enter = workspace_group_handle_output_enter, .output_leave = workspace_group_handle_output_leave, .workspace = workspace_group_handle_workspace, .remove = workspace_group_handle_remove}; void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void * data) { zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); } void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) { static_cast(data)->handle_name(name); } void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_, struct wl_array *coordinates) { std::vector coords_vec; auto coords = static_cast(coordinates->data); for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { coords_vec.push_back(coords[i]); } static_cast(data)->handle_coordinates(coords_vec); } void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle, struct wl_array *state) { std::vector state_vec; auto states = static_cast(state->data); for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) { state_vec.push_back(states[i]); } static_cast(data)->handle_state(state_vec); } void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) { static_cast(data)->handle_remove(); } static const zext_workspace_handle_v1_listener workspace_impl = { .name = workspace_handle_name, .coordinates = workspace_handle_coordinates, .state = workspace_handle_state, .remove = workspace_handle_remove}; void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) { zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); } } // namespace waybar::modules::wlr waybar-0.9.9/src/util/000077500000000000000000000000001416677246500146045ustar00rootroot00000000000000waybar-0.9.9/src/util/rfkill.cpp000066400000000000000000000051041416677246500165730ustar00rootroot00000000000000/* https://git.kernel.org/pub/scm/linux/kernel/git/jberg/rfkill.git/ * * Copyright 2009 Johannes Berg * Copyright 2009 Marcel Holtmann * Copyright 2009 Tim Gardner * * 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. */ #include "util/rfkill.hpp" #include #include #include #include #include #include waybar::util::Rfkill::Rfkill(const enum rfkill_type rfkill_type) : rfkill_type_(rfkill_type) { fd_ = open("/dev/rfkill", O_RDONLY); if (fd_ < 0) { spdlog::error("Can't open RFKILL control device"); return; } int rc = fcntl(fd_, F_SETFL, O_NONBLOCK); if (rc < 0) { spdlog::error("Can't set RFKILL control device to non-blocking: {}", errno); close(fd_); fd_ = -1; return; } Glib::signal_io().connect( sigc::mem_fun(*this, &Rfkill::on_event), fd_, Glib::IO_IN | Glib::IO_ERR | Glib::IO_HUP); } waybar::util::Rfkill::~Rfkill() { if (fd_ >= 0) { close(fd_); } } bool waybar::util::Rfkill::on_event(Glib::IOCondition cond) { if (cond & Glib::IO_IN) { struct rfkill_event event; ssize_t len; len = read(fd_, &event, sizeof(event)); if (len < 0) { if (errno == EAGAIN) { return true; } spdlog::error("Reading of RFKILL events failed: {}", errno); return false; } if (len < RFKILL_EVENT_SIZE_V1) { spdlog::error("Wrong size of RFKILL event: {} < {}", len, RFKILL_EVENT_SIZE_V1); return true; } if (event.type == rfkill_type_ && (event.op == RFKILL_OP_ADD || event.op == RFKILL_OP_CHANGE)) { state_ = event.soft || event.hard; on_update.emit(event); } return true; } else { spdlog::error("Failed to poll RFKILL control device"); return false; } } bool waybar::util::Rfkill::getState() const { return state_; } waybar-0.9.9/src/util/ustring_clen.cpp000066400000000000000000000003171416677246500200050ustar00rootroot00000000000000#include "util/ustring_clen.hpp" int ustring_clen(const Glib::ustring &str){ int total = 0; for (auto i = str.begin(); i != str.end(); ++i) { total += g_unichar_iswide(*i) + 1; } return total; }waybar-0.9.9/subprojects/000077500000000000000000000000001416677246500154035ustar00rootroot00000000000000waybar-0.9.9/subprojects/catch2.wrap000066400000000000000000000006751416677246500174520ustar00rootroot00000000000000[wrap-file] directory = Catch2-2.13.7 source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip source_filename = Catch2-2.13.7.zip source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9 patch_filename = catch2_2.13.7-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158 [provide] catch2 = catch2_dep waybar-0.9.9/subprojects/date.wrap000066400000000000000000000006671416677246500172240ustar00rootroot00000000000000[wrap-file] source_url=https://github.com/HowardHinnant/date/archive/v3.0.0.tar.gz source_filename=date-3.0.0.tar.gz source_hash=87bba2eaf0ebc7ec539e5e62fc317cb80671a337c1fb1b84cb9e4d42c6dbebe3 directory=date-3.0.0 patch_url = https://github.com/mesonbuild/hinnant-date/releases/download/3.0.0-1/hinnant-date.zip patch_filename = hinnant-date-3.0.0-1-wrap.zip patch_hash = 6ccaf70732d8bdbd1b6d5fdf3e1b935c23bf269bda12fdfd0e561276f63432fe waybar-0.9.9/subprojects/fmt.wrap000066400000000000000000000006631416677246500170710ustar00rootroot00000000000000[wrap-file] directory = fmt-7.1.3 source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz source_filename = fmt-7.1.3.tar.gz source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc patch_url = https://github.com/mesonbuild/fmt/releases/download/7.1.3-1/fmt.zip patch_filename = fmt-7.1.3-1-wrap.zip patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213 [provide] fmt = fmt_dep waybar-0.9.9/subprojects/gtk-layer-shell.wrap000066400000000000000000000004151416677246500213020ustar00rootroot00000000000000[wrap-file] directory = gtk-layer-shell-0.4.0 source_filename = gtk-layer-shell-0.4.0.tar.gz source_hash = 52fd74d3161fefa5528585ca5a523c3150934961f2284ad010ae54336dad097e source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.4.0/gtk-layer-shell-0.4.0.tar.gz waybar-0.9.9/subprojects/spdlog.wrap000066400000000000000000000006731416677246500175740ustar00rootroot00000000000000[wrap-file] directory = spdlog-1.8.5 source_url = https://github.com/gabime/spdlog/archive/v1.8.5.tar.gz source_filename = v1.8.5.tar.gz source_hash = 944d0bd7c763ac721398dca2bb0f3b5ed16f67cef36810ede5061f35a543b4b8 patch_url = https://wrapdb.mesonbuild.com/v1/projects/spdlog/1.8.5/1/get_zip patch_filename = spdlog-1.8.5-1-wrap.zip patch_hash = 3c38f275d5792b1286391102594329e98b17737924b344f98312ab09929b74be [provide] spdlog = spdlog_dep waybar-0.9.9/test/000077500000000000000000000000001416677246500140175ustar00rootroot00000000000000waybar-0.9.9/test/GlibTestsFixture.hpp000066400000000000000000000011721416677246500200000ustar00rootroot00000000000000#pragma once #include /** * Minimal Glib application to be used for tests that require Glib main loop */ class GlibTestsFixture : public sigc::trackable { public: GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} void setTimeout(int timeout) { Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, timeout); } void run(std::function fn) { Glib::signal_idle().connect_once(fn); main_loop_->run(); } void quit() { main_loop_->quit(); } protected: Glib::RefPtr main_loop_; }; waybar-0.9.9/test/SafeSignal.cpp000066400000000000000000000071321416677246500165420ustar00rootroot00000000000000#include "util/SafeSignal.hpp" #include #include #include #include #include "GlibTestsFixture.hpp" using namespace waybar; template using remove_cvref_t = typename std::remove_cv::type>::type; /** * Basic sanity test for SafeSignal: * check that type deduction works, events are delivered and the order is right * Running this with -fsanitize=thread should not fail */ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") { const int NUM_EVENTS = 100; int count = 0; int last_value = 0; SafeSignal test_signal; const auto main_tid = std::this_thread::get_id(); std::thread producer; // timeout the test in 500ms setTimeout(500); test_signal.connect([&](auto val, auto str) { static_assert(std::is_same::value); static_assert(std::is_same::value); // check that we're in the same thread as the main loop REQUIRE(std::this_thread::get_id() == main_tid); // check event order REQUIRE(val == last_value + 1); last_value = val; if (++count >= NUM_EVENTS) { this->quit(); }; }); run([&]() { // check that events from the same thread are delivered and processed synchronously test_signal.emit(1, "test"); REQUIRE(count == 1); // start another thread and generate events producer = std::thread([&]() { for (auto i = 2; i <= NUM_EVENTS; ++i) { test_signal.emit(i, "test"); } }); }); producer.join(); REQUIRE(count == NUM_EVENTS); } template struct TestObject { T value; unsigned copied = 0; unsigned moved = 0; TestObject(const T& v) : value(v){}; ~TestObject() = default; TestObject(const TestObject& other) : value(other.value), copied(other.copied + 1), moved(other.moved) {} TestObject(TestObject&& other) noexcept : value(std::move(other.value)), copied(std::exchange(other.copied, 0)), moved(std::exchange(other.moved, 0) + 1) {} TestObject& operator=(const TestObject& other) { value = other.value; copied = other.copied + 1; moved = other.moved; return *this; } TestObject& operator=(TestObject&& other) noexcept { value = std::move(other.value); copied = std::exchange(other.copied, 0); moved = std::exchange(other.moved, 0) + 1; return *this; } bool operator==(T other) const { return value == other; } operator T() const { return value; } }; /* * Check the number of copies/moves performed on the object passed through SafeSignal */ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") { const int NUM_EVENTS = 3; int count = 0; SafeSignal> test_signal; std::thread producer; // timeout the test in 500ms setTimeout(500); test_signal.connect([&](auto& val) { static_assert(std::is_same, remove_cvref_t>::value); /* explicit move in the producer thread */ REQUIRE(val.moved <= 1); /* copy within the SafeSignal queuing code */ REQUIRE(val.copied <= 1); if (++count >= NUM_EVENTS) { this->quit(); }; }); run([&]() { test_signal.emit(1); REQUIRE(count == 1); producer = std::thread([&]() { for (auto i = 2; i <= NUM_EVENTS; ++i) { TestObject t{i}; // check that signal.emit accepts moved objects test_signal.emit(std::move(t)); } }); }); producer.join(); REQUIRE(count == NUM_EVENTS); } waybar-0.9.9/test/config.cpp000066400000000000000000000077751416677246500160100ustar00rootroot00000000000000#include "config.hpp" #include TEST_CASE("Load simple config", "[config]") { waybar::Config conf; conf.load("test/config/simple.json"); SECTION("validate the config data") { auto& data = conf.getConfig(); REQUIRE(data["layer"].asString() == "top"); REQUIRE(data["height"].asInt() == 30); } SECTION("select configs for configured output") { auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); REQUIRE(configs.size() == 1); } SECTION("select configs for missing output") { auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); REQUIRE(configs.empty()); } } TEST_CASE("Load config with multiple bars", "[config]") { waybar::Config conf; conf.load("test/config/multi.json"); SECTION("select multiple configs #1") { auto data = conf.getOutputConfigs("DP-0", "Fake DisplayPort output #0"); REQUIRE(data.size() == 3); REQUIRE(data[0]["layer"].asString() == "bottom"); REQUIRE(data[0]["height"].asInt() == 20); REQUIRE(data[1]["layer"].asString() == "top"); REQUIRE(data[1]["position"].asString() == "bottom"); REQUIRE(data[1]["height"].asInt() == 21); REQUIRE(data[2]["layer"].asString() == "overlay"); REQUIRE(data[2]["position"].asString() == "right"); REQUIRE(data[2]["height"].asInt() == 23); } SECTION("select multiple configs #2") { auto data = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); REQUIRE(data.size() == 2); REQUIRE(data[0]["layer"].asString() == "bottom"); REQUIRE(data[0]["height"].asInt() == 20); REQUIRE(data[1]["layer"].asString() == "overlay"); REQUIRE(data[1]["position"].asString() == "right"); REQUIRE(data[1]["height"].asInt() == 23); } SECTION("select single config by output description") { auto data = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); REQUIRE(data.size() == 1); REQUIRE(data[0]["layer"].asString() == "overlay"); REQUIRE(data[0]["position"].asString() == "left"); REQUIRE(data[0]["height"].asInt() == 22); } } TEST_CASE("Load simple config with include", "[config]") { waybar::Config conf; conf.load("test/config/include.json"); SECTION("validate the config data") { auto& data = conf.getConfig(); // config override behavior: preserve first included value REQUIRE(data["layer"].asString() == "top"); REQUIRE(data["height"].asInt() == 30); // config override behavior: preserve value from the top config REQUIRE(data["position"].asString() == "top"); // config override behavior: explicit null is still a value and should be preserved REQUIRE((data.isMember("nullOption") && data["nullOption"].isNull())); } SECTION("select configs for configured output") { auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0"); REQUIRE(configs.size() == 1); } SECTION("select configs for missing output") { auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1"); REQUIRE(configs.empty()); } } TEST_CASE("Load multiple bar config with include", "[config]") { waybar::Config conf; conf.load("test/config/include-multi.json"); SECTION("bar config with sole include") { auto data = conf.getOutputConfigs("OUT-0", "Fake output #0"); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 20); } SECTION("bar config with output and include") { auto data = conf.getOutputConfigs("OUT-1", "Fake output #1"); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 21); } SECTION("bar config with output override") { auto data = conf.getOutputConfigs("OUT-2", "Fake output #2"); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 22); } SECTION("multiple levels of include") { auto data = conf.getOutputConfigs("OUT-3", "Fake output #3"); REQUIRE(data.size() == 1); REQUIRE(data[0]["height"].asInt() == 23); } auto& data = conf.getConfig(); REQUIRE(data.isArray()); REQUIRE(data.size() == 4); REQUIRE(data[0]["output"].asString() == "OUT-0"); } waybar-0.9.9/test/config/000077500000000000000000000000001416677246500152645ustar00rootroot00000000000000waybar-0.9.9/test/config/include-1.json000066400000000000000000000001711416677246500177370ustar00rootroot00000000000000{ "layer": "top", "position": "bottom", "height": 30, "output": ["HDMI-0", "DP-0"], "nullOption": "not null" } waybar-0.9.9/test/config/include-2.json000066400000000000000000000000301416677246500177320ustar00rootroot00000000000000{ "layer": "bottom" } waybar-0.9.9/test/config/include-multi-0.json000066400000000000000000000000501416677246500210620ustar00rootroot00000000000000{ "output": "OUT-0", "height": 20 } waybar-0.9.9/test/config/include-multi-1.json000066400000000000000000000000231416677246500210630ustar00rootroot00000000000000{ "height": 21 } waybar-0.9.9/test/config/include-multi-2.json000066400000000000000000000000501416677246500210640ustar00rootroot00000000000000{ "output": "OUT-1", "height": 22 } waybar-0.9.9/test/config/include-multi-3-0.json000066400000000000000000000000231416677246500212220ustar00rootroot00000000000000{ "height": 23 } waybar-0.9.9/test/config/include-multi-3.json000066400000000000000000000001131416677246500210650ustar00rootroot00000000000000{ "output": "OUT-3", "include": "test/config/include-multi-3-0.json" } waybar-0.9.9/test/config/include-multi.json000066400000000000000000000004351416677246500207340ustar00rootroot00000000000000[ { "include": "test/config/include-multi-0.json" }, { "output": "OUT-1", "include": "test/config/include-multi-1.json" }, { "output": "OUT-2", "include": "test/config/include-multi-2.json" }, { "include": "test/config/include-multi-3.json" } ] waybar-0.9.9/test/config/include.json000066400000000000000000000001711416677246500176010ustar00rootroot00000000000000{ "include": ["test/config/include-1.json", "test/config/include-2.json"], "position": "top", "nullOption": null } waybar-0.9.9/test/config/multi.json000066400000000000000000000006111416677246500173070ustar00rootroot00000000000000[ { "layer": "bottom", "height": 20, "output": ["HDMI-0", "DP-0"] }, { "position": "bottom", "layer": "top", "height": 21, "output": ["DP-0"] }, { "position": "left", "layer": "overlay", "height": 22, "output": "Fake HDMI output #1" }, { "position": "right", "layer": "overlay", "height": 23, "output": "!HDMI-1" } ] waybar-0.9.9/test/config/simple.json000066400000000000000000000001051416677246500174440ustar00rootroot00000000000000{ "layer": "top", "height": 30, "output": ["HDMI-0", "DP-0"] } waybar-0.9.9/test/main.cpp000066400000000000000000000012751416677246500154540ustar00rootroot00000000000000#define CATCH_CONFIG_RUNNER #include #include #include #include #include #include int main(int argc, char* argv[]) { Catch::Session session; Glib::init(); session.applyCommandLine(argc, argv); const auto logger = spdlog::default_logger(); const auto& reporter_name = session.config().getReporterName(); if (reporter_name == "tap") { spdlog::set_pattern("# [%l] %v"); } else if (reporter_name == "compact") { logger->sinks().clear(); } else { logger->sinks().assign({std::make_shared()}); } return session.run(); } waybar-0.9.9/test/meson.build000066400000000000000000000007661416677246500161720ustar00rootroot00000000000000test_inc = include_directories('../include') test_dep = [ catch2, fmt, gtkmm, jsoncpp, spdlog, ] test_src = files( 'main.cpp', 'SafeSignal.cpp', 'config.cpp', '../src/config.cpp', ) if tz_dep.found() test_dep += tz_dep test_src += files('waybar_time.cpp') endif waybar_test = executable( 'waybar_test', test_src, dependencies: test_dep, include_directories: test_inc, ) test( 'waybar', waybar_test, workdir: meson.source_root(), ) waybar-0.9.9/test/waybar_time.cpp000066400000000000000000000066201416677246500170320ustar00rootroot00000000000000#include "util/waybar_time.hpp" #include #include #include #include #include using namespace std::literals::chrono_literals; /* * Check that the date/time formatter with locale and timezone support is working as expected. */ const date::zoned_time TEST_TIME = date::make_zoned( "UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s); TEST_CASE("Format UTC time", "[clock][util]") { waybar::waybar_time tm{std::locale("C"), TEST_TIME}; REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); /* Test a few locales that are most likely to be present */ SECTION("US locale") { try { tm.locale = std::locale("en_US"); REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM")); REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); } catch (const std::runtime_error&) { // locale not found; ignore } } SECTION("GB locale") { try { tm.locale = std::locale("en_GB"); REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05")); REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 13:04:05"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405"); } catch (const std::runtime_error&) { // locale not found; ignore } } } TEST_CASE("Format zoned time", "[clock][util]") { waybar::waybar_time tm{std::locale("C"), date::make_zoned("America/New_York", TEST_TIME)}; REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); /* Test a few locales that are most likely to be present */ SECTION("US locale") { try { tm.locale = std::locale("en_US"); REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM")); REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); } catch (const std::runtime_error&) { // locale not found; ignore } } SECTION("GB locale") { try { tm.locale = std::locale("en_GB"); REQUIRE(fmt::format("{}", tm).empty()); // no format specified REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704 Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05")); REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 08:04:05"); REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405"); } catch (const std::runtime_error&) { // locale not found; ignore } } }