diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine index 78a78b6..fec63c2 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 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 libnl3-dev pulseaudio-dev libmpdclient-dev scdoc diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux index 9197fb8..b640ce7 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 scdoc --noconfirm + pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc --noconfirm diff --git a/Dockerfiles/debian b/Dockerfiles/debian index 8d7a065..eedbcfb 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 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 && \ + 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 && \ apt-get clean diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora index af88607..7bd9d76 100644 --- a/Dockerfiles/fedora +++ b/Dockerfiles/fedora @@ -2,6 +2,6 @@ FROM fedora:30 -RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \ +RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel pugixml-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \ dnf group install "C Development Tools and Libraries" -y && \ dnf clean all -y diff --git a/Dockerfiles/opensuse b/Dockerfiles/opensuse index c54777e..af1be95 100644 --- a/Dockerfiles/opensuse +++ b/Dockerfiles/opensuse @@ -4,4 +4,4 @@ FROM opensuse/tumbleweed:latest RUN zypper -n up && \ zypper -n install -t pattern devel_C_C++ && \ - zypper -n install git meson clang libinput10 libinput-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel scdoc + zypper -n install git meson clang libinput10 libinput-devel libpugixml1 libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel scdoc diff --git a/Makefile b/Makefile index bb6b334..d7182c1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build build-debug run clean default install -default: run +default: build build: meson build diff --git a/README.md b/README.md index 3e14e57..be45818 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ libsigc++ fmt wayland wlroots +libgtk-3-dev [gtk-layer-shell] +gobject-introspection [gtk-layer-shell] +libgirepository1.0-dev [gtk-layer-shell] libpulse [Pulseaudio module] libnl [Network module] sway [Sway modules] @@ -56,6 +59,30 @@ libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] ``` +On Ubuntu 19.10 you can install all the relevant dependencies using this command: + +``` +sudo apt install \ + clang-tidy \ + gobject-introspection \ + libdbusmenu-gtk3-dev \ + libfmt-dev \ + libgirepository1.0-dev \ + libgtk-3-dev \ + libgtkmm-3.0-dev \ + libinput-dev \ + libjsoncpp-dev \ + libmpdclient-dev \ + libnl-3-dev \ + libnl-genl-3-dev \ + libpulse-dev \ + libsigc++-2.0-dev \ + libspdlog-dev \ + libwayland-dev \ + scdoc +``` + + Contributions welcome! - have fun :)
The style guidelines is [Google's](https://google.github.io/styleguide/cppguide.html) diff --git a/include/bar.hpp b/include/bar.hpp index 7566b08..fb0cd59 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -15,10 +16,11 @@ namespace waybar { class Factory; struct waybar_output { - struct wl_output * output = nullptr; - std::string name; - uint32_t wl_name; - struct zxdg_output_v1 *xdg_output = nullptr; + Glib::RefPtr monitor; + std::string name; + + std::unique_ptr xdg_output = { + nullptr, &zxdg_output_v1_destroy}; }; class Bar { @@ -30,13 +32,12 @@ class Bar { auto toggle() -> void; void handleSignal(int); - struct waybar_output * output; - Json::Value config; - Gtk::Window window; - struct wl_surface * surface; - struct zwlr_layer_surface_v1 *layer_surface; - bool visible = true; - bool vertical = false; + struct waybar_output *output; + Json::Value config; + Gtk::Window window; + struct wl_surface * surface; + bool visible = true; + bool vertical = false; private: static constexpr const char *MIN_HEIGHT_MSG = @@ -51,7 +52,9 @@ class Bar { uint32_t, uint32_t); static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *); - void destroyOutput(); +#ifdef HAVE_GTK_LAYER_SHELL + void initGtkLayerShell(); +#endif void onConfigure(GdkEventConfigure *ev); void onRealize(); void onMap(GdkEventAny *ev); @@ -68,6 +71,9 @@ class Bar { int bottom = 0; int left = 0; } margins_; + struct zwlr_layer_surface_v1 *layer_surface_; + // use gtk-layer-shell instead of handling layer surfaces directly + bool use_gls_ = false; uint32_t width_ = 0; uint32_t height_ = 1; uint8_t anchor_; diff --git a/include/client.hpp b/include/client.hpp index 715ae58..39b6ae3 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -30,26 +30,24 @@ class Client { const std::string &style) const; void bindInterfaces(); const std::string getValidPath(const std::vector &paths) const; - void handleOutput(std::unique_ptr &output); - bool isValidOutput(const Json::Value &config, std::unique_ptr &output); + void handleOutput(struct waybar_output &output); + bool isValidOutput(const Json::Value &config, struct waybar_output &output); auto setupConfig(const std::string &config_file) -> void; auto setupCss(const std::string &css_file) -> void; - std::unique_ptr &getOutput(uint32_t wl_name); - std::vector getOutputConfigs(std::unique_ptr &output); + struct waybar_output &getOutput(void *); + std::vector getOutputConfigs(struct waybar_output &output); static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); - static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t, int32_t); - static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t, int32_t); - static void handleDone(void *, struct zxdg_output_v1 *); - static void handleName(void *, struct zxdg_output_v1 *, const char *); - static void handleDescription(void *, struct zxdg_output_v1 *, const char *); + static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); + void handleMonitorAdded(Glib::RefPtr monitor); + void handleMonitorRemoved(Glib::RefPtr monitor); - Json::Value config_; - Glib::RefPtr style_context_; - Glib::RefPtr css_provider_; - std::vector> outputs_; + Json::Value config_; + Glib::RefPtr style_context_; + Glib::RefPtr css_provider_; + std::list outputs_; }; } // namespace waybar diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index aa9a0a2..e3873a6 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -6,11 +6,17 @@ #else #include #endif +#include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { +struct waybar_time { + std::locale locale; + date::zoned_seconds ztime; +}; + class Clock : public ALabel { public: Clock(const std::string&, const Json::Value&); @@ -19,6 +25,15 @@ class Clock : public ALabel { private: util::SleeperThread thread_; + std::locale locale_; + const date::time_zone* time_zone_; + bool fixed_time_zone_; + date::year_month_day cached_calendar_ymd_; + std::string cached_calendar_text_; + + 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; }; } // namespace waybar::modules diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp index be66611..5b0f51b 100644 --- a/include/modules/memory.hpp +++ b/include/modules/memory.hpp @@ -17,8 +17,7 @@ class Memory : public ALabel { static inline const std::string data_dir_ = "/proc/meminfo"; void parseMeminfo(); - unsigned long memtotal_; - unsigned long memfree_; + std::unordered_map meminfo_; util::SleeperThread thread_; }; diff --git a/include/modules/mpd.hpp b/include/modules/mpd.hpp index d69618a..d08b28b 100644 --- a/include/modules/mpd.hpp +++ b/include/modules/mpd.hpp @@ -36,6 +36,7 @@ class MPD : public ALabel { bool stopped(); bool playing(); + bool paused(); const std::string module_name_; diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp index d575f62..541747c 100644 --- a/include/modules/pulseaudio.hpp +++ b/include/modules/pulseaudio.hpp @@ -34,14 +34,17 @@ class Pulseaudio : public ALabel { pa_cvolume pa_volume_; bool muted_; std::string port_name_; + std::string form_factor_; std::string desc_; std::string monitor_; + std::string default_sink_name_; // SOURCE uint32_t source_idx_{0}; uint16_t source_volume_; bool source_muted_; std::string source_port_name_; std::string source_desc_; + std::string default_source_name_; }; } // namespace waybar::modules diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index e0aced1..aaa1e4f 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -21,7 +21,7 @@ class Tray : public AModule { static inline std::size_t nb_hosts_ = 0; Gtk::Box box_; - SNI::Watcher watcher_; + SNI::Watcher::singleton watcher_; SNI::Host host_; }; diff --git a/include/modules/sni/watcher.hpp b/include/modules/sni/watcher.hpp index 599380a..5a55d2d 100644 --- a/include/modules/sni/watcher.hpp +++ b/include/modules/sni/watcher.hpp @@ -7,10 +7,24 @@ namespace waybar::modules::SNI { class Watcher { + private: + Watcher(); + public: - Watcher(std::size_t id); ~Watcher(); + using singleton = std::shared_ptr; + static singleton getInstance() { + static std::weak_ptr weak; + + std::shared_ptr strong = weak.lock(); + if (!strong) { + strong = std::shared_ptr(new Watcher()); + weak = strong; + } + return strong; + } + private: typedef enum { GF_WATCH_TYPE_HOST, GF_WATCH_TYPE_ITEM } GfWatchType; @@ -34,7 +48,6 @@ class Watcher { void updateRegisteredItems(SnWatcher *obj); uint32_t bus_name_id_; - uint32_t watcher_id_; GSList * hosts_ = nullptr; GSList * items_ = nullptr; SnWatcher *watcher_ = nullptr; diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 629556f..7df5362 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -8,6 +8,7 @@ #include #include #include "ipc.hpp" +#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { @@ -28,6 +29,7 @@ class Ipc { void sendCmd(uint32_t type, const std::string &payload = ""); void subscribe(const std::string &payload); void handleEvent(); + void setWorker(std::function &&func); protected: static inline const std::string ipc_magic_ = "i3-ipc"; @@ -38,9 +40,10 @@ class Ipc { struct ipc_response send(int fd, uint32_t type, const std::string &payload = ""); struct ipc_response recv(int fd); - int fd_; - int fd_event_; - std::mutex mutex_; + int fd_; + int fd_event_; + std::mutex mutex_; + util::SleeperThread thread_; }; } // namespace waybar::modules::sway diff --git a/include/modules/sway/mode.hpp b/include/modules/sway/mode.hpp index f0cf74c..a1a88b0 100644 --- a/include/modules/sway/mode.hpp +++ b/include/modules/sway/mode.hpp @@ -6,7 +6,6 @@ #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" -#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { @@ -18,14 +17,11 @@ class Mode : public ALabel, public sigc::trackable { private: void onEvent(const struct Ipc::ipc_response&); - void worker(); std::string mode_; util::JsonParser parser_; std::mutex mutex_; - - util::SleeperThread thread_; - Ipc ipc_; + Ipc ipc_; }; } // namespace waybar::modules::sway diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index 5bb129d..40aaa1a 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -7,7 +7,6 @@ #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" -#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { @@ -20,7 +19,6 @@ class Window : public ALabel, public sigc::trackable { private: void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); - void worker(); std::tuple getFocusedNode(const Json::Value& nodes, std::string& output); void getTree(); @@ -33,9 +31,7 @@ class Window : public ALabel, public sigc::trackable { std::size_t app_nb_; util::JsonParser parser_; std::mutex mutex_; - - util::SleeperThread thread_; - Ipc ipc_; + Ipc ipc_; }; } // namespace waybar::modules::sway diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index 498acc9..fef24bf 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -8,7 +9,6 @@ #include "client.hpp" #include "modules/sway/ipc/client.hpp" #include "util/json.hpp" -#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { @@ -21,7 +21,6 @@ class Workspaces : public AModule, public sigc::trackable { private: void onCmd(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&); - void worker(); bool filterButtons(); Gtk::Button& addButton(const Json::Value&); void onButtonReady(const Json::Value&, Gtk::Button&); @@ -38,9 +37,7 @@ class Workspaces : public AModule, public sigc::trackable { util::JsonParser parser_; std::unordered_map buttons_; std::mutex mutex_; - - util::SleeperThread thread_; - Ipc ipc_; + Ipc ipc_; }; } // namespace waybar::modules::sway diff --git a/include/util/command.hpp b/include/util/command.hpp index 69b1fd4..f3dd4e1 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -72,7 +72,10 @@ inline struct res exec(std::string cmd) { if (!fp) return {-1, ""}; auto output = command::read(fp); auto stat = command::close(fp, pid); - return {WEXITSTATUS(stat), output}; + if (WIFEXITED(stat)) { + return {WEXITSTATUS(stat), output}; + } + return {-1, output}; } inline int32_t forkExec(std::string cmd) { @@ -88,6 +91,7 @@ inline int32_t forkExec(std::string cmd) { // Child executes the command if (!pid) { setpgid(pid, pid); + signal(SIGCHLD, SIG_DFL); execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); exit(0); } else { diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index 2d97199..5f2bb82 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -36,6 +36,10 @@ The *backlight* module displays the current backlight level. typeof: string ++ Command to execute when the module is clicked. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right* ++ typeof: string ++ Command to execute when the module is right clicked. diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 94a99e4..8bcd2e8 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -18,6 +18,10 @@ The *battery* module displays the current capacity and state (eg. charging) of y typeof: string ++ The adapter to monitor, as in /sys/class/power_supply/ instead of auto detect. +*full-at* ++ + typeof: integer ++ + Define the max percentage of the battery, usefull for an old battery, e.g. 96 + *interval* ++ typeof: integer ++ default: 60 ++ @@ -37,32 +41,36 @@ The *battery* module displays the current capacity and state (eg. charging) of y default: {H} h {M} min ++ The format, how the time should be displayed. -*format-icons* - typeof: array/object +*format-icons*: ++ + typeof: array/object ++ Based on the current capacity, the corresponding icon gets selected. ++ The order is *low* to *high*. Or by the state if it is an object. -*max-length* ++ +*max-length*: ++ typeof: integer++ The maximum length in character the module should display. -*rotate* ++ +*rotate*: ++ typeof: integer++ Positive value to rotate the text label. -*on-click* ++ +*on-click*: ++ typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right* ++ typeof: string ++ Command to execute when you right clicked on the module. -*on-scroll-up* ++ +*on-scroll-up*: ++ typeof: string ++ Command to execute when scrolling up on the module. -*on-scroll-down* ++ +*on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index d979a67..6684d89 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -18,7 +18,18 @@ The *clock* module displays the current date and time. *format*: ++ typeof: string ++ default: {:%H:%M} ++ - The format, how the date and time should be displayed. + The format, how the date and time should be displayed. ++ + It uses the format of the date library. See https://howardhinnant.github.io/date/date.html#to_stream_formatting for details. + +*timezone*: ++ + typeof: string ++ + default: inferred local timezone ++ + The timezone to display the time in, e.g. America/New_York. + +*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. *max-length*: ++ typeof: integer ++ @@ -32,6 +43,10 @@ The *clock* module displays the current date and time. typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. @@ -50,6 +65,10 @@ The *clock* module displays the current date and time. View all valid format options in *strftime(3)*. +# FORMAT REPLACEMENTS + +*{calendar}*: Current month calendar + # EXAMPLES ``` diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index e4e5250..27dde96 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -36,6 +36,10 @@ The *cpu* module displays the current cpu utilization. typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index a427be3..905dbc1 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -33,6 +33,12 @@ Addressed by *custom/* You can update it manually with a signal. If no *interval* is defined, it is assumed that the out script loops it self. +*restart-interval*: ++ + typeof: integer ++ + The restart interval (in seconds). + Can't be used with the *interval* option, so only with continuous scripts. + Once the script exit, it'll be re-executed after the *restart-interval*. + *signal*: ++ typeof: integer ++ The signal number used to update the module. @@ -59,6 +65,10 @@ Addressed by *custom/* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 2a69cf4..25d00b1 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -39,6 +39,10 @@ Addressed by *disk* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index 74e2192..1fba291 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -31,6 +31,10 @@ screensaving, also known as "presentation mode". typeof: string ++ Command to execute when clicked on the module. A click also toggles the state +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index a9dfb3a..70718e1 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -38,6 +38,10 @@ Addressed by *memory* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 155e7b3..fc3b1b3 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -13,110 +13,118 @@ The *mpd* module displays information about a running "Music Player Daemon" inst Addressed by *mpd* *server*: ++ - typeof: string ++ - The network address or Unix socket path of the MPD server. If empty, connect to the default host. + typeof: string ++ + The network address or Unix socket path of the MPD server. If empty, connect to the default host. *port*: ++ - typeof: integer ++ - The port MPD listens to. If empty, use the default port. + typeof: integer ++ + The port MPD listens to. If empty, use the default port. *interval*: ++ - typeof: integer++ - default: 5 ++ - The interval in which the connection to the MPD server is retried + typeof: integer++ + default: 5 ++ + The interval in which the connection to the MPD server is retried *timeout*: ++ - typeof: integer++ - default: 30 ++ - The timeout for the connection. Change this if your MPD server has a low `connection_timeout` setting + typeof: integer++ + default: 30 ++ + The timeout for the connection. Change this if your MPD server has a low `connection_timeout` setting *unknown-tag*: ++ - typeof: string ++ - default: "N/A" ++ - The text to display when a tag is not present in the current song, but used in `format` + typeof: string ++ + default: "N/A" ++ + The text to display when a tag is not present in the current song, but used in `format` *format*: ++ - typeof: string ++ - default: "{album} - {artist} - {title}" ++ - Information displayed when a song is playing or paused + typeof: string ++ + default: "{album} - {artist} - {title}" ++ + Information displayed when a song is playing. *format-stopped*: ++ - typeof: string ++ - default: "stopped" ++ - Information displayed when the player is stopped. + typeof: string ++ + default: "stopped" ++ + Information displayed when the player is stopped. + +*format-paused*: ++ + typeof: string ++ + This format is used when a song is paused. *format-disconnected*: ++ - typeof: string ++ - default: "disconnected" ++ - Information displayed when the MPD server can't be reached. + typeof: string ++ + default: "disconnected" ++ + Information displayed when the MPD server can't be reached. *tooltip*: ++ - typeof: bool ++ - default: true ++ - Option to disable tooltip on hover. + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. *tooltip-format*: ++ - typeof: string ++ - default: "MPD (connected)" ++ - Tooltip information displayed when connected to MPD. + typeof: string ++ + default: "MPD (connected)" ++ + Tooltip information displayed when connected to MPD. *tooltip-format-disconnected*: ++ - typeof: string ++ - default: "MPD (disconnected)" ++ - Tooltip information displayed when the MPD server can't be reached. + typeof: string ++ + default: "MPD (disconnected)" ++ + Tooltip information displayed when the MPD server can't be reached. *rotate*: ++ - typeof: integer ++ - Positive value to rotate the text label. + typeof: integer ++ + Positive value to rotate the text label. *max-length*: ++ - typeof: integer ++ - The maximum length in character the module should display. + typeof: integer ++ + The maximum length in character the module should display. *on-click*: ++ - typeof: string ++ - Command to execute when clicked on the module. + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ - typeof: string ++ - Command to execute when you right clicked on the module. + typeof: string ++ + Command to execute when you right clicked on the module. *on-scroll-up*: ++ - typeof: string ++ - Command to execute when scrolling up on the module. + typeof: string ++ + Command to execute when scrolling up on the module. *on-scroll-down*: ++ - typeof: string ++ - Command to execute when scrolling down on the module. + typeof: string ++ + Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ - typeof: double ++ - Threshold to be used when scrolling. + typeof: double ++ + Threshold to be used when scrolling. *state-icons*: ++ - typeof: object ++ - default: {} ++ - Icon to show depending on the play/pause state of the player (*{ "playing": "...", "paused": "..." }*) + typeof: object ++ + default: {} ++ + Icon to show depending on the play/pause state of the player (*{ "playing": "...", "paused": "..." }*) *consume-icons*: ++ - typeof: object ++ - default: {} ++ - Icon to show depending on the "consume" option (*{ "on": "...", "off": "..." }*) + typeof: object ++ + default: {} ++ + Icon to show depending on the "consume" option (*{ "on": "...", "off": "..." }*) *random-icons*: ++ - typeof: object ++ - default: {} ++ - Icon to show depending on the "random" option (*{ "on": "...", "off": "..." }*) + typeof: object ++ + default: {} ++ + Icon to show depending on the "random" option (*{ "on": "...", "off": "..." }*) *repeat-icons*: ++ - typeof: object ++ - default: {} ++ - Icon to show depending on the "repeat" option (*{ "on": "...", "off": "..." }*) + typeof: object ++ + default: {} ++ + Icon to show depending on the "repeat" option (*{ "on": "...", "off": "..." }*) *single-icons*: ++ - typeof: object ++ - default: {} ++ - Icon to show depending on the "single" option (*{ "on": "...", "off": "..." }*) + typeof: object ++ + default: {} ++ + Icon to show depending on the "single" option (*{ "on": "...", "off": "..." }*) # FORMAT REPLACEMENTS diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index ed8451f..a557aa3 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -21,6 +21,11 @@ Addressed by *network* default: 60 ++ The interval in which the network information gets polled (e.g. signal strength). +*family*: ++ + typeof: string ++ + default: *ipv4* ++ + The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. + *format*: ++ typeof: string ++ default: *{ifname}* ++ @@ -42,6 +47,11 @@ Addressed by *network* typeof: string ++ This format is used when the displayed interface is disconnected. +*format-icons*: ++ + typeof: array/object ++ + Based on the current signal strength, the corresponding icon gets selected. ++ + The order is *low* to *high*. Or by the state if it is an object. + *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. @@ -54,6 +64,10 @@ Addressed by *network* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. @@ -117,6 +131,8 @@ Addressed by *network* *{bandwidthDownOctets}*: Instant down speed in octets/seconds. +*{icon}*: Icon, as defined in *format-icons*. + # EXAMPLES ``` diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 2b11778..487888a 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -59,6 +59,10 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. @@ -96,16 +100,17 @@ The following strings for *format-icons* are supported. If they are found in the current PulseAudio port name, the corresponding icons will be selected. - *default* (Shown, when no other port is found) -- *headphones* +- *headphone* - *speaker* - *hdmi* - *headset* -- *handsfree* +- *hands-free* - *portable* - *car* - *hifi* - *phone* + # EXAMPLES ``` diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index 64d9a3e..85a25d1 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -29,6 +29,10 @@ Addressed by *sway/mode* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 75a974c..6a9d4e3 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -29,6 +29,10 @@ Addressed by *sway/window* typeof: string ++ Command to execute when clicked on the module. +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + *on-click-right*: ++ typeof: string ++ Command to execute when you right clicked on the module. diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index fe38d23..f9ef31d 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -19,7 +19,7 @@ Addressed by *sway/workspaces* *format*: ++ typeof: string ++ - default: {name} ++ + default: {value} ++ The format, how information should be displayed. *format-icons*: ++ @@ -62,7 +62,9 @@ Addressed by *sway/workspaces* # FORMAT REPLACEMENTS -*{name}*: Name of the workspace, as defined by sway. +*{value}*: Name of the workspace, as defined by sway. + +*{name}*: Number stripped from workspace value. *{icon}*: Icon, as defined in *format-icons*. @@ -75,6 +77,7 @@ Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string matches is found. - *urgent*: Will be shown, when workspace is flagged as urgent - *focused*: Will be shown, when workspace is focused +- *persistent*: Will be shown, when workspace is persistent one. # PERSISTENT WORKSPACES diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 8177969..eeae546 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -13,71 +13,83 @@ The *temperature* module displays the current temperature from a thermal zone. Addressed by *temperature* *thermal-zone*: ++ - typeof: integer ++ - The thermal zone, as in */sys/class/thermal/*. + typeof: integer ++ + The thermal zone, as in */sys/class/thermal/*. *hwmon-path*: ++ - typeof: string ++ - The temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*. + typeof: string ++ + The temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*. + +*hwmon-path-abs*: ++ + typeof: string ++ + The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*. + +*input-filename*: ++ + typeof: string ++ + The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input* *critical-threshold*: ++ - typeof: integer ++ - The threshold before it is considered critical (Celcius). + typeof: integer ++ + The threshold before it is considered critical (Celsius). *interval*: ++ - typeof: integer ++ - default: 10 ++ - The interval in which the information gets polled. + typeof: integer ++ + default: 10 ++ + The interval in which the information gets polled. *format-critical*: ++ - typeof: string ++ - The format to use when temperature is considered critical + typeof: string ++ + The format to use when temperature is considered critical *format*: ++ - typeof: string ++ - default: {temperatureC}°C ++ - The format (Celcius/Farenheit) in which the temperature should be displayed. + typeof: string ++ + default: {temperatureC}°C ++ + The format (Celsius/Fahrenheit) in which the temperature should be displayed. *format-icons*: ++ - typeof: array ++ - Based on the current temperature (Celcius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*. + typeof: array ++ + Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*. *rotate*: ++ - typeof: integer ++ - Positive value to rotate the text label. + typeof: integer ++ + Positive value to rotate the text label. *max-length*: ++ - typeof: integer ++ - The maximum length in characters the module should display. + typeof: integer ++ + The maximum length in characters the module should display. *on-click*: ++ - typeof: string ++ - Command to execute when you clicked on the module. + typeof: string ++ + Command to execute when you clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. *on-click-right*: ++ - typeof: string ++ - Command to execute when you right clicked on the module. + typeof: string ++ + Command to execute when you right clicked on the module. *on-scroll-up*: ++ - typeof: string ++ - Command to execute when scrolling up on the module. + typeof: string ++ + Command to execute when scrolling up on the module. *on-scroll-down*: ++ - typeof: string ++ - Command to execute when scrolling down on the module. + typeof: string ++ + Command to execute when scrolling down on the module. *smooth-scrolling-threshold*: ++ - typeof: double ++ - Threshold to be used when scrolling. + typeof: double ++ + Threshold to be used when scrolling. *tooltip*: ++ - typeof: bool ++ - default: true ++ - Option to disable tooltip on hover. + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. # FORMAT REPLACEMENTS -*{temperatureC}*: Temperature in Celcius. +*{temperatureC}*: Temperature in Celsius. *{temperatureF}*: Temperature in Fahrenheit. diff --git a/man/waybar.5.scd b/man/waybar.5.scd index 3278c63..758d90a 100644 --- a/man/waybar.5.scd +++ b/man/waybar.5.scd @@ -23,7 +23,8 @@ Also a minimal example configuration can be found on the at the bottom of this m *layer* ++ typeof: string ++ default: bottom ++ - Decide if the bar is displayed in front of the windows or behind them. + Decide if the bar is displayed in front (*top*) of the windows or behind (*bottom*) + them. *output* ++ typeof: string|array ++ @@ -66,6 +67,12 @@ Also a minimal example configuration can be found on the at the bottom of this m typeof: string ++ Optional name added as a CSS class, for styling multiple waybars. +*gtk-layer-shell* ++ + typeof: bool ++ + default: true ++ + Option to disable the use of gtk-layer-shell for popups. + Only functional if compiled with gtk-layer-shell support. + # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). diff --git a/meson.build b/meson.build index a9d44ee..1dab13d 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.8.0', + version: '0.9.2', license: 'MIT', default_options : [ 'cpp_std=c++17', @@ -9,6 +9,8 @@ project( ], ) +compiler = meson.get_compiler('cpp') + cpp_args = [] cpp_link_args = [] @@ -16,13 +18,14 @@ if get_option('libcxx') cpp_args += ['-stdlib=libc++'] cpp_link_args += ['-stdlib=libc++', '-lc++abi'] - cpp_link_args += ['-lc++fs'] + if compiler.has_link_argument('-lc++fs') + cpp_link_args += ['-lc++fs'] + endif else cpp_link_args += ['-lstdc++fs'] endif -compiler = meson.get_compiler('cpp') -git = find_program('git', required: false) +git = find_program('git', native: true, required: false) if not git.found() add_project_arguments('-DVERSION="@0@"'.format(meson.project_version()), language: 'cpp') @@ -42,6 +45,22 @@ if not compiler.has_header('filesystem') endif endif +code = ''' +#include +#include +int main(int argc, char** argv) { + locale_t locale = newlocale(LC_ALL, "en_US.UTF-8", nullptr); + char* str; + str = nl_langinfo_l(_NL_TIME_WEEK_1STDAY, locale); + str = nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, locale); + freelocale(locale); + return 0; +} +''' +if compiler.links(code, name : 'nl_langinfo with _NL_TIME_WEEK_1STDAY, _NL_TIME_FIRST_WEEKDAY') + add_project_arguments('-DHAVE_LANGINFO_1STDAY', language: 'cpp') +endif + add_global_arguments(cpp_args, language : 'cpp') add_global_link_arguments(cpp_link_args, language : 'cpp') @@ -52,7 +71,7 @@ spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdl wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') -gtkmm = dependency('gtkmm-3.0') +gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) jsoncpp = dependency('jsoncpp') @@ -62,7 +81,11 @@ 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')) +gtk_layer_shell = dependency('gtk-layer-shell-0', + required: get_option('gtk-layer-shell'), + fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) +tz_dep = dependency('date', default_options : [ 'use_system_tzdb=true' ], fallback: [ 'date', 'tz_dep' ]) prefix = get_option('prefix') conf_data = configuration_data() @@ -136,6 +159,10 @@ if libmpdclient.found() src_files += 'src/modules/mpd.cpp' endif +if gtk_layer_shell.found() + add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') +endif + subdir('protocol') executable( @@ -158,7 +185,9 @@ executable( libnlgen, libpulse, libudev, - libmpdclient + libmpdclient, + gtk_layer_shell, + tz_dep ], include_directories: [include_directories('include')], install: true, diff --git a/meson_options.txt b/meson_options.txt index 1b86b57..8d442c7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,3 +7,4 @@ 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('out', type: 'string', value : '/', description: 'output prefix directory') +option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') diff --git a/protocol/dbus-status-notifier-item.xml b/protocol/dbus-status-notifier-item.xml index e46eb3c..c33cd84 100644 --- a/protocol/dbus-status-notifier-item.xml +++ b/protocol/dbus-status-notifier-item.xml @@ -31,7 +31,9 @@ + @@ -44,4 +46,4 @@ - \ No newline at end of file + diff --git a/resources/config b/resources/config index 4e39239..832f76c 100644 --- a/resources/config +++ b/resources/config @@ -1,5 +1,5 @@ { - "layer": "top", // Waybar at top layer + // "layer": "top", // Waybar at top layer // "position": "bottom", // Waybar position (top|bottom|left|right) "height": 30, // Waybar height (to be removed for auto height) // "width": 1280, // Waybar width @@ -64,7 +64,8 @@ "spacing": 10 }, "clock": { - "tooltip-format": "{:%Y-%m-%d | %H:%M}", + // "timezone": "America/New_York", + "tooltip-format": "{:%Y %B}\n{calendar}", "format-alt": "{:%Y-%m-%d}" }, "cpu": { @@ -121,8 +122,8 @@ "format-source": "{volume}% ", "format-source-muted": "", "format-icons": { - "headphones": "", - "handsfree": "", + "headphone": "", + "hands-free": "", "headset": "", "phone": "", "portable": "", diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index 4d75a25..cf3df4b 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -38,6 +38,8 @@ def on_metadata(player, metadata, manager): elif player.get_artist() != '' and player.get_title() != '': track_info = '{artist} - {title}'.format(artist=player.get_artist(), title=player.get_title()) + else: + track_info = player.get_title() if player.props.status != 'Playing' and track_info: track_info = ' ' + track_info @@ -77,7 +79,7 @@ def signal_handler(sig, frame): def parse_arguments(): parser = argparse.ArgumentParser() - # Increase verbosity with every occurance of -v + # Increase verbosity with every occurence of -v parser.add_argument('-v', '--verbose', action='count', default=0) # Define for which player we're listening @@ -123,4 +125,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/resources/waybar.service.in b/resources/waybar.service.in index 1c086bf..03262a3 100644 --- a/resources/waybar.service.in +++ b/resources/waybar.service.in @@ -2,6 +2,7 @@ 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 [Service] Type=dbus diff --git a/src/bar.cpp b/src/bar.cpp index fc88511..431a564 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -1,3 +1,7 @@ +#ifdef HAVE_GTK_LAYER_SHELL +#include +#endif + #include "bar.hpp" #include "client.hpp" #include "factory.hpp" @@ -8,7 +12,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, surface(nullptr), - layer_surface(nullptr), + layer_surface_(nullptr), anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP), left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -28,11 +32,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) height_ = config["height"].isUInt() ? config["height"].asUInt() : height_; width_ = config["width"].isUInt() ? config["width"].asUInt() : width_; - 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_); - if (config["position"] == "bottom") { anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else if (config["position"] == "left") { @@ -98,6 +97,17 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps}; } +#ifdef HAVE_GTK_LAYER_SHELL + use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; + if (use_gls_) { + initGtkLayerShell(); + } +#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)); + window.set_size_request(width_, height_); setupWidgets(); if (window.get_realized()) { @@ -131,11 +141,48 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) { tmp_width = ev->width; } } - if (tmp_width != width_ || tmp_height != height_) { + 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(); + // this has to be executed before GtkWindow.realize + gtk_layer_init_for_window(gtk_window); + gtk_layer_set_keyboard_interactivity(gtk_window, FALSE); + auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM; + gtk_layer_set_layer(gtk_window, layer); + gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); + gtk_layer_set_namespace(gtk_window, "waybar"); + + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); + + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom); + + if (width_ > 1 && height_ > 1) { + /* configure events are not emitted if the bar is using initial size */ + setExclusiveZone(width_, height_); + } +} +#endif + void waybar::Bar::onRealize() { auto gdk_window = window.get_window()->gobj(); gdk_wayland_window_set_use_custom_surface(gdk_window); @@ -145,16 +192,22 @@ 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()); auto layer = config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - layer_surface = zwlr_layer_shell_v1_get_layer_surface( - client->layer_shell, surface, output->output, layer, "waybar"); + layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( + client->layer_shell, surface, wl_output, layer, "waybar"); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, false); - zwlr_layer_surface_v1_set_anchor(layer_surface, anchor_); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); + zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); zwlr_layer_surface_v1_set_margin( - layer_surface, margins_.top, margins_.right, margins_.bottom, margins_.left); + layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); setSurfaceSize(width_, height_); setExclusiveZone(width_, height_); @@ -162,7 +215,7 @@ void waybar::Bar::onMap(GdkEventAny* ev) { .configure = layerSurfaceHandleConfigure, .closed = layerSurfaceHandleClosed, }; - zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); + zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); wl_surface_commit(surface); wl_display_roundtrip(client->wl_display); @@ -182,7 +235,15 @@ void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) { } } spdlog::debug("Set exclusive zone {} for output {}", zone, output->name); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone); + +#ifdef HAVE_GTK_LAYER_SHELL + if (use_gls_) { + gtk_layer_set_exclusive_zone(window.gobj(), zone); + } else +#endif + { + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); + } } void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) { @@ -198,7 +259,7 @@ void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) { width += margins_.right + margins_.left; } spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name); - zwlr_layer_surface_v1_set_size(layer_surface, width, height); + zwlr_layer_surface_v1_set_size(layer_surface_, width, height); } // Converting string to button code rn as to avoid doing it later @@ -282,9 +343,9 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surf void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) { auto o = static_cast(data); - if (o->layer_surface) { - zwlr_layer_surface_v1_destroy(o->layer_surface); - o->layer_surface = nullptr; + if (o->layer_surface_) { + zwlr_layer_surface_v1_destroy(o->layer_surface_); + o->layer_surface_ = nullptr; } o->modules_left_.clear(); o->modules_center_.clear(); diff --git a/src/client.cpp b/src/client.cpp index 34efdf1..f7c70e0 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,4 +1,5 @@ #include "client.hpp" +#include #include #include #include @@ -33,11 +34,6 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { client->layer_shell = static_cast( wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - auto wl_output = static_cast( - wl_registry_bind(registry, name, &wl_output_interface, version)); - client->outputs_.emplace_back(new struct waybar_output({wl_output, "", name, nullptr})); - client->handleOutput(client->outputs_.back()); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { client->xdg_output_manager = static_cast(wl_registry_bind( @@ -50,91 +46,51 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint void waybar::Client::handleGlobalRemove(void * data, struct wl_registry * /*registry*/, uint32_t name) { - auto client = static_cast(data); - for (auto it = client->bars.begin(); it != client->bars.end();) { - if ((*it)->output->wl_name == name) { - auto output_name = (*it)->output->name; - (*it)->window.close(); - it = client->bars.erase(it); - spdlog::info("Bar removed from output: {}", output_name); - } else { - ++it; - } - } - auto it = std::find_if(client->outputs_.begin(), - client->outputs_.end(), - [&name](const auto &output) { return output->wl_name == name; }); - if (it != client->outputs_.end()) { - if ((*it)->xdg_output != nullptr) { - zxdg_output_v1_destroy((*it)->xdg_output); - (*it)->xdg_output = nullptr; - } - if ((*it)->output != nullptr) { - wl_output_destroy((*it)->output); - (*it)->output = nullptr; - } - client->outputs_.erase(it); - } + // Nothing here } -void waybar::Client::handleOutput(std::unique_ptr &output) { +void waybar::Client::handleOutput(struct waybar_output &output) { static const struct zxdg_output_v1_listener xdgOutputListener = { - .logical_position = handleLogicalPosition, - .logical_size = handleLogicalSize, - .done = handleDone, - .name = handleName, - .description = handleDescription, + .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, + .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, + .done = [](void *, struct zxdg_output_v1 *) {}, + .name = &handleOutputName, + .description = [](void *, struct zxdg_output_v1 *, const char *) {}, }; - output->xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, output->output); - zxdg_output_v1_add_listener(output->xdg_output, &xdgOutputListener, &output->wl_name); + // owned by output->monitor; no need to destroy + auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); + output.xdg_output.reset(zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, wl_output)); + zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); } -void waybar::Client::handleLogicalPosition(void * /*data*/, - struct zxdg_output_v1 * /*zxdg_output_v1*/, - int32_t /*x*/, int32_t /*y*/) { - // Nothing here -} - -void waybar::Client::handleLogicalSize(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/, - int32_t /*width*/, int32_t /*height*/) { - // Nothing here -} - -void waybar::Client::handleDone(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/) { - // Nothing here -} - -bool waybar::Client::isValidOutput(const Json::Value & config, - std::unique_ptr &output) { +bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) { bool found = true; if (config["output"].isArray()) { bool in_array = false; for (auto const &output_conf : config["output"]) { - if (output_conf.isString() && output_conf.asString() == output->name) { + if (output_conf.isString() && output_conf.asString() == output.name) { in_array = true; break; } } found = in_array; } - if (config["output"].isString() && config["output"].asString() != output->name) { + if (config["output"].isString() && config["output"].asString() != output.name) { found = false; } return found; } -std::unique_ptr &waybar::Client::getOutput(uint32_t wl_name) { - auto it = std::find_if(outputs_.begin(), outputs_.end(), [&wl_name](const auto &output) { - return output->wl_name == wl_name; - }); +struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { + auto it = std::find_if( + outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); if (it == outputs_.end()) { throw std::runtime_error("Unable to find valid output"); } return *it; } -std::vector waybar::Client::getOutputConfigs( - std::unique_ptr &output) { +std::vector waybar::Client::getOutputConfigs(struct waybar_output &output) { std::vector configs; if (config_.isArray()) { for (auto const &config : config_) { @@ -148,27 +104,23 @@ std::vector waybar::Client::getOutputConfigs( return configs; } -void waybar::Client::handleName(void * data, struct zxdg_output_v1 * /*xdg_output*/, - const char *name) { - auto wl_name = *static_cast(data); +void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/, + const char *name) { auto client = waybar::Client::inst(); try { - auto &output = client->getOutput(wl_name); - output->name = name; + auto &output = client->getOutput(data); + output.name = name; + spdlog::debug("Output detected: {} ({} {})", + name, + output.monitor->get_manufacturer(), + output.monitor->get_model()); auto configs = client->getOutputConfigs(output); if (configs.empty()) { - if (output->output != nullptr) { - wl_output_destroy(output->output); - output->output = nullptr; - } - if (output->xdg_output != nullptr) { - zxdg_output_v1_destroy(output->xdg_output); - output->xdg_output = nullptr; - } + output.xdg_output.reset(); } else { wl_display_roundtrip(client->wl_display); for (const auto &config : configs) { - client->bars.emplace_back(std::make_unique(output.get(), config)); + client->bars.emplace_back(std::make_unique(&output, config)); Glib::RefPtr screen = client->bars.back()->window.get_screen(); client->style_context_->add_provider_for_screen( screen, client->css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER); @@ -179,9 +131,25 @@ void waybar::Client::handleName(void * data, struct zxdg_output_v1 * /*xdg_ } } -void waybar::Client::handleDescription(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/, - const char * /*description*/) { - // Nothing here +void waybar::Client::handleMonitorAdded(Glib::RefPtr monitor) { + auto &output = outputs_.emplace_back(); + output.monitor = monitor; + handleOutput(output); +} + +void waybar::Client::handleMonitorRemoved(Glib::RefPtr monitor) { + spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model()); + for (auto it = bars.begin(); it != bars.end();) { + if ((*it)->output->monitor == monitor) { + auto output_name = (*it)->output->name; + (*it)->window.close(); + it = bars.erase(it); + spdlog::info("Bar removed from output: {}", output_name); + } else { + ++it; + } + } + outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); } std::tuple waybar::Client::getConfigs( @@ -240,6 +208,14 @@ void waybar::Client::bindInterfaces() { if (layer_shell == nullptr || xdg_output_manager == nullptr) { throw std::runtime_error("Failed to acquire required resources."); } + // add existing outputs and subscribe to updates + for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { + auto monitor = gdk_display->get_monitor(i); + handleMonitorAdded(monitor); + } + gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); + gdk_display->signal_monitor_removed().connect( + sigc::mem_fun(*this, &Client::handleMonitorRemoved)); } int waybar::Client::main(int argc, char *argv[]) { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 632abd9..bad3355 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -115,6 +115,16 @@ const std::tuple waybar::modules::Battery::getInfos time_remaining = -(float)(total_energy_full - total_energy) / total_power; } uint16_t capacity = total / batteries_.size(); + // Handle full-at + if (config_["full-at"].isUInt()) { + auto full_at = config_["full-at"].asUInt(); + if (full_at < 100) { + capacity = static_cast(capacity / full_at) * 100; + if (capacity > full_at) { + capacity = full_at; + } + } + } return {capacity, time_remaining, status}; } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); @@ -163,7 +173,12 @@ auto waybar::modules::Battery::update() -> void { } label_.set_tooltip_text(tooltip_text); } + // Transform to lowercase std::transform(status.begin(), status.end(), status.begin(), ::tolower); + // Replace space with dash + std::transform(status.begin(), status.end(), status.begin(), [](char ch) { + return ch == ' ' ? '-' : ch; + }); auto format = format_; auto state = getState(capacity, true); if (!old_status_.empty()) { diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 4e05868..20aa1ce 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -1,8 +1,28 @@ #include "modules/clock.hpp" -#include +#include +#include +#ifdef HAVE_LANGINFO_1STDAY +#include +#include +#endif + +using waybar::modules::waybar_time; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) - : ALabel(config, "clock", id, "{:%H:%M}", 60) { + : ALabel(config, "clock", id, "{:%H:%M}", 60) + , fixed_time_zone_(false) +{ + if (config_["timezone"].isString()) { + time_zone_ = date::locate_zone(config_["timezone"].asString()); + fixed_time_zone_ = true; + } + + if (config_["locale"].isString()) { + locale_ = std::locale(config_["locale"].asString()); + } else { + locale_ = std::locale(""); + } + thread_ = [this] { dp.emit(); auto now = std::chrono::system_clock::now(); @@ -13,21 +33,115 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) } auto waybar::modules::Clock::update() -> void { - tzset(); // Update timezone information - auto now = std::chrono::system_clock::now(); - auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - auto text = fmt::format(format_, localtime); + if (!fixed_time_zone_) { + // Time zone can change. Be sure to pick that. + time_zone_ = date::current_zone(); + } + auto now = std::chrono::system_clock::now(); + waybar_time wtime = {locale_, + date::make_zoned(time_zone_, date::floor(now))}; + + auto text = fmt::format(format_, wtime); label_.set_markup(text); if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { + const auto calendar = calendar_text(wtime); auto tooltip_format = config_["tooltip-format"].asString(); - auto tooltip_text = fmt::format(tooltip_format, localtime); - label_.set_tooltip_text(tooltip_text); + auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); + label_.set_tooltip_markup(tooltip_text); } else { - label_.set_tooltip_text(text); + label_.set_tooltip_markup(text); } } // Call parent update ALabel::update(); } + +auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { + const auto daypoint = date::floor(wtime.ztime.get_local_time()); + const auto ymd = date::year_month_day(daypoint); + if (cached_calendar_ymd_ == ymd) { + return cached_calendar_text_; + } + + const date::year_month ym(ymd.year(), ymd.month()); + const auto curr_day = ymd.day(); + + std::stringstream os; + const auto first_dow = first_day_of_week(); + weekdays_header(first_dow, os); + + // First week prefixed with spaces if needed. + auto wd = date::weekday(ym/1); + auto empty_days = (wd - first_dow).count(); + if (empty_days > 0) { + os << std::string(empty_days * 3 - 1, ' '); + } + auto last_day = (ym/date::literals::last).day(); + for (auto d = date::day(1); d <= last_day; ++d, ++wd) { + if (wd != first_dow) { + os << ' '; + } else if (unsigned(d) != 1) { + os << '\n'; + } + if (d == curr_day) { + os << "" << date::format("%e", d) << ""; + } else { + os << date::format("%e", d); + } + } + + auto result = os.str(); + cached_calendar_ymd_ = ymd; + cached_calendar_text_ = result; + return result; +} + +auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void { + auto wd = first_dow; + do { + if (wd != first_dow) os << ' '; + Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); + auto wd_len = wd_ustring.length(); + if (wd_len > 2) { + wd_ustring = wd_ustring.substr(0, 2); + wd_len = 2; + } + const std::string pad(2 - wd_len, ' '); + os << pad << wd_ustring; + } while (++wd != first_dow); + os << "\n"; +} + +#ifdef HAVE_LANGINFO_1STDAY +template +using deleter_from_fn = std::integral_constant; + +template +using deleting_unique_ptr = std::unique_ptr>; +#endif + +// Computations done similarly to Linux cal utility. +auto waybar::modules::Clock::first_day_of_week() -> date::weekday { +#ifdef HAVE_LANGINFO_1STDAY + deleting_unique_ptr::type, freelocale> + posix_locale{newlocale(LC_ALL, locale_.name().c_str(), nullptr)}; + if (posix_locale) { + const int i = (std::intptr_t) nl_langinfo_l(_NL_TIME_WEEK_1STDAY, posix_locale.get()); + auto ymd = date::year(i / 10000)/(i / 100 % 100)/(i % 100); + auto wd = date::weekday(ymd); + uint8_t j = *nl_langinfo_l(_NL_TIME_FIRST_WEEKDAY, posix_locale.get()); + return wd + date::days(j - 1); + } +#endif + return date::Sunday; +} + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(const waybar_time& t, FormatContext& ctx) { + return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime)); + } +}; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 7a66447..5a64ce2 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -49,19 +49,24 @@ void waybar::modules::Custom::continuousWorker() { thread_ = [&] { char* buff = nullptr; size_t len = 0; + bool restart = false; if (getline(&buff, &len, fp_) == -1) { int exit_code = 1; if (fp_) { exit_code = WEXITSTATUS(util::command::close(fp_, pid_)); fp_ = nullptr; } - thread_.stop(); if (exit_code != 0) { output_ = {exit_code, ""}; dp.emit(); spdlog::error("{} stopped unexpectedly, is it endless?", name_); } - return; + if (config_["restart-interval"].isUInt()) { + restart = true; + } else { + thread_.stop(); + return; + } } std::string output = buff; @@ -71,6 +76,14 @@ void waybar::modules::Custom::continuousWorker() { } output_ = {0, output}; dp.emit(); + if (restart) { + pid_ = -1; + fp_ = util::command::open(cmd, pid_); + if (!fp_) { + throw std::runtime_error("Unable to open " + cmd); + } + thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); + } }; } diff --git a/src/modules/memory.cpp b/src/modules/memory.cpp index c5b8b40..8e54d27 100644 --- a/src/modules/memory.cpp +++ b/src/modules/memory.cpp @@ -10,11 +10,23 @@ waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config auto waybar::modules::Memory::update() -> void { parseMeminfo(); - if (memtotal_ > 0 && memfree_ >= 0) { - auto total_ram_gigabytes = memtotal_ / std::pow(1024, 2); - int used_ram_percentage = 100 * (memtotal_ - memfree_) / memtotal_; - auto used_ram_gigabytes = (memtotal_ - memfree_) / std::pow(1024, 2); - auto available_ram_gigabytes = memfree_ / std::pow(1024, 2); + + unsigned long memtotal = meminfo_["MemTotal"]; + unsigned long memfree; + if (meminfo_.count("MemAvailable")) { + // New kernels (3.4+) have an accurate available memory field. + memfree = meminfo_["MemAvailable"]; + } else { + // Old kernel; give a best-effort approximation of available memory. + memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + + meminfo_["SReclaimable"] - meminfo_["Shmem"]; + } + + if (memtotal > 0 && memfree >= 0) { + auto total_ram_gigabytes = memtotal / std::pow(1024, 2); + int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; + auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); + auto available_ram_gigabytes = memfree / std::pow(1024, 2); getState(used_ram_percentage); label_.set_markup(fmt::format(format_, @@ -35,7 +47,6 @@ auto waybar::modules::Memory::update() -> void { } void waybar::modules::Memory::parseMeminfo() { - int64_t memfree = -1, membuffer = -1, memcache = -1, memavail = -1; std::ifstream info(data_dir_); if (!info.is_open()) { throw std::runtime_error("Can't open " + data_dir_); @@ -46,23 +57,9 @@ void waybar::modules::Memory::parseMeminfo() { if (posDelim == std::string::npos) { continue; } + std::string name = line.substr(0, posDelim); int64_t value = std::stol(line.substr(posDelim + 1)); - - if (name.compare("MemTotal") == 0) { - memtotal_ = value; - } else if (name.compare("MemAvailable") == 0) { - memavail = value; - } else if (name.compare("MemFree") == 0) { - memfree = value; - } else if (name.compare("Buffers") == 0) { - membuffer = value; - } else if (name.compare("Cached") == 0) { - memcache = value; - } - if (memtotal_ > 0 && (memavail >= 0 || (memfree > -1 && membuffer > -1 && memcache > -1))) { - break; - } + meminfo_[name] = value; } - memfree_ = memavail >= 0 ? memavail : memfree + membuffer + memcache; } diff --git a/src/modules/mpd.cpp b/src/modules/mpd.cpp index 6dca5ee..e37b687 100644 --- a/src/modules/mpd.cpp +++ b/src/modules/mpd.cpp @@ -143,7 +143,9 @@ void waybar::modules::MPD::setLabel() { if (playing()) { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); - } else { + } else if (paused()) { + format = + config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } @@ -349,3 +351,5 @@ bool waybar::modules::MPD::stopped() { } bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; } + +bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 54a92ba..a98e18d 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "util/format.hpp" @@ -278,8 +279,13 @@ 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 (text != label_.get_label()) { + if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); + if (text.empty()) { + event_box_.hide(); + } else { + event_box_.show(); + } } if (tooltipEnabled()) { if (tooltip_format.empty() && config_["tooltip-format"].isString()) { @@ -437,7 +443,6 @@ out: } void waybar::modules::Network::getInterfaceAddress() { - unsigned int cidrRaw; struct ifaddrs *ifaddr, *ifa; cidr_ = 0; int success = getifaddrs(&ifaddr); @@ -449,18 +454,34 @@ void waybar::modules::Network::getInterfaceAddress() { if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ && ifa->ifa_name == ifname_) { char ipaddr[INET6_ADDRSTRLEN]; - ipaddr_ = inet_ntop(family_, - &reinterpret_cast(ifa->ifa_addr)->sin_addr, - ipaddr, - INET6_ADDRSTRLEN); char netmask[INET6_ADDRSTRLEN]; - auto net_addr = reinterpret_cast(ifa->ifa_netmask); - netmask_ = inet_ntop(family_, &net_addr->sin_addr, netmask, INET6_ADDRSTRLEN); - cidrRaw = net_addr->sin_addr.s_addr; unsigned int cidr = 0; - while (cidrRaw) { - cidr += cidrRaw & 1; - cidrRaw >>= 1; + if (family_ == AF_INET) { + ipaddr_ = inet_ntop(AF_INET, + &reinterpret_cast(ifa->ifa_addr)->sin_addr, + ipaddr, + INET_ADDRSTRLEN); + auto net_addr = reinterpret_cast(ifa->ifa_netmask); + netmask_ = inet_ntop(AF_INET, &net_addr->sin_addr, netmask, INET_ADDRSTRLEN); + unsigned int cidrRaw = net_addr->sin_addr.s_addr; + while (cidrRaw) { + cidr += cidrRaw & 1; + cidrRaw >>= 1; + } + } else { + ipaddr_ = inet_ntop(AF_INET6, + &reinterpret_cast(ifa->ifa_addr)->sin6_addr, + ipaddr, + INET6_ADDRSTRLEN); + auto net_addr = reinterpret_cast(ifa->ifa_netmask); + netmask_ = inet_ntop(AF_INET6, &net_addr->sin6_addr, netmask, INET6_ADDRSTRLEN); + for (size_t i = 0; i < sizeof(net_addr->sin6_addr.s6_addr); ++i) { + unsigned char cidrRaw = net_addr->sin6_addr.s6_addr[i]; + while (cidrRaw) { + cidr += cidrRaw & 1; + cidrRaw >>= 1; + } + } } cidr_ = cidr; break; diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 06c0ad8..571a78e 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -21,7 +21,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value if (context_ == nullptr) { throw std::runtime_error("pa_context_new() failed."); } - if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr) < 0) { + if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) { auto err = fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_))); throw std::runtime_error(err); @@ -52,7 +52,8 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) { pa_context_set_subscribe_callback(c, subscribeCb, data); pa_context_subscribe( c, - static_cast(static_cast(PA_SUBSCRIPTION_MASK_SINK) | + static_cast(static_cast(PA_SUBSCRIPTION_MASK_SERVER) | + static_cast(PA_SUBSCRIPTION_MASK_SINK) | static_cast(PA_SUBSCRIPTION_MASK_SOURCE)), nullptr, nullptr); @@ -109,7 +110,9 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context * conte if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) { return; } - if (facility == PA_SUBSCRIPTION_EVENT_SINK) { + if (facility == PA_SUBSCRIPTION_EVENT_SERVER) { + pa_context_get_server_info(context, serverInfoCb, data); + } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); @@ -131,15 +134,15 @@ void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, voi */ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i, int /*eol*/, void *data) { - if (i != nullptr) { - auto self = static_cast(data); + auto pa = static_cast(data); + if (i != nullptr && pa->default_source_name_ == i->name) { auto source_volume = static_cast(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM}; - self->source_volume_ = std::round(source_volume * 100.0F); - self->source_idx_ = i->index; - self->source_muted_ = i->mute != 0; - self->source_desc_ = i->description; - self->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; - self->dp.emit(); + pa->source_volume_ = std::round(source_volume * 100.0F); + pa->source_idx_ = i->index; + pa->source_muted_ = i->mute != 0; + pa->source_desc_ = i->description; + pa->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; + pa->dp.emit(); } } @@ -147,9 +150,9 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p * Called when the requested sink information is ready. */ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, - int /*eol*/, void * data) { - if (i != nullptr) { - auto pa = static_cast(data); + int /*eol*/, void *data) { + auto pa = static_cast(data); + if (i != nullptr && pa->default_sink_name_ == i->name) { pa->pa_volume_ = i->volume; float volume = static_cast(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM}; pa->sink_idx_ = i->index; @@ -158,6 +161,9 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_ pa->desc_ = i->description; pa->monitor_ = i->monitor_source_name; pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; + if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) { + pa->form_factor_ = ff; + } pa->dp.emit(); } } @@ -168,16 +174,20 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_ */ void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) { + auto pa = static_cast(data); + pa->default_sink_name_ = i->default_sink_name; + pa->default_source_name_ = i->default_source_name; + pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data); pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data); } static const std::array ports = { - "headphones", + "headphone", "speaker", "hdmi", "headset", - "handsfree", + "hands-free", "portable", "car", "hifi", @@ -185,7 +195,7 @@ static const std::array ports = { }; const std::string waybar::modules::Pulseaudio::getPortIcon() const { - std::string nameLC = port_name_; + std::string nameLC = port_name_ + form_factor_; std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { if (nameLC.find(port) != std::string::npos) { @@ -197,21 +207,27 @@ const std::string waybar::modules::Pulseaudio::getPortIcon() const { auto waybar::modules::Pulseaudio::update() -> void { auto format = format_; - std::string format_name = "format"; - if (monitor_.find("a2dp_sink") != std::string::npos) { - format_name = format_name + "-bluetooth"; - label_.get_style_context()->add_class("bluetooth"); - } else { - label_.get_style_context()->remove_class("bluetooth"); + if (!alt_) { + std::string format_name = "format"; + if (monitor_.find("a2dp_sink") != std::string::npos) { + format_name = format_name + "-bluetooth"; + label_.get_style_context()->add_class("bluetooth"); + } else { + label_.get_style_context()->remove_class("bluetooth"); + } + if (muted_) { + // Check muted bluetooth format exist, otherwise fallback to default muted format + if (format_name != "format" && !config_[format_name + "-muted"].isString()) { + format_name = "format"; + } + format_name = format_name + "-muted"; + label_.get_style_context()->add_class("muted"); + } else { + label_.get_style_context()->remove_class("muted"); + } + format = + config_[format_name].isString() ? config_[format_name].asString() : format; } - if (muted_ ) { - format_name = format_name + "-muted"; - label_.get_style_context()->add_class("muted"); - } else { - label_.get_style_context()->remove_class("muted"); - } - format = - config_[format_name].isString() ? config_[format_name].asString() : format; // TODO: find a better way to split source/sink std::string format_source = "{volume}%"; if (source_muted_ && config_["format-source-muted"].isString()) { diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index bcc66e2..b298e44 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -265,7 +265,11 @@ void Item::updateImage() { if (pixbuf->gobj() != nullptr) { // An icon specified by path and filename may be the wrong size for // the tray - pixbuf = pixbuf->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR); + // Keep the aspect ratio and scale to make the height equal to icon_size + // If people have non square icons, assume they want it to grow in width not height + int width = icon_size * pixbuf->get_width() / pixbuf->get_height(); + + pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR); image.set(pixbuf); } } else { diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index a698a97..e16837f 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -6,7 +6,7 @@ namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "tray", id), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), - watcher_(nb_hosts_), + watcher_(SNI::Watcher::getInstance()), host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1), std::bind(&Tray::onRemove, this, std::placeholders::_1)) { spdlog::warn( diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 1db3708..73b3eac 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -3,14 +3,13 @@ using namespace waybar::modules::SNI; -Watcher::Watcher(std::size_t id) +Watcher::Watcher() : bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Watcher::busAcquired), Gio::DBus::SlotNameAcquired(), Gio::DBus::SlotNameLost(), Gio::DBus::BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | Gio::DBus::BUS_NAME_OWNER_FLAGS_REPLACE)), - watcher_id_(id), watcher_(sn_watcher_skeleton_new()) {} Watcher::~Watcher() { @@ -23,6 +22,7 @@ Watcher::~Watcher() { g_slist_free_full(items_, gfWatchFree); items_ = nullptr; } + Gio::DBus::unown_name(bus_name_id_); auto iface = G_DBUS_INTERFACE_SKELETON(watcher_); g_dbus_interface_skeleton_unexport(iface); } @@ -34,7 +34,7 @@ void Watcher::busAcquired(const Glib::RefPtr& conn, Glib: if (error != nullptr) { // Don't print an error when a watcher is already present if (error->code != 2) { - spdlog::error("Watcher {}: {}", watcher_id_, error->message); + spdlog::error("Watcher: {}", error->message); } g_error_free(error); return; diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index eae6c76..58aed60 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -10,19 +10,23 @@ Ipc::Ipc() { } Ipc::~Ipc() { - // To fail the IPC header - write(fd_, "close-sway-ipc", 14); - write(fd_event_, "close-sway-ipc", 14); + thread_.stop(); + if (fd_ > 0) { + // To fail the IPC header + write(fd_, "close-sway-ipc", 14); close(fd_); fd_ = -1; } if (fd_event_ > 0) { + write(fd_event_, "close-sway-ipc", 14); close(fd_event_); fd_event_ = -1; } } +void Ipc::setWorker(std::function&& func) { thread_ = func; } + const std::string Ipc::getSocketPath() const { const char* env = getenv("SWAYSOCK"); if (env != nullptr) { diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index 33e4f72..605271c 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -8,7 +8,13 @@ Mode::Mode(const std::string& id, const Json::Value& config) ipc_.subscribe(R"(["mode"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent)); // Launch worker - worker(); + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Mode: {}", e.what()); + } + }); dp.emit(); } @@ -31,16 +37,6 @@ void Mode::onEvent(const struct Ipc::ipc_response& res) { } } -void Mode::worker() { - thread_ = [this] { - try { - ipc_.handleEvent(); - } catch (const std::exception& e) { - spdlog::error("Mode: {}", e.what()); - } - }; -} - auto Mode::update() -> void { if (mode_.empty()) { event_box_.hide(); diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 1f90eba..f10bf1c 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -11,7 +11,13 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) // Get Initial focused window getTree(); // Launch worker - worker(); + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Window: {}", e.what()); + } + }); } void Window::onEvent(const struct Ipc::ipc_response& res) { getTree(); } @@ -28,16 +34,6 @@ void Window::onCmd(const struct Ipc::ipc_response& res) { } } -void Window::worker() { - thread_ = [this] { - try { - ipc_.handleEvent(); - } catch (const std::exception& e) { - spdlog::error("Window: {}", e.what()); - } - }; -} - auto Window::update() -> void { if (!old_app_id_.empty()) { bar_.window.get_style_context()->remove_class(old_app_id_); diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index d0c8cc3..91df113 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -22,7 +22,13 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } // Launch worker - worker(); + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception &e) { + spdlog::error("Workspaces: {}", e.what()); + } + }); } void Workspaces::onEvent(const struct Ipc::ipc_response &res) { @@ -102,16 +108,6 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { } } -void Workspaces::worker() { - thread_ = [this] { - try { - ipc_.handleEvent(); - } catch (const std::exception &e) { - spdlog::error("Workspaces: {}", e.what()); - } - }; -} - bool Workspaces::filterButtons() { bool needReorder = false; for (auto it = buttons_.begin(); it != buttons_.end();) { @@ -161,12 +157,13 @@ auto Workspaces::update() -> void { if (needReorder) { box_.reorder_child(button, it - workspaces_.begin()); } - std::string output = getIcon((*it)["name"].asString(), *it); + std::string output = (*it)["name"].asString(); if (config_["format"].isString()) { auto format = config_["format"].asString(); output = fmt::format(format, - fmt::arg("icon", output), - fmt::arg("name", trimWorkspaceName((*it)["name"].asString())), + fmt::arg("icon", getIcon(output, *it)), + fmt::arg("value", output), + fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString())); } if (!config_["disable-markup"].asBool()) { @@ -211,6 +208,8 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node if (config_["format-icons"][key].isString() && node[key].asBool()) { return config_["format-icons"][key].asString(); } + } else if (config_["format_icons"]["persistent"].isString() && node["target_output"].isString()) { + return config_["format-icons"]["persistent"].asString(); } else if (config_["format-icons"][key].isString()) { return config_["format-icons"][key].asString(); } diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 5508fd2..d8307d7 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -1,9 +1,12 @@ #include "modules/temperature.hpp" +#include waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config) : ALabel(config, "temperature", id, "{temperatureC}°C", 10) { if (config_["hwmon-path"].isString()) { file_path_ = config_["hwmon-path"].asString(); + } else if (config_["hwmon-path-abs"].isString() && config_["input-filename"].isString()) { + file_path_ = (*std::filesystem::directory_iterator(config_["hwmon-path-abs"].asString())).path().u8string() + "/" + config_["input-filename"].asString(); } else { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); diff --git a/subprojects/date.wrap b/subprojects/date.wrap new file mode 100644 index 0000000..ea73f0f --- /dev/null +++ b/subprojects/date.wrap @@ -0,0 +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 + +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 diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index b60b22a..eb79283 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -5,6 +5,6 @@ source_url = https://github.com/fmtlib/fmt/archive/5.3.0.tar.gz source_filename = fmt-5.3.0.tar.gz source_hash = defa24a9af4c622a7134076602070b45721a43c51598c8456ec6f2c4dbb51c89 -patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/5.3.0/1/get_zip +patch_url = https://github.com/mesonbuild/fmt/releases/download/5.3.0-1/fmt.zip patch_filename = fmt-5.3.0-1-wrap.zip patch_hash = 18f21a3b8833949c35d4ac88a7059577d5fa24b98786e4b1b2d3d81bb811440f \ No newline at end of file diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap new file mode 100644 index 0000000..b826ab9 --- /dev/null +++ b/subprojects/gtk-layer-shell.wrap @@ -0,0 +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 diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap index 9dac4d8..750036b 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -5,6 +5,6 @@ source_url = https://github.com/gabime/spdlog/archive/v1.3.1.tar.gz source_filename = v1.3.1.tar.gz source_hash = 160845266e94db1d4922ef755637f6901266731c4cb3b30b45bf41efa0e6ab70 -patch_url = https://wrapdb.mesonbuild.com/v1/projects/spdlog/1.3.1/1/get_zip +patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.3.1-1/spdlog.zip patch_filename = spdlog-1.3.1-1-wrap.zip patch_hash = 715a0229781019b853d409cc0bf891ee4b9d3a17bec0cf87f4ad30b28bbecc87