diff --git a/.travis.yml b/.travis.yml index 62f78633..abc739cd 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 7b718375..21d1cbba 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 d8ae16fd..0b618ffe 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 077aca86..cee17441 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 f7bc4c89..b104adec 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 d4ad94d3..6848d677 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 63f0e221..fdc5a739 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 fcbf3a2d..3efe6cbd 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 e3873a6d..643b7369 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 b8dad9dd..7c771450 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 a0156fbc..c02d3c5e 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 00000000..32ed7066 --- /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 a72a8294..52655581 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 0147701b..288d8f0c 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 3610f19d..9f36c435 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 121585a9..3e820c63 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 1a9320ce..431d7c8f 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 1ee7a988..e8105deb 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 00000000..a61c3325 --- /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 56b703a2..5e516892 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 f0444122..55d2afdc 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 52671106..430b9fcd 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 dd56c290..023894cd 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 a44ff648..cb5581b3 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 fb4f6b23..f9a4fe05 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 832f76c8..36c4f967 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 e21ae00e..c454bffa 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 03262a33..2c907e90 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 68313e09..3a4063d9 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 10bd0775..7da942e0 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 3bbc2a35..8af6b971 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 316e7ec6..70cc22cf 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 5a01d523..8a5e8250 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 f066cf85..19a8de1e 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 f41126b0..f3136067 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 56431609..92eedaaa 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 59ffea67..83d612b1 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 4875ec8f..a332d58c 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 957b3c76..26878b11 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 2c0562f4..74ae9130 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 00000000..34c46bdd --- /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 fc6d5eb8..8d78bf50 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 5c0dcb3b..45915dc6 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 f987f4cc..82d29e91 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 ea73f0fa..4d4067c9 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 b826ab9c..6fe68c3f 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