diff --git a/.travis.yml b/.travis.yml index 62f7863..abc739c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,9 @@ jobs: compiler: clang env: before_install: - - sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu + - export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio + - sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf + - sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog script: - meson build -Dman-pages=enabled diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine index 7b71837..21d1cbb 100644 --- a/Dockerfiles/alpine +++ b/Dockerfiles/alpine @@ -2,4 +2,4 @@ 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 scdoc +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 diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index d8ae16f..0b618ff 100644 --- a/Dockerfiles/archlinux +++ b/Dockerfiles/archlinux @@ -3,4 +3,4 @@ FROM archlinux/base:latest 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 --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 diff --git a/Dockerfiles/debian b/Dockerfiles/debian index 077aca8..cee1744 100644 --- a/Dockerfiles/debian +++ b/Dockerfiles/debian @@ -3,5 +3,5 @@ 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 && \ + 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 && \ apt-get clean diff --git a/README.md b/README.md index f7bc4c8..b104ade 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [AUR](https://aur.archlinux.org/packages/waybar-git/), [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** +#### Current features - Sway (Workspaces, Binding mode, Focused window name) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time @@ -22,11 +22,21 @@ - Multiple output configuration - And much more customizations -**Configuration and Styling** +#### Configuration and Styling [See the wiki for more details](https://github.com/Alexays/Waybar/wiki). -**How to build** +### 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 @@ -57,6 +67,7 @@ libnl [Network module] libappindicator-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] +libsndio [sndio module] ``` **Build dependencies** diff --git a/include/ALabel.hpp b/include/ALabel.hpp index d4ad94d..6848d67 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -10,7 +10,7 @@ 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); + 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); diff --git a/include/bar.hpp b/include/bar.hpp index 63f0e22..fdc5a73 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -53,13 +53,18 @@ class Bar { static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *); #ifdef HAVE_GTK_LAYER_SHELL + /* gtk-layer-shell code */ void initGtkLayerShell(); + void onConfigureGLS(GdkEventConfigure *ev); + void onMapGLS(GdkEventAny *ev); #endif + /* fallback layer-surface code */ void onConfigure(GdkEventConfigure *ev); void onRealize(); void onMap(GdkEventAny *ev); - void setExclusiveZone(uint32_t width, uint32_t height); void setSurfaceSize(uint32_t width, uint32_t height); + /* common code */ + void setExclusiveZone(uint32_t width, uint32_t height); auto setupWidgets() -> void; void getModules(const Factory &, const std::string &); void setupAltFormatKeyForModule(const std::string &module_name); diff --git a/include/factory.hpp b/include/factory.hpp index fcbf3a2..3efe6cb 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -39,11 +39,16 @@ #ifdef HAVE_LIBMPDCLIENT #include "modules/mpd.hpp" #endif +#ifdef HAVE_LIBSNDIO +#include "modules/sndio.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" #if defined(__linux__) -#include "modules/bluetooth.hpp" +# ifdef WANT_RFKILL +# include "modules/bluetooth.hpp" +# endif #endif namespace waybar { diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index e3873a6..643b736 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -28,9 +28,12 @@ class Clock : public ALabel { std::locale locale_; const date::time_zone* time_zone_; bool fixed_time_zone_; + int time_zone_idx_; date::year_month_day cached_calendar_ymd_; std::string cached_calendar_text_; + 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; diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index b8dad9d..7c77145 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -22,6 +22,7 @@ class Custom : public ALabel { void continuousWorker(); void parseOutputRaw(); void parseOutputJson(); + void handleEvent(); bool handleScroll(GdkEventScroll* e); bool handleToggle(GdkEventButton* const& e); diff --git a/include/modules/network.hpp b/include/modules/network.hpp index a0156fb..c02d3c5 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -11,7 +11,9 @@ #include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" +#ifdef WANT_RFKILL #include "util/rfkill.hpp" +#endif namespace waybar::modules { @@ -52,6 +54,8 @@ class Network : public ALabel { 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_; @@ -70,9 +74,11 @@ class Network : public ALabel { util::SleeperThread thread_; util::SleeperThread thread_timer_; +#ifdef WANT_RFKILL util::SleeperThread thread_rfkill_; util::Rfkill rfkill_; +#endif }; } // namespace waybar::modules diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp new file mode 100644 index 0000000..32ed706 --- /dev/null +++ b/include/modules/sndio.hpp @@ -0,0 +1,30 @@ +#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 diff --git a/include/util/command.hpp b/include/util/command.hpp index a72a829..5265558 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -7,6 +7,9 @@ #include +extern std::mutex reap_mtx; +extern std::list reap; + namespace waybar::util::command { struct res { @@ -32,10 +35,11 @@ inline std::string read(FILE* fp) { inline int close(FILE* fp, pid_t pid) { int stat = -1; + pid_t ret; fclose(fp); do { - waitpid(pid, &stat, WCONTINUED | WUNTRACED); + ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED); if (WIFEXITED(stat)) { spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat)); @@ -45,6 +49,8 @@ inline int close(FILE* fp, pid_t pid) { 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; } @@ -65,6 +71,12 @@ inline FILE* open(const std::string& cmd, int& pid) { } 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)); ::close(fd[0]); dup2(fd[1], 1); setpgid(child_pid, child_pid); @@ -97,7 +109,7 @@ inline struct res execNoRead(const std::string& cmd) { inline int32_t forkExec(const std::string& cmd) { if (cmd == "") return -1; - int32_t pid = fork(); + pid_t pid = fork(); if (pid < 0) { spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno)); @@ -106,12 +118,20 @@ inline int32_t forkExec(const std::string& cmd) { // 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); - signal(SIGCHLD, SIG_DFL); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); exit(0); } else { - signal(SIGCHLD, SIG_IGN); + reap_mtx.lock(); + reap.push_back(pid); + reap_mtx.unlock(); + spdlog::debug("Added child to reap list: {}", pid); } return pid; diff --git a/include/util/format.hpp b/include/util/format.hpp index 0147701..288d8f0 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -23,7 +23,7 @@ namespace fmt { constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == ':') ++it; - if (*it == '>' || *it == '<' || *it == '=') { + if (it && (*it == '>' || *it == '<' || *it == '=')) { spec = *it; ++it; } diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 3610f19..9f36c43 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -26,11 +26,21 @@ The *clock* module displays the current date and time. default: inferred local timezone ++ The timezone to display the time in, e.g. America/New_York. +*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. diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 121585a..3e820c6 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -22,6 +22,12 @@ Addressed by *custom/* 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* diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 1a9320c..431d7c8 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -31,6 +31,10 @@ Addressed by *disk* 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. diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 1ee7a98..e8105de 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -148,6 +148,10 @@ Addressed by *mpd* *{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) diff --git a/man/waybar-sndio.5.scd b/man/waybar-sndio.5.scd new file mode 100644 index 0000000..a61c332 --- /dev/null +++ b/man/waybar-sndio.5.scd @@ -0,0 +1,83 @@ +waybar-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. + +*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* diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 56b703a..5e51689 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -31,6 +31,11 @@ Addressed by *sway/workspaces* 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. @@ -134,3 +139,4 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge - *#workspaces button.urgent* - *#workspaces button.persistent* - *#workspaces button.current_output* +- *#workspaces button#sway-workspace-${name}* diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index f044412..55d2afd 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -32,6 +32,11 @@ Addressed by *wlr/taskbar* 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 ++ diff --git a/man/waybar.5.scd b/man/waybar.5.scd.in similarity index 97% rename from man/waybar.5.scd rename to man/waybar.5.scd.in index 5267110..430b9fc 100644 --- a/man/waybar.5.scd +++ b/man/waybar.5.scd.in @@ -14,6 +14,7 @@ Valid locations for this file are: - *~/.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. @@ -185,14 +186,18 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270. - *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-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)* diff --git a/meson.build b/meson.build index dd56c29..023894c 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.2', + version: '0.9.4', license: 'MIT', default_options : [ 'cpp_std=c++17', @@ -9,6 +9,8 @@ project( ], ) +fs = import('fs') + compiler = meson.get_compiler('cpp') cpp_args = [] @@ -94,6 +96,19 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) + +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']) @@ -137,12 +152,10 @@ if is_linux add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp') src_files += files( 'src/modules/battery.cpp', - 'src/modules/bluetooth.cpp', 'src/modules/cpu/common.cpp', 'src/modules/cpu/linux.cpp', 'src/modules/memory/common.cpp', 'src/modules/memory/linux.cpp', - 'src/util/rfkill.cpp' ) elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp') @@ -207,6 +220,21 @@ 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 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 + subdir('protocol') executable( @@ -231,6 +259,7 @@ executable( libepoll, libmpdclient, gtk_layer_shell, + libsndio, tz_dep ], include_directories: [include_directories('include')], @@ -248,9 +277,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti 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 = [ - 'waybar.5.scd', + main_manpage_path, 'waybar-backlight.5.scd', 'waybar-battery.5.scd', 'waybar-clock.5.scd', @@ -271,16 +311,21 @@ if scdoc.found() 'waybar-states.5.scd', 'waybar-wlr-taskbar.5.scd', 'waybar-bluetooth.5.scd', + 'waybar-sndio.5.scd', ] - foreach filename : man_files - topic = filename.split('.')[-3].split('/')[-1] - section = filename.split('.')[-2] + foreach file : man_files + path = '@0@'.format(file) + basename = fs.name(path) + + topic = basename.split('.')[-3].split('/')[-1] + section = basename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, - input: 'man/@0@'.format(filename), + # 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) diff --git a/meson_options.txt b/meson_options.txt index a44ff64..cb5581b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,3 +7,5 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp 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') diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml index fb4f6b2..f9a4fe0 100644 --- a/protocol/wlr-layer-shell-unstable-v1.xml +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -25,7 +25,7 @@ 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 @@ -82,17 +82,27 @@ + + + + + + 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 (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. + 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. @@ -115,7 +125,7 @@ Requests that the compositor anchor the surface to the specified edges - and corners. If two orthoginal edges are specified (e.g. 'top' and + 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. @@ -127,20 +137,25 @@ - Requests that the compositor avoids occluding an area of the surface - with other surfaces. The compositor's use of this information is + 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 an - edge, rather than a corner. The zone is the number of surface-local - coordinates from the edge that are considered exclusive. + 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 excluzive zone. If set to -1, the surface - indicates that it would not like to be moved to accomodate for other + 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. @@ -281,5 +296,16 @@ + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + diff --git a/resources/config b/resources/config index 832f76c..36c4f96 100644 --- a/resources/config +++ b/resources/config @@ -27,7 +27,7 @@ "format": "{}" }, "mpd": { - "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ", + "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) βΈ¨{songPosition}|{queueLength}βΈ© ", "format-disconnected": "Disconnected ", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ", "unknown-tag": "N/A", diff --git a/resources/style.css b/resources/style.css index e21ae00..c454bff 100644 --- a/resources/style.css +++ b/resources/style.css @@ -41,19 +41,19 @@ window#waybar.chromium { padding: 0 5px; background-color: transparent; color: #ffffff; - border-bottom: 3px solid transparent; + /* Use box-shadow instead of border so the text isn't offset */ + box-shadow: inset 0 -3px transparent; } /* 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: inherit; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -3px #ffffff; } #workspaces button.focused { background-color: #64727D; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -3px #ffffff; } #workspaces button.urgent { diff --git a/resources/waybar.service.in b/resources/waybar.service.in index 03262a3..2c907e9 100644 --- a/resources/waybar.service.in +++ b/resources/waybar.service.in @@ -1,13 +1,11 @@ [Unit] Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. Documentation=https://github.com/Alexays/Waybar/wiki/ -PartOf=wayland-session.target -After=wayland-session.target +PartOf=graphical-session.target +After=graphical-session.target [Service] -Type=dbus -BusName=fr.arouillard.waybar ExecStart=@prefix@/bin/waybar [Install] -WantedBy=wayland-session.target +WantedBy=graphical-session.target diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 68313e0..3a4063d 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -5,8 +5,9 @@ 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) - : AModule(config, name, id, config["format-alt"].isString()), + 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) @@ -21,8 +22,10 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st if (config_["max-length"].isUInt()) { label_.set_max_width_chars(config_["max-length"].asUInt()); 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_["rotate"].isUInt()) { diff --git a/src/AModule.cpp b/src/AModule.cpp index 10bd077..7da942e 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -44,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) { 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-forward"].isString() && e->button == 8) { + } else if (config_["on-click-backward"].isString() && e->button == 8) { format = config_["on-click-backward"].asString(); - } else if (config_["on-click-backward"].isString() && e->button == 9) { + } else if (config_["on-click-forward"].isString() && e->button == 9) { format = config_["on-click-forward"].asString(); } if (!format.empty()) { diff --git a/src/bar.cpp b/src/bar.cpp index 3bbc2a3..8af6b97 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -24,6 +24,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) 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()); + 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["position"] == "right" || config["position"] == "left") { height_ = 0; @@ -101,56 +104,25 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; if (use_gls_) { initGtkLayerShell(); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMapGLS)); + window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigureGLS)); } #endif - window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); - window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); + if (!use_gls_) { + window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); + window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); + } window.set_size_request(width_, height_); setupWidgets(); - if (window.get_realized()) { + if (!use_gls_ && window.get_realized()) { onRealize(); } window.show_all(); } -void waybar::Bar::onConfigure(GdkEventConfigure* ev) { - 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 (config["height"].isUInt()) { - 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 (config["width"].isUInt()) { - spdlog::info(SIZE_DEFINED, "Width"); - } else { - tmp_width = ev->width; - } - } - if (use_gls_) { - width_ = tmp_width; - height_ = tmp_height; - spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name); - setExclusiveZone(tmp_width, tmp_height); - } else if (tmp_width != width_ || tmp_height != height_) { - setSurfaceSize(tmp_width, tmp_height); - } -} - #ifdef HAVE_GTK_LAYER_SHELL void waybar::Bar::initGtkLayerShell() { auto gtk_window = window.gobj(); @@ -181,8 +153,80 @@ void waybar::Bar::initGtkLayerShell() { setExclusiveZone(width_, height_); } } + +void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) { + /* + * GTK wants new size for the window. + * Actual resizing is done within the gtk-layer-shell code; the only remaining action is to apply + * exclusive zone. + * gtk_layer_auto_exclusive_zone_enable() could handle even that, but at the cost of ignoring + * margins on unanchored edge. + * + * 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 (!vertical && 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); + setExclusiveZone(width_, height_); +} + +void waybar::Bar::onMapGLS(GdkEventAny* ev) { + /* + * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). + */ + auto gdk_window = window.get_window(); + surface = gdk_wayland_window_get_wl_surface(gdk_window->gobj()); +} + #endif +void waybar::Bar::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 (config["height"].isUInt()) { + 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 (config["width"].isUInt()) { + spdlog::info(SIZE_DEFINED, "Width"); + } else { + tmp_width = ev->width; + } + } + if (tmp_width != width_ || tmp_height != height_) { + setSurfaceSize(tmp_width, tmp_height); + } +} + void waybar::Bar::onRealize() { auto gdk_window = window.get_window()->gobj(); gdk_wayland_window_set_use_custom_surface(gdk_window); @@ -192,10 +236,6 @@ void waybar::Bar::onMap(GdkEventAny* ev) { auto gdk_window = window.get_window()->gobj(); surface = gdk_wayland_window_get_wl_surface(gdk_window); - if (use_gls_) { - return; - } - auto client = waybar::Client::inst(); // owned by output->monitor; no need to destroy auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj()); @@ -362,7 +402,9 @@ auto waybar::Bar::toggle() -> void { window.set_opacity(1); } setExclusiveZone(width_, height_); - wl_surface_commit(surface); + if (!use_gls_) { + wl_surface_commit(surface); + } } void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { diff --git a/src/client.cpp b/src/client.cpp index 316e7ec..70cc22c 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -162,6 +162,7 @@ std::tuple waybar::Client::getConfigs( "$XDG_CONFIG_HOME/waybar/config", "$HOME/.config/waybar/config", "$HOME/waybar/config", + "/etc/xdg/waybar/config", SYSCONFDIR "/xdg/waybar/config", "./resources/config", }) @@ -170,6 +171,7 @@ std::tuple waybar::Client::getConfigs( "$XDG_CONFIG_HOME/waybar/style.css", "$HOME/.config/waybar/style.css", "$HOME/waybar/style.css", + "/etc/xdg/waybar/style.css", SYSCONFDIR "/xdg/waybar/style.css", "./resources/style.css", }) diff --git a/src/factory.cpp b/src/factory.cpp index 5a01d52..8a5e825 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -76,14 +76,21 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { 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 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]); diff --git a/src/main.cpp b/src/main.cpp index f066cf8..19a8de1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,72 @@ #include +#include +#include +#include +#include #include #include "client.hpp" +std::mutex reap_mtx; +std::list reap; + +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(); @@ -18,6 +83,7 @@ int main(int argc, char* argv[]) { } }); } + startSignalThread(); auto ret = client->main(argc, argv); delete client; diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index f41126b..f313606 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -13,7 +13,7 @@ using waybar::modules::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) - : ALabel(config, "clock", id, "{:%H:%M}", 60), fixed_time_zone_(false) { + : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { if (config_["timezone"].isString()) { spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018."); time_zone_ = date::locate_zone(config_["timezone"].asString()); @@ -71,6 +71,40 @@ auto waybar::modules::Clock::update() -> void { 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 (!config_["timezones"].isArray() || config_["timezones"].empty()) { + return true; + } + auto nr_zones = config_["timezones"].size(); + int new_idx = time_zone_idx_ + ((dir == SCROLL_DIR::UP) ? 1 : -1); + if (new_idx < 0) { + time_zone_idx_ = nr_zones - 1; + } else if (new_idx >= nr_zones) { + time_zone_idx_ = 0; + } else { + time_zone_idx_ = new_idx; + } + auto zone_name = config_["timezones"][time_zone_idx_]; + if (!zone_name.isString() || zone_name.empty()) { + fixed_time_zone_ = false; + } else { + time_zone_ = date::locate_zone(zone_name.asString()); + fixed_time_zone_ = true; + } + + 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); @@ -99,7 +133,12 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str os << '\n'; } if (d == curr_day) { - os << "" << date::format("%e", d) << ""; + 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); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 5643160..92eedaa 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -50,7 +50,6 @@ void waybar::modules::Custom::continuousWorker() { thread_ = [this, cmd] { char* buff = nullptr; size_t len = 0; - bool restart = false; if (getline(&buff, &len, fp_) == -1) { int exit_code = 1; if (fp_) { @@ -63,8 +62,8 @@ void waybar::modules::Custom::continuousWorker() { spdlog::error("{} stopped unexpectedly, is it endless?", name_); } if (config_["restart-interval"].isUInt()) { - restart = true; 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); @@ -83,9 +82,6 @@ void waybar::modules::Custom::continuousWorker() { output_ = {0, output}; dp.emit(); } - if (restart) { - thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); - } }; } @@ -95,15 +91,21 @@ void waybar::modules::Custom::refresh(int sig) { } } +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); - thread_.wake_up(); + handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { auto ret = ALabel::handleToggle(e); - thread_.wake_up(); + handleEvent(); return ret; } diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 59ffea6..83d612b 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -47,13 +47,14 @@ auto waybar::modules::Disk::update() -> void { auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bavail) * 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_bavail) * 100 / stats.f_blocks; 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", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); @@ -67,12 +68,13 @@ auto waybar::modules::Disk::update() -> void { , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) - , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); } event_box_.show(); + getState(percentage_used); // Call parent update ALabel::update(); } diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 4875ec8..a332d58 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -36,7 +36,17 @@ auto waybar::modules::Memory::update() -> void { fmt::arg("used", used_ram_gigabytes), fmt::arg("avail", available_ram_gigabytes))); if (tooltipEnabled()) { - label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); + 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)); + } } event_box_.show(); } else { diff --git a/src/modules/mpd.cpp b/src/modules/mpd.cpp index 957b3c7..26878b1 100644 --- a/src/modules/mpd.cpp +++ b/src/modules/mpd.cpp @@ -63,20 +63,22 @@ auto waybar::modules::MPD::update() -> void { std::thread waybar::modules::MPD::event_listener() { return std::thread([this] { - try { - if (connection_ == nullptr) { - // Retry periodically if no connection - dp.emit(); - std::this_thread::sleep_for(interval_); - } else { - waitForEvent(); - dp.emit(); - } - } catch (const std::exception& e) { - if (strcmp(e.what(), "Connection to MPD closed") == 0) { - spdlog::debug("{}: {}", module_name_, e.what()); - } else { - spdlog::warn("{}: {}", module_name_, e.what()); + while (true) { + try { + if (connection_ == nullptr) { + // Retry periodically if no connection + dp.emit(); + std::this_thread::sleep_for(interval_); + } else { + waitForEvent(); + dp.emit(); + } + } catch (const std::exception& e) { + if (strcmp(e.what(), "Connection to MPD closed") == 0) { + spdlog::debug("{}: {}", module_name_, e.what()); + } else { + spdlog::warn("{}: {}", module_name_, e.what()); + } } } }); @@ -131,6 +133,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; + int song_pos, queue_length; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; @@ -159,6 +162,8 @@ void waybar::modules::MPD::setLabel() { album = getTag(MPD_TAG_ALBUM); title = getTag(MPD_TAG_TITLE); date = getTag(MPD_TAG_DATE); + song_pos = mpd_status_get_song_pos(status_.get()); + 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())); } @@ -182,6 +187,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("date", Glib::Markup::escape_text(date).raw()), 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), @@ -198,6 +205,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("album", album), fmt::arg("title", title), fmt::arg("date", date), + fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 2c0562f..74ae913 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -4,7 +4,9 @@ #include #include #include "util/format.hpp" +#ifdef WANT_RFKILL #include "util/rfkill.hpp" +#endif namespace { @@ -81,11 +83,15 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf : ALabel(config, "network", id, "{ifname}", 60), ifid_(-1), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), + efd_(-1), + ev_fd_(-1), cidr_(-1), signal_strength_dbm_(0), signal_strength_(0), - frequency_(0), - rfkill_{RFKILL_TYPE_WLAN} { +#ifdef WANT_RFKILL + rfkill_{RFKILL_TYPE_WLAN}, +#endif + frequency_(0) { auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); if (down_octets) { @@ -115,6 +121,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf } 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) { @@ -146,6 +158,30 @@ void waybar::modules::Network::createEventSocket() { } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } + 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() { @@ -174,6 +210,7 @@ void waybar::modules::Network::worker() { } thread_timer_.sleep_for(interval_); }; +#ifdef WANT_RFKILL thread_rfkill_ = [this] { rfkill_.waitForEvent(); { @@ -184,12 +221,30 @@ void waybar::modules::Network::worker() { } } }; +#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_) || nl_recvmsgs_default(ev_sock_) < 0) { + 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 (ipaddr_.empty()) return "linked"; @@ -277,7 +332,7 @@ auto waybar::modules::Network::update() -> void { 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() != text) { + if (label_.get_tooltip_text() != tooltip_text) { label_.set_tooltip_text(tooltip_text); } } else if (label_.get_tooltip_text() != text) { diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp new file mode 100644 index 0000000..34c46bd --- /dev/null +++ b/src/modules/sndio.cpp @@ -0,0 +1,201 @@ +#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 */ diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index fc6d5eb..8d78bf5 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -248,23 +248,26 @@ 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); - button.signal_clicked().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_, - node["name"].asString(), - node["target_output"].asString(), - node["name"].asString())); - } else { - ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, node["name"].asString())); + 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_, + node["name"].asString(), + node["target_output"].asString(), + node["name"].asString())); + } else { + ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, node["name"].asString())); + } + } catch (const std::exception &e) { + spdlog::error("Workspaces: {}", e.what()); } - } catch (const std::exception &e) { - spdlog::error("Workspaces: {}", e.what()); - } - }); + }); + } return button; } @@ -280,6 +283,8 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node 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; diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 5c0dcb3..45915dc 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -460,38 +460,51 @@ bool Task::operator!=(const Task &o) const void Task::update() { + bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; + std::string title = title_; + std::string app_id = app_id_; + if (markup) { + title = Glib::Markup::escape_text(title); + app_id = Glib::Markup::escape_text(app_id); + } if (!format_before_.empty()) { - text_before_.set_label( - fmt::format(format_before_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_before_, + fmt::arg("title", title), + 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()) { - text_after_.set_label( - fmt::format(format_after_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_after_, + fmt::arg("title", title), + 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()) { - button_.set_tooltip_markup( - fmt::format(format_tooltip_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_tooltip_, + fmt::arg("title", title), + 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); } } @@ -709,7 +722,7 @@ bool Taskbar::show_output(struct wl_output *output) const bool Taskbar::all_outputs() const { - return config_["all_outputs"].isBool() && config_["all_outputs"].asBool(); + return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } std::vector> Taskbar::icon_themes() const diff --git a/src/util/rfkill.cpp b/src/util/rfkill.cpp index f987f4c..82d29e9 100644 --- a/src/util/rfkill.cpp +++ b/src/util/rfkill.cpp @@ -20,8 +20,8 @@ #include #include +#include #include -#include #include #include diff --git a/subprojects/date.wrap b/subprojects/date.wrap index ea73f0f..4d4067c 100644 --- a/subprojects/date.wrap +++ b/subprojects/date.wrap @@ -1,9 +1,9 @@ [wrap-file] -source_url=https://github.com/HowardHinnant/date/archive/v2.4.1.tar.gz -source_filename=date-2.4.1.tar.gz -source_hash=98907d243397483bd7ad889bf6c66746db0d7d2a39cc9aacc041834c40b65b98 -directory=date-2.4.1 +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/2.4.1-1/hinnant-date.zip -patch_filename = hinnant-date-2.4.1-1-wrap.zip -patch_hash = 2061673a6f8e6d63c3a40df4da58fa2b3de2835fd9b3e74649e8279599f3a8f6 +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 diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index b826ab9..6fe68c3 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -directory = gtk-layer-shell-0.1.0 -source_filename = gtk-layer-shell-0.1.0.tar.gz -source_hash = f7569e27ae30b1a94c3ad6c955cf56240d6bc272b760d9d266ce2ccdb94a5cf0 -source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.1.0/gtk-layer-shell-0.1.0.tar.gz +directory = gtk-layer-shell-0.3.0 +source_filename = gtk-layer-shell-0.3.0.tar.gz +source_hash = edd5e31279d494df66da9e9190c219fa295da547f5538207685e98468dbc134d +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.3.0/gtk-layer-shell-0.3.0.tar.gz