Merge branch 'master' into list_of_times

This commit is contained in:
Alex 2022-03-08 09:59:22 +01:00 committed by GitHub
commit b6655e475b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1062 additions and 286 deletions

View File

@ -11,7 +11,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0 uses: vmactions/freebsd-vm@v0.1.6 # aka FreeBSD 13.0
with: with:
mem: 2048 mem: 2048
usesh: true usesh: true
@ -21,7 +21,7 @@ jobs:
pkg install -y git # subprojects/date pkg install -y git # subprojects/date
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
pkgconf pulseaudio scdoc sndio spdlog pkgconf pulseaudio scdoc sndio spdlog wayland-protocols
run: | run: |
meson build -Dman-pages=enabled meson build -Dman-pages=enabled
ninja -C build ninja -C build

View File

@ -2,4 +2,4 @@
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata

View File

@ -2,7 +2,7 @@
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[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)<br> [AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
#### Current features #### Current features
@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module] libmpdclient [MPD module]
libsndio [sndio module] libsndio [sndio module]
libevdev [KeyboardState module] libevdev [KeyboardState module]
xkbregistry
``` ```
**Build dependencies** **Build dependencies**
@ -101,7 +102,8 @@ sudo apt install \
libsigc++-2.0-dev \ libsigc++-2.0-dev \
libspdlog-dev \ libspdlog-dev \
libwayland-dev \ libwayland-dev \
scdoc scdoc \
libxkbregistry-dev
``` ```

25
include/AIconLabel.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <gtkmm/box.h>
#include <gtkmm/image.h>
#include "ALabel.hpp"
namespace waybar {
class AIconLabel : public ALabel {
public:
AIconLabel(const Json::Value &config, const std::string &name, const std::string &id,
const std::string &format, uint16_t interval = 0, bool ellipsize = false,
bool enable_click = false, bool enable_scroll = false);
virtual ~AIconLabel() = default;
virtual auto update() -> void;
protected:
Gtk::Image image_;
Gtk::Box box_;
bool iconEnabled() const;
};
} // namespace waybar

View File

@ -112,14 +112,14 @@ class Bar {
Gtk::Box center_; Gtk::Box center_;
Gtk::Box right_; Gtk::Box right_;
Gtk::Box box_; Gtk::Box box_;
std::vector<std::unique_ptr<waybar::AModule>> modules_left_; std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
std::vector<std::unique_ptr<waybar::AModule>> modules_center_; std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
std::vector<std::unique_ptr<waybar::AModule>> modules_right_; std::vector<std::shared_ptr<waybar::AModule>> modules_right_;
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
using BarIpcClient = modules::sway::BarIpcClient; using BarIpcClient = modules::sway::BarIpcClient;
std::unique_ptr<BarIpcClient> _ipc_client; std::unique_ptr<BarIpcClient> _ipc_client;
#endif #endif
std::vector<std::unique_ptr<waybar::AModule>> modules_all_; std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
}; };
} // namespace waybar } // namespace waybar

View File

@ -51,6 +51,9 @@
#ifdef HAVE_LIBSNDIO #ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp" #include "modules/sndio.hpp"
#endif #endif
#ifdef HAVE_GIO_UNIX
#include "modules/inhibitor.hpp"
#endif
#include "bar.hpp" #include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/temperature.hpp" #include "modules/temperature.hpp"

View File

@ -1,21 +1,14 @@
#pragma once #pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <date/tz.h> #include <date/tz.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar {
struct waybar_time { struct waybar_time;
std::locale locale;
date::zoned_seconds ztime; namespace modules {
};
const std::string kCalendarPlaceholder = "calendar"; const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
@ -46,4 +39,5 @@ class Clock : public ALabel {
auto timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string; auto timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string;
}; };
} // namespace waybar::modules } // namespace modules
} // namespace waybar

View File

@ -0,0 +1,27 @@
#pragma once
#include <memory>
#include <gio/gio.h>
#include "ALabel.hpp"
#include "bar.hpp"
namespace waybar::modules {
class Inhibitor : public ALabel {
public:
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~Inhibitor() override;
auto update() -> void;
auto activated() -> bool;
private:
auto handleToggle(::GdkEventButton* const& e) -> bool;
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
const std::string inhibitors_;
int handle_ = -1;
};
} // namespace waybar::modules

View File

@ -24,8 +24,6 @@ class KeyboardState : public AModule {
auto update() -> void; auto update() -> void;
private: private:
static auto openDevice(const std::string&) -> std::pair<int, libevdev*>;
Gtk::Box box_; Gtk::Box box_;
Gtk::Label numlock_label_; Gtk::Label numlock_label_;
Gtk::Label capslock_label_; Gtk::Label capslock_label_;

View File

@ -73,7 +73,8 @@ class Network : public ALabel {
int cidr_; int cidr_;
int32_t signal_strength_dbm_; int32_t signal_strength_dbm_;
uint8_t signal_strength_; uint8_t signal_strength_;
uint32_t frequency_; std::string signal_strength_app_;
float frequency_;
uint32_t route_priority; uint32_t route_priority;
util::SleeperThread thread_; util::SleeperThread thread_;

View File

@ -2,7 +2,8 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <tuple> #include <tuple>
#include "ALabel.hpp"
#include "AIconLabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
@ -10,7 +11,7 @@
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Window : public ALabel, public sigc::trackable { class Window : public AIconLabel, public sigc::trackable {
public: public:
Window(const std::string&, const waybar::Bar&, const Json::Value&); Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default; ~Window() = default;
@ -23,6 +24,7 @@ class Window : public ALabel, public sigc::trackable {
std::string& output); std::string& output);
void getTree(); void getTree();
std::string rewriteTitle(const std::string& title); std::string rewriteTitle(const std::string& title);
void updateAppIcon();
const Bar& bar_; const Bar& bar_;
std::string window_; std::string window_;

View File

@ -3,11 +3,13 @@
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "giomm/desktopappinfo.h"
#include "util/json.hpp" #include "util/json.hpp"
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map>
#include <unordered_set> #include <unordered_set>
#include <gdk/gdk.h> #include <gdk/gdk.h>
@ -61,15 +63,18 @@ class Task
Gtk::Image icon_; Gtk::Image icon_;
Gtk::Label text_before_; Gtk::Label text_before_;
Gtk::Label text_after_; Gtk::Label text_after_;
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
bool button_visible_ = false; bool button_visible_ = false;
bool ignored_ = false; bool ignored_ = false;
bool with_icon_; bool with_icon_ = false;
bool with_name_ = false;
std::string format_before_; std::string format_before_;
std::string format_after_; std::string format_after_;
std::string format_tooltip_; std::string format_tooltip_;
std::string name_;
std::string title_; std::string title_;
std::string app_id_; std::string app_id_;
uint32_t state_ = 0; uint32_t state_ = 0;
@ -77,6 +82,8 @@ class Task
private: private:
std::string repr() const; std::string repr() const;
std::string state_string(bool = false) const; std::string state_string(bool = false) const;
void set_app_info_from_app_id_list(const std::string& app_id_list);
bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
void hide_if_ignored(); void hide_if_ignored();
public: public:
@ -136,6 +143,7 @@ class Taskbar : public waybar::AModule
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_; std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
std::unordered_set<std::string> ignore_list_; std::unordered_set<std::string> ignore_list_;
std::map<std::string, std::string> app_ids_replace_map_;
struct zwlr_foreign_toplevel_manager_v1 *manager_; struct zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_; struct wl_seat *seat_;
@ -158,8 +166,9 @@ class Taskbar : public waybar::AModule
bool show_output(struct wl_output *) const; bool show_output(struct wl_output *) const;
bool all_outputs() const; bool all_outputs() const;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const; const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const;
const std::unordered_set<std::string>& ignore_list() const; const std::unordered_set<std::string>& ignore_list() const;
const std::map<std::string, std::string>& app_ids_replace_map() const;
}; };
} /* namespace waybar::modules::wlr */ } /* namespace waybar::modules::wlr */

View File

@ -68,7 +68,10 @@ inline int close(FILE* fp, pid_t pid) {
inline FILE* open(const std::string& cmd, int& pid) { inline FILE* open(const std::string& cmd, int& pid) {
if (cmd == "") return nullptr; if (cmd == "") return nullptr;
int fd[2]; int fd[2];
pipe(fd); if (pipe(fd) != 0){
spdlog::error("Unable to pipe fd");
return nullptr;
}
pid_t child_pid = fork(); pid_t child_pid = fork();

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <glibmm/ustring.h>
class pow_format { class pow_format {
public: public:
@ -84,5 +85,15 @@ namespace fmt {
); );
} }
}; };
// Glib ustirng support
template <>
struct formatter<Glib::ustring> : formatter<std::string> {
template <typename FormatContext>
auto format(const Glib::ustring& value, FormatContext& ctx) {
return formatter<std::string>::format(value, ctx);
}
};
} }

View File

@ -1,15 +1,17 @@
#pragma once
#include <string> #include <string>
const std::string WHITESPACE = " \n\r\t\f\v"; const std::string WHITESPACE = " \n\r\t\f\v";
std::string ltrim(const std::string s) { inline std::string ltrim(const std::string& s) {
size_t begin = s.find_first_not_of(WHITESPACE); size_t begin = s.find_first_not_of(WHITESPACE);
return (begin == std::string::npos) ? "" : s.substr(begin); return (begin == std::string::npos) ? "" : s.substr(begin);
} }
std::string rtrim(const std::string s) { inline std::string rtrim(const std::string& s) {
size_t end = s.find_last_not_of(WHITESPACE); size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1); return (end == std::string::npos) ? "" : s.substr(0, end + 1);
} }
std::string trim(const std::string& s) { return rtrim(ltrim(s)); } inline std::string trim(const std::string& s) { return rtrim(ltrim(s)); }

View File

@ -0,0 +1,39 @@
#pragma once
#include <date/tz.h>
#include <fmt/format.h>
namespace waybar {
struct waybar_time {
std::locale locale;
date::zoned_seconds ztime;
};
} // namespace waybar
template <>
struct fmt::formatter<waybar::waybar_time> {
std::string_view specs;
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it == ':') {
++it;
}
auto end = it;
while (end != ctx.end() && *end != '}') {
++end;
}
if (end != it) {
specs = {it, std::string_view::size_type(end - it)};
}
return end;
}
template <typename FormatContext>
auto format(const waybar::waybar_time& t, FormatContext& ctx) {
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
}
};

View File

@ -86,6 +86,11 @@ The *clock* module displays the current date and time.
typeof: double ++ typeof: double ++
Threshold to be used when scrolling. Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
View all valid format options in *strftime(3)*. View all valid format options in *strftime(3)*.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS

View File

@ -151,7 +151,8 @@ $text\\n$tooltip\\n$class*
"max-length": 40, "max-length": 40,
"interval": 30, // Remove this if your script is endless and write in loop "interval": 30, // Remove this if your script is endless and write in loop
"exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder "exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder
"exec-if": "pgrep spotify" "exec-if": "pgrep spotify",
"return-type": "json"
} }
``` ```

View File

@ -0,0 +1,92 @@
waybar-inhibitor(5)
# NAME
waybar - inhibitor module
# DESCRIPTION
The *inhibitor* module allows to take an inhibitor lock that logind provides.
See *systemd-inhibit*(1) for more information.
# CONFIGURATION
*what*: ++
typeof: string or array ++
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
*format*: ++
typeof: string ++
The format, how the state should be displayed.
*format-icons*: ++
typeof: array ++
Based on the current state, the corresponding icon gets selected.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module. A click also toggles the state
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# FORMAT REPLACEMENTS
*{status}*: status (*activated* or *deactivated*)
*{icon}*: Icon, as defined in *format-icons*
# EXAMPLES
```
"inhibitor": {
"what": "handle-lid-switch",
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
}
```

View File

@ -8,6 +8,8 @@ waybar - keyboard-state module
The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock. The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.
You must be a member of the input group to use this module.
# CONFIGURATION # CONFIGURATION
*interval*: ++ *interval*: ++

View File

@ -84,12 +84,20 @@ Addressed by *memory*
*{percentage}*: Percentage of memory in use. *{percentage}*: Percentage of memory in use.
*{swapPercentage}*: Percentage of swap in use.
*{total}*: Amount of total memory available in GiB. *{total}*: Amount of total memory available in GiB.
*{swapTotal}*: Amount of total swap available in GiB.
*{used}*: Amount of used memory in GiB. *{used}*: Amount of used memory in GiB.
*{swapUsed}*: Amount of used swap in GiB.
*{avail}*: Amount of available memory in GiB. *{avail}*: Amount of available memory in GiB.
*{swapAvail}*: Amount of available swap in GiB.
# EXAMPLES # EXAMPLES
``` ```

View File

@ -69,7 +69,7 @@ Addressed by *sway/mode*
# EXAMPLES # EXAMPLES
``` ```
"sway/window": { "sway/mode": {
"format": " {}", "format": " {}",
"max-length": 50 "max-length": 50
} }

View File

@ -70,6 +70,11 @@ Addressed by *sway/window*
typeof: object ++ typeof: object ++
Rules to rewrite window title. See *rewrite rules*. Rules to rewrite window title. See *rewrite rules*.
*icon*: ++
typeof: bool ++
default: true ++
Option to hide the application icon.
# REWRITE RULES # REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are *rewrite* is an object where keys are regular expressions and values are

View File

@ -69,10 +69,6 @@ Addressed by *sway/workspaces*
typeof: string ++ typeof: string ++
Command to execute when the module is updated. Command to execute when the module is updated.
*numeric-first*: ++
typeof: bool ++
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
*disable-auto-back-and-forth*: ++ *disable-auto-back-and-forth*: ++
typeof: bool ++ typeof: bool ++
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
"sway/workspaces": { "sway/workspaces": {
"disable-scroll": true, "disable-scroll": true,
"all-outputs": true, "all-outputs": true,
"numeric-first": false,
"format": "{name}: {icon}", "format": "{name}: {icon}",
"format-icons": { "format-icons": {
"1": "", "1": "",

View File

@ -29,6 +29,10 @@ Addressed by *tray*
typeof: integer ++ typeof: integer ++
Defines the spacing between the tray icons. Defines the spacing between the tray icons.
*reverse-direction*: ++
typeof: bool ++
Defines if new app icons should be added in a reverse order
*on-update*: ++ *on-update*: ++
typeof: string ++ typeof: string ++
Command to execute when the module is updated. Command to execute when the module is updated.

View File

@ -72,10 +72,16 @@ Addressed by *wlr/taskbar*
typeof: array ++ typeof: array ++
List of app_id/titles to be invisible. List of app_id/titles to be invisible.
*app_ids-mapping*: ++
typeof: object ++
Dictionary of app_id to be replaced with
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{icon}*: The icon of the application. *{icon}*: The icon of the application.
*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id}
*{title}*: The title of the application. *{title}*: The title of the application.
*{app_id}*: The app_id (== application name) of the application. *{app_id}*: The app_id (== application name) of the application.
@ -87,10 +93,15 @@ Addressed by *wlr/taskbar*
# CLICK ACTIONS # CLICK ACTIONS
*activate*: Bring the application into foreground. *activate*: Bring the application into foreground.
*minimize*: Toggle application's minimized state. *minimize*: Toggle application's minimized state.
*minimize-raise*: Bring the application into foreground or toggle its minimized state. *minimize-raise*: Bring the application into foreground or toggle its minimized state.
*maximize*: Toggle application's maximized state. *maximize*: Toggle application's maximized state.
*fullscreen*: Toggle application's fullscreen state. *fullscreen*: Toggle application's fullscreen state.
*close*: Close the application. *close*: Close the application.
# EXAMPLES # EXAMPLES
@ -105,7 +116,10 @@ Addressed by *wlr/taskbar*
"on-click-middle": "close", "on-click-middle": "close",
"ignore-list": [ "ignore-list": [
"Alacritty" "Alacritty"
] ],
"app_ids-mapping": {
"firefoxdeveloperedition": "firefox-developer-edition"
}
} }
``` ```

View File

@ -52,6 +52,7 @@ Addressed by *wlr/workspaces*
# CLICK ACTIONS # CLICK ACTIONS
*activate*: Switch to workspace. *activate*: Switch to workspace.
*close*: Close the workspace. *close*: Close the workspace.
# ICONS # ICONS

View File

@ -82,6 +82,12 @@ Also a minimal example configuration can be found on the at the bottom of this m
default: *true* ++ default: *true* ++
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar. Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
*fixed-center* ++
typeof: bool ++
default: *true*
Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space.
When false, the center block is centered in the space between the left and right block.
*passthrough* ++ *passthrough* ++
typeof: bool ++ typeof: bool ++
default: *false* ++ default: *false* ++

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.8', version: '0.9.10',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options : [ default_options : [
@ -79,14 +79,14 @@ is_netbsd = host_machine.system() == 'netbsd'
is_openbsd = host_machine.system() == 'openbsd' is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads') thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
jsoncpp = dependency('jsoncpp') jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0') sigcpp = dependency('sigc++-2.0')
libepoll = dependency('epoll-shim', required: false) libepoll = dependency('epoll-shim', required: false)
@ -142,6 +142,7 @@ src_files = files(
'src/factory.cpp', 'src/factory.cpp',
'src/AModule.cpp', 'src/AModule.cpp',
'src/ALabel.cpp', 'src/ALabel.cpp',
'src/AIconLabel.cpp',
'src/modules/custom.cpp', 'src/modules/custom.cpp',
'src/modules/disk.cpp', 'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp', 'src/modules/idle_inhibitor.cpp',
@ -242,6 +243,11 @@ if libsndio.found()
src_files += 'src/modules/sndio.cpp' src_files += 'src/modules/sndio.cpp'
endif endif
if (giounix.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
src_files += 'src/modules/inhibitor.cpp'
endif
if get_option('rfkill').enabled() if get_option('rfkill').enabled()
if is_linux if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp') add_project_arguments('-DWANT_RFKILL', language: 'cpp')
@ -290,7 +296,7 @@ executable(
gtk_layer_shell, gtk_layer_shell,
libsndio, libsndio,
tz_dep, tz_dep,
xkbregistry xkbregistry
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,
@ -347,6 +353,10 @@ if scdoc.found()
'waybar-sndio.5.scd', 'waybar-sndio.5.scd',
] ]
if (giounix.found() and not get_option('logind').disabled())
man_files += 'waybar-inhibitor.5.scd'
endif
foreach file : man_files foreach file : man_files
path = '@0@'.format(file) path = '@0@'.format(file)
basename = path.split('/')[-1] basename = path.split('/')[-1]

View File

@ -10,5 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') 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('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')

View File

@ -1,10 +1,7 @@
* { * {
border: none;
border-radius: 0;
/* `otf-font-awesome` is required to be installed for icons */ /* `otf-font-awesome` is required to be installed for icons */
font-family: Roboto, Helvetica, Arial, sans-serif; font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;
font-size: 13px; font-size: 13px;
min-height: 0;
} }
window#waybar { window#waybar {
@ -43,6 +40,9 @@ window#waybar.chromium {
color: #ffffff; color: #ffffff;
/* Use box-shadow instead of border so the text isn't offset */ /* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent; box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each workspace name */
border: none;
border-radius: 0;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */

28
src/AIconLabel.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "AIconLabel.hpp"
#include <gdkmm/pixbuf.h>
namespace waybar {
AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const std::string &id,
const std::string &format, uint16_t interval, bool ellipsize,
bool enable_click, bool enable_scroll)
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
event_box_.remove();
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
box_.set_spacing(8);
box_.add(image_);
box_.add(label_);
event_box_.add(box_);
}
auto AIconLabel::update() -> void {
image_.set_visible(image_.get_visible() && iconEnabled());
ALabel::update();
}
bool AIconLabel::iconEnabled() const {
return config_["icon"].isBool() ? config_["icon"].asBool() : true;
}
} // namespace waybar

View File

@ -6,7 +6,9 @@ namespace waybar {
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id, AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
bool enable_click, bool enable_scroll) bool enable_click, bool enable_scroll)
: name_(std::move(name)), config_(std::move(config)) { : name_(std::move(name)), config_(std::move(config))
, distance_scrolled_y_(0.0)
, distance_scrolled_x_(0.0) {
// configure events' user commands // configure events' user commands
if (config_["on-click"].isString() || config_["on-click-middle"].isString() || if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() || config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||

View File

@ -735,21 +735,22 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk
module = factory.makeModule(ref); module = factory.makeModule(ref);
} }
modules_all_.emplace_back(module); std::shared_ptr<AModule> module_sp(module);
modules_all_.emplace_back(module_sp);
if (group) { if (group) {
group->pack_start(*module, false, false); group->pack_start(*module, false, false);
} else { } else {
if (pos == "modules-left") { if (pos == "modules-left") {
modules_left_.emplace_back(module); modules_left_.emplace_back(module_sp);
} }
if (pos == "modules-center") { if (pos == "modules-center") {
modules_center_.emplace_back(module); modules_center_.emplace_back(module_sp);
} }
if (pos == "modules-right") { if (pos == "modules-right") {
modules_right_.emplace_back(module); modules_right_.emplace_back(module_sp);
} }
} }
module->dp.connect([module, &name] { module->dp.connect([module, name] {
try { try {
module->update(); module->update();
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -766,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk
auto waybar::Bar::setupWidgets() -> void { auto waybar::Bar::setupWidgets() -> void {
window.add(box_); window.add(box_);
box_.pack_start(left_, false, false); box_.pack_start(left_, false, false);
box_.set_center_widget(center_); if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
box_.set_center_widget(center_);
} else {
box_.pack_start(center_, true, false);
}
box_.pack_end(right_, false, false); box_.pack_end(right_, false, false);
// Convert to button code for every module that is used. // Convert to button code for every module that is used.

View File

@ -94,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sndio") { if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]); return new waybar::modules::Sndio(id, config_[name]);
} }
#endif
#ifdef HAVE_GIO_UNIX
if (ref == "inhibitor") {
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
}
#endif #endif
if (ref == "temperature") { if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]); return new waybar::modules::Temperature(id, config_[name]);

View File

@ -161,7 +161,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
uint32_t energy_now; uint32_t energy_now;
uint32_t energy_full_design; uint32_t energy_full_design;
std::string _status; std::string _status;
std::ifstream(bat / "status") >> _status; std::getline(std::ifstream(bat / "status"), _status);
// Some battery will report current and charge in μA/μAh. // Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh. // Scale these by the voltage to get μW/μWh.

View File

@ -1,17 +1,24 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
#include <time.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <ctime>
#include <sstream> #include <sstream>
#include <type_traits> #include <type_traits>
#include "util/ustring_clen.hpp" #include "util/ustring_clen.hpp"
#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#endif #endif
using waybar::modules::waybar_time; using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
@ -96,7 +103,7 @@ auto waybar::modules::Clock::update() -> void {
// As date dep is not fully compatible, prefer fmt // As date dep is not fully compatible, prefer fmt
tzset(); tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
text = fmt::format(format_, localtime); text = fmt::format(locale_, format_, localtime);
} else { } else {
text = fmt::format(format_, wtime); text = fmt::format(format_, wtime);
} }
@ -114,10 +121,10 @@ auto waybar::modules::Clock::update() -> void {
} }
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
} }
} }
label_.set_tooltip_markup(text);
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
@ -255,14 +262,3 @@ auto waybar::modules::Clock::first_day_of_week() -> date::weekday {
#endif #endif
return date::Sunday; return date::Sunday;
} }
template <>
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
template <typename FormatContext>
auto format(const waybar_time& t, FormatContext& ctx) {
#if FMT_VERSION >= 80000
auto& tm_format = specs;
#endif
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
}
};

View File

@ -62,7 +62,7 @@ auto waybar::modules::Cpu::update() -> void {
double waybar::modules::Cpu::getCpuLoad() { double waybar::modules::Cpu::getCpuLoad() {
double load[1]; double load[1];
if (getloadavg(load, 1) != -1) { if (getloadavg(load, 1) != -1) {
return load[0]; return std::ceil(load[0] * 100.0) / 100.0;
} }
throw std::runtime_error("Can't get Cpu load"); throw std::runtime_error("Can't get Cpu load");
} }

175
src/modules/inhibitor.cpp Normal file
View File

@ -0,0 +1,175 @@
#include "modules/inhibitor.hpp"
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <spdlog/spdlog.h>
namespace {
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
auto dbus() -> DBus {
GError *error = nullptr;
GDBusConnection* connection =
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
if (error) {
spdlog::error("g_bus_get_sync() failed: {}", error->message);
g_error_free(error);
connection = nullptr;
}
auto destructor = [](GDBusConnection* connection) {
GError *error = nullptr;
g_dbus_connection_close_sync(connection, nullptr, &error);
if (error) {
spdlog::error(
"g_bus_connection_close_sync failed(): {}",
error->message);
g_error_free(error);
}
};
return DBus{connection, destructor};
}
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
GError *error = nullptr;
GUnixFDList* fd_list;
int handle;
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"Inhibit",
g_variant_new(
"(ssss)",
inhibitors.c_str(),
"waybar",
"Asked by user",
"block"),
G_VARIANT_TYPE("(h)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&fd_list,
nullptr,
&error);
if (error) {
spdlog::error(
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
error->message);
g_error_free(error);
handle = -1;
} else {
gint index;
g_variant_get(reply, "(h)", &index);
g_variant_unref(reply);
handle = g_unix_fd_list_get(fd_list, index, nullptr);
g_object_unref(fd_list);
}
return handle;
}
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
static const auto inhibitors = std::array{
"idle",
"shutdown",
"sleep",
"handle-power-key",
"handle-suspend-key",
"handle-hibernate-key",
"handle-lid-switch"
};
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
== inhibitors.end()) {
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
}
return inhibitor;
}
auto getInhibitors(const Json::Value& config) -> std::string {
std::string inhibitors = "idle";
if (config["what"].empty()) {
return inhibitors;
}
if (config["what"].isString()) {
return checkInhibitor(config["what"].asString());
}
if (config["what"].isArray()) {
inhibitors = checkInhibitor(config["what"][0].asString());
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
}
return inhibitors;
}
return inhibitors;
}
}
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Inhibitor::handleToggle));
dp.emit();
}
Inhibitor::~Inhibitor() {
if (handle_ != -1) {
::close(handle_);
}
}
auto Inhibitor::activated() -> bool {
return handle_ != -1;
}
auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(
activated() ? "deactivated" : "activated");
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
}
return ALabel::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
if (e->button == 1) {
if (activated()) {
::close(handle_);
handle_ = -1;
} else {
handle_ = ::getLocks(dbus_, inhibitors_);
if (handle_ == -1) {
spdlog::error("cannot get inhibitor locks");
}
}
}
return ALabel::handleToggle(e);
}
} // waybar::modules

View File

@ -1,6 +1,8 @@
#include "modules/keyboard_state.hpp" #include "modules/keyboard_state.hpp"
#include <errno.h>
#include <filesystem> #include <filesystem>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <string.h>
extern "C" { extern "C" {
#include <sys/types.h> #include <sys/types.h>
@ -8,6 +10,69 @@ extern "C" {
#include <fcntl.h> #include <fcntl.h>
} }
class errno_error : public std::runtime_error {
public:
int code;
errno_error(int code, const std::string& msg)
: std::runtime_error(getErrorMsg(code, msg.c_str())),
code(code) {}
errno_error(int code, const char* msg)
: std::runtime_error(getErrorMsg(code, msg)),
code(code) {}
private:
static auto getErrorMsg(int err, const char* msg) -> std::string {
std::string error_msg{msg};
error_msg += ": ";
#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 32)
// strerrorname_np gets the error code's name; it's nice to have, but it's a recent GNU extension
const auto errno_name = strerrorname_np(err);
error_msg += errno_name;
error_msg += " ";
#endif
const auto errno_str = strerror(err);
error_msg += errno_str;
return error_msg;
}
};
auto openFile(const std::string& path, int flags) -> int {
int fd = open(path.c_str(), flags);
if (fd < 0) {
if (errno == EACCES) {
throw errno_error(errno, "Can't open " + path + " (are you in the input group?)");
} else {
throw errno_error(errno, "Can't open " + path);
}
}
return fd;
}
auto closeFile(int fd) -> void {
int res = close(fd);
if (res < 0) {
throw errno_error(errno, "Can't close file");
}
}
auto openDevice(int fd) -> libevdev* {
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw errno_error(-err, "Can't create libevdev device");
}
return dev;
}
auto supportsLockStates(const libevdev* dev) -> bool {
return libevdev_has_event_type(dev, EV_LED)
&& libevdev_has_event_code(dev, EV_LED, LED_NUML)
&& libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
&& libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config) waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()), : AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
@ -48,26 +113,36 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
if (config_["device-path"].isString()) { if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString(); std::string dev_path = config_["device-path"].asString();
std::tie(fd_, dev_) = openDevice(dev_path); fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
dev_ = openDevice(fd_);
} else { } else {
DIR* dev_dir = opendir("/dev/input"); DIR* dev_dir = opendir("/dev/input");
if (dev_dir == nullptr) { if (dev_dir == nullptr) {
throw std::runtime_error("Failed to open /dev/input"); throw errno_error(errno, "Failed to open /dev/input");
} }
dirent *ep; dirent *ep;
while ((ep = readdir(dev_dir))) { while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue; if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name; std::string dev_path = std::string("/dev/input/") + ep->d_name;
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
try { try {
std::tie(fd_, dev_) = openDevice(dev_path); auto dev = openDevice(fd);
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path); if (supportsLockStates(dev)) {
break; spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
} catch (const std::runtime_error& e) { fd_ = fd;
continue; dev_ = dev;
break;
}
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
} }
closeFile(fd);
} }
if (dev_ == nullptr) { if (dev_ == nullptr) {
throw std::runtime_error("Failed to find keyboard device"); throw errno_error(errno, "Failed to find keyboard device");
} }
} }
@ -79,35 +154,13 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
waybar::modules::KeyboardState::~KeyboardState() { waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_); libevdev_free(dev_);
int err = close(fd_); try {
if (err < 0) { closeFile(fd_);
// Not much we can do, so ignore it. } catch (const std::runtime_error& e) {
spdlog::warn(e.what());
} }
} }
auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair<int, libevdev*> {
int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
if (fd < 0) {
throw std::runtime_error("Can't open " + path);
}
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw std::runtime_error("Can't create libevdev device");
}
if (!libevdev_has_event_type(dev, EV_LED)) {
throw std::runtime_error("Device doesn't support LED events");
}
if (!libevdev_has_event_code(dev, EV_LED, LED_NUML)
|| !libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|| !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) {
throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events");
}
return std::make_pair(fd, dev);
}
auto waybar::modules::KeyboardState::update() -> void { auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS; int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) { while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
@ -117,8 +170,8 @@ auto waybar::modules::KeyboardState::update() -> void {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev); err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
} }
} }
if (err != -EAGAIN) { if (-err != EAGAIN) {
throw std::runtime_error("Failed to sync evdev device"); throw errno_error(-err, "Failed to sync evdev device");
} }
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML); int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);

View File

@ -12,7 +12,15 @@ auto waybar::modules::Memory::update() -> void {
parseMeminfo(); parseMeminfo();
unsigned long memtotal = meminfo_["MemTotal"]; unsigned long memtotal = meminfo_["MemTotal"];
unsigned long swaptotal = 0;
if (meminfo_.count("SwapTotal")) {
swaptotal = meminfo_["SwapTotal"];
}
unsigned long memfree; unsigned long memfree;
unsigned long swapfree = 0;
if (meminfo_.count("SwapFree")) {
swapfree = meminfo_["SwapFree"];
}
if (meminfo_.count("MemAvailable")) { if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field. // New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
@ -24,9 +32,16 @@ auto waybar::modules::Memory::update() -> void {
if (memtotal > 0 && memfree >= 0) { if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2); auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
int used_swap_percentage = 0;
if (swaptotal && swapfree) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
}
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2); auto available_ram_gigabytes = memfree / std::pow(1024, 2);
auto available_swap_gigabytes = swapfree / std::pow(1024, 2);
auto format = format_; auto format = format_;
auto state = getState(used_ram_percentage); auto state = getState(used_ram_percentage);
@ -43,9 +58,13 @@ auto waybar::modules::Memory::update() -> void {
used_ram_percentage, used_ram_percentage,
fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage),
fmt::arg("used", used_ram_gigabytes), fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes))); fmt::arg("swapUsed", used_swap_gigabytes),
fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} }
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -54,9 +73,13 @@ auto waybar::modules::Memory::update() -> void {
label_.set_tooltip_text(fmt::format(tooltip_format, label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage, used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage),
fmt::arg("used", used_ram_gigabytes), fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes))); fmt::arg("swapUsed", used_swap_gigabytes),
fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else { } else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
} }

View File

@ -129,7 +129,7 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM); album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE); title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE); date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get()); song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get()); volume = mpd_status_get_volume(status_.get());
if (volume < 0) { if (volume < 0) {
volume = 0; volume = 0;

View File

@ -78,7 +78,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1), efd_(-1),
ev_fd_(-1), ev_fd_(-1),
want_route_dump_(false), want_route_dump_(true),
want_link_dump_(false), want_link_dump_(false),
want_addr_dump_(false), want_addr_dump_(false),
dump_in_progress_(false), dump_in_progress_(false),
@ -88,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN}, rfkill_{RFKILL_TYPE_WLAN},
#endif #endif
frequency_(0) { frequency_(0.0) {
// Start with some "text" in the module's label_, update() will then // Start with some "text" in the module's label_, update() will then
// update it. Since the text should be different, update() will be able // update it. Since the text should be different, update() will be able
@ -106,7 +106,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
} }
if (!config_["interface"].isString()) { if (!config_["interface"].isString()) {
// "interface" isn't configure, then try to guess the external // "interface" isn't configured, then try to guess the external
// interface currently used for internet. // interface currently used for internet.
want_route_dump_ = true; want_route_dump_ = true;
} else { } else {
@ -331,12 +331,13 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("essid", essid_), fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_),
fmt::arg("ifname", ifname_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_), fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
@ -360,12 +361,13 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("essid", essid_), fmt::arg("essid", essid_),
fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_),
fmt::arg("ifname", ifname_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_), fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
@ -403,7 +405,8 @@ void waybar::modules::Network::clearIface() {
cidr_ = 0; cidr_ = 0;
signal_strength_dbm_ = 0; signal_strength_dbm_ = 0;
signal_strength_ = 0; signal_strength_ = 0;
frequency_ = 0; signal_strength_app_.clear();
frequency_ = 0.0;
} }
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
@ -470,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->essid_.clear(); net->essid_.clear();
net->signal_strength_dbm_ = 0; net->signal_strength_dbm_ = 0;
net->signal_strength_ = 0; net->signal_strength_ = 0;
net->frequency_ = 0; net->signal_strength_app_.clear();
net->frequency_ = 0.0;
} }
} }
net->carrier_ = carrier.value(); net->carrier_ = carrier.value();
@ -788,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
// signalstrength in dBm from mBm // signalstrength in dBm from mBm
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
// WiFi-hardware usually operates in the range -90 to -30dBm.
// WiFi-hardware usually operates in the range -90 to -20dBm. // If a signal is too strong, it can overwhelm receiving circuity that is designed
const int hardwareMax = -20; // to pick up and process a certain signal level. The following percentage is scaled to
// punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).
const int hardwareOptimum = -45;
const int hardwareMin = -90; const int hardwareMin = -90;
const int strength = const int strength =
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100; 100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100);
signal_strength_ = std::clamp(strength, 0, 100); signal_strength_ = std::clamp(strength, 0, 100);
if (signal_strength_dbm_ >= -50) {
signal_strength_app_ = "Great Connectivity";
} else if (signal_strength_dbm_ >= -60) {
signal_strength_app_ = "Good Connectivity";
} else if (signal_strength_dbm_ >= -67) {
signal_strength_app_ = "Streaming";
} else if (signal_strength_dbm_ >= -70) {
signal_strength_app_ = "Web Surfing";
} else if (signal_strength_dbm_ >= -80) {
signal_strength_app_ = "Basic Connectivity";
} else {
signal_strength_app_ = "Poor Connectivity";
}
} }
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) { if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]); signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
@ -803,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
void waybar::modules::Network::parseFreq(struct nlattr **bss) { void waybar::modules::Network::parseFreq(struct nlattr **bss) {
if (bss[NL80211_BSS_FREQUENCY] != nullptr) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
// in MHz // in GHz
frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000;
} }
} }

View File

@ -54,7 +54,9 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
c, c,
static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) | static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) | static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE)), static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
nullptr, nullptr,
nullptr); nullptr);
break; break;
@ -79,6 +81,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (dir == SCROLL_DIR::NONE) { if (dir == SCROLL_DIR::NONE) {
return true; return true;
} }
if (config_["reverse-scrolling"].asInt() == 1){
if (dir == SCROLL_DIR::UP) {
dir = SCROLL_DIR::DOWN;
} else if (dir == SCROLL_DIR::DOWN) {
dir = SCROLL_DIR::UP;
}
}
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick; pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_; pa_cvolume pa_volume = pa_volume_;
@ -114,8 +123,12 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context * conte
pa_context_get_server_info(context, serverInfoCb, data); pa_context_get_server_info(context, serverInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
pa_context_get_sink_info_list(context, sinkInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
pa_context_get_source_info_list(context, sourceInfoCb, data);
} }
} }

View File

@ -8,13 +8,7 @@
#include <fstream> #include <fstream>
#include <map> #include <map>
template <> #include "util/format.hpp"
struct fmt::formatter<Glib::ustring> : formatter<std::string> {
template <typename FormatContext>
auto format(const Glib::ustring& value, FormatContext& ctx) {
return formatter<std::string>::format(value, ctx);
}
};
template <> template <>
struct fmt::formatter<Glib::VariantBase> : formatter<std::string> { struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {

View File

@ -25,7 +25,11 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
} }
void Tray::onAdd(std::unique_ptr<Item>& item) { void Tray::onAdd(std::unique_ptr<Item>& item) {
box_.pack_start(item->event_box); if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
box_.pack_end(item->event_box);
} else {
box_.pack_start(item->event_box);
}
dp.emit(); dp.emit();
} }

View File

@ -154,7 +154,10 @@ auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) ->
std::map<std::string, int> short_name_to_number_map; std::map<std::string, int> short_name_to_number_map;
for (const auto& used_layout_name : used_layouts) { for (const auto& used_layout_name : used_layouts) {
auto used_layout = &layouts_map_.find(used_layout_name)->second; auto found = layouts_map_.find(used_layout_name);
if (found == layouts_map_.end())
continue;
auto used_layout = &found->second;
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name]; auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
if (layouts_with_same_name_list.size() < 2) { if (layouts_with_same_name_list.size() < 2) {
continue; continue;

View File

@ -1,11 +1,20 @@
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#include <gdkmm/pixbuf.h>
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <gtkmm/enums.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <filesystem>
#include <regex> #include <regex>
#include <string>
namespace waybar::modules::sway { namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
ipc_.subscribe(R"(["window","workspace"])"); ipc_.subscribe(R"(["window","workspace"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent)); ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
@ -29,12 +38,60 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : ""; auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output); std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output);
updateAppIcon();
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
} }
} }
std::optional<std::string> getDesktopFilePath(const std::string& app_id) {
const auto data_dirs = Glib::get_system_data_dirs();
for (const auto& data_dir : data_dirs) {
const auto desktop_file_path = data_dir + "applications/" + app_id + ".desktop";
if (std::filesystem::exists(desktop_file_path)) {
return desktop_file_path;
}
}
return {};
}
std::optional<Glib::ustring> getIconName(const std::string& app_id) {
const auto desktop_file_path = getDesktopFilePath(app_id);
if (!desktop_file_path.has_value()) {
return {};
}
try {
Glib::KeyFile desktop_file;
desktop_file.load_from_file(desktop_file_path.value());
const auto icon_name = desktop_file.get_string("Desktop Entry", "Icon");
if (icon_name.empty()) {
return {};
}
return icon_name;
} catch (Glib::FileError& error) {
spdlog::warn(
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
} catch (Glib::KeyFileError& error) {
spdlog::warn(
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
}
return {};
}
void Window::updateAppIcon() {
if (!iconEnabled()) {
return;
}
const auto icon_name = getIconName(app_id_);
if (icon_name.has_value()) {
image_.set_from_icon_name(icon_name.value(), Gtk::ICON_SIZE_LARGE_TOOLBAR);
image_.set_visible(true);
return;
}
image_.set_visible(false);
}
auto Window::update() -> void { auto Window::update() -> void {
if (!old_app_id_.empty()) { if (!old_app_id_.empty()) {
bar_.window.get_style_context()->remove_class(old_app_id_); bar_.window.get_style_context()->remove_class(old_app_id_);
@ -63,7 +120,7 @@ auto Window::update() -> void {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
} }
// Call parent update // Call parent update
ALabel::update(); AIconLabel::update();
} }
int leafNodesInWorkspace(const Json::Value& node) { int leafNodesInWorkspace(const Json::Value& node) {

View File

@ -2,6 +2,7 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <cctype> #include <cctype>
#include <string> #include <string>
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v; Json::Value v;
v["name"] = p_w_name; v["name"] = p_w_name;
v["target_output"] = bar_.output->name; v["target_output"] = bar_.output->name;
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v)); workspaces_.emplace_back(std::move(v));
break; break;
} }
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v; Json::Value v;
v["name"] = p_w_name; v["name"] = p_w_name;
v["target_output"] = ""; v["target_output"] = "";
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v)); workspaces_.emplace_back(std::move(v));
} }
} }
} }
// config option to sort numeric workspace names before others // sway has a defined ordering of workspaces that should be preserved in
bool config_numeric_first = config_["numeric-first"].asBool(); // the representation displayed by waybar to ensure that commands such
// as "workspace prev" or "workspace next" make sense when looking at
// the workspace representation in the bar.
// Due to waybar's own feature of persistent workspaces unknown to sway,
// custom sorting logic is necessary to make these workspaces appear
// naturally in the list of workspaces without messing up sway's
// sorting. For this purpose, a custom numbering property is created
// that preserves the order provided by sway while inserting numbered
// persistent workspaces at their natural positions.
//
// All of this code assumes that sway provides numbered workspaces first
// and other workspaces are sorted by their creation time.
//
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
int max_num = -1;
for (auto & workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
}
for (auto & workspace : workspaces_) {
auto workspace_num = workspace["num"].asInt();
if (workspace_num > -1) {
workspace["sort"] = workspace_num;
} else {
workspace["sort"] = ++max_num;
}
}
std::sort(workspaces_.begin(), std::sort(workspaces_.begin(),
workspaces_.end(), workspaces_.end(),
[config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) { [](const Json::Value &lhs, const Json::Value &rhs) {
// the "num" property (integer type):
// The workspace number or -1 for workspaces that do
// not start with a number.
// We could rely on sway providing this property:
//
// auto l = lhs["num"].asInt();
// auto r = rhs["num"].asInt();
//
// We cannot rely on the "num" property as provided by sway
// via IPC, because persistent workspace might not exist in
// sway's view. However, we need this property also for
// not-yet created persistent workspace. As such, we simply
// duplicate sway's logic of assigning the "num" property
// into waybar (see convertWorkspaceNameToNum). This way the
// sorting should work out even when we include workspaces
// that do not currently exist.
auto lname = lhs["name"].asString(); auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString(); auto rname = rhs["name"].asString();
int l = convertWorkspaceNameToNum(lname); int l = lhs["sort"].asInt();
int r = convertWorkspaceNameToNum(rname); int r = rhs["sort"].asInt();
if (l == r) { if (l == r) {
// in case both integers are the same, lexicographical // In case both integers are the same, lexicographical
// sort. This also covers the case when both don't have a // sort. The code above already ensure that this will only
// number (i.e., l == r == -1). // happend in case of explicitly numbered workspaces.
return lname < rname; return lname < rname;
} }
// one of the workspaces doesn't begin with a number, so
// num is -1.
if (l < 0 || r < 0) {
if (config_numeric_first) {
return r < 0;
}
return l < 0;
}
// both workspaces have a "num" so let's just compare those
return l < r; return l < r;
}); });
} }
dp.emit(); dp.emit();
} catch (const std::exception &e) { } catch (const std::exception &e) {

View File

@ -4,6 +4,7 @@
#include "glibmm/fileutils.h" #include "glibmm/fileutils.h"
#include "glibmm/refptr.h" #include "glibmm/refptr.h"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/string.hpp"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@ -11,7 +12,9 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <utility>
#include <fmt/core.h>
#include <gdkmm/monitor.h> #include <gdkmm/monitor.h>
#include <gtkmm/icontheme.h> #include <gtkmm/icontheme.h>
@ -24,27 +27,6 @@
namespace waybar::modules::wlr { namespace waybar::modules::wlr {
/* String manipulation methods */
const std::string WHITESPACE = " \n\r\t\f\v";
static std::string ltrim(const std::string& s)
{
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
static std::string rtrim(const std::string& s)
{
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
static std::string trim(const std::string& s)
{
return rtrim(ltrim(s));
}
/* Icon loading functions */ /* Icon loading functions */
static std::vector<std::string> search_prefix() static std::vector<std::string> search_prefix()
{ {
@ -86,8 +68,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
} }
} }
/* Method 1 - get the correct icon name from the desktop file */ static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
static std::string get_from_desktop_app_info(const std::string &app_id)
{ {
static std::vector<std::string> prefixes = search_prefix(); static std::vector<std::string> prefixes = search_prefix();
@ -103,33 +84,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
".desktop" ".desktop"
}; };
Glib::RefPtr<Gio::DesktopAppInfo> app_info; for (auto& prefix : prefixes) {
for (auto& folder : app_folders) {
for (auto& suffix : suffixes) {
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (!app_info_) {
continue;
}
for (auto& prefix : prefixes) return app_info_;
for (auto& folder : app_folders) }
for (auto& suffix : suffixes) }
if (!app_info) }
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (app_info && app_info->get_icon()) return {};
return app_info->get_icon()->to_string();
return "";
} }
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
{ {
if (icon_theme->lookup_icon(app_id, 24)) auto app_info = get_app_info_by_name(app_id);
return app_id; if (app_info) {
return app_info;
}
return "";
}
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
static std::string get_from_desktop_app_info_search(const std::string &app_id)
{
std::string desktop_file = ""; std::string desktop_file = "";
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
@ -151,65 +128,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
} }
g_free(desktop_list); g_free(desktop_list);
return get_from_desktop_app_info(desktop_file); return get_app_info_by_name(desktop_file);
} }
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
const std::string &app_id_list, int size)
{
std::string app_id; std::string app_id;
std::istringstream stream(app_id_list); std::istringstream stream(app_id_list);
bool found = false;
/* Wayfire sends a list of app-id's in space separated format, other compositors /* Wayfire sends a list of app-id's in space separated format, other compositors
* send a single app-id, but in any case this works fine */ * send a single app-id, but in any case this works fine */
while (stream >> app_id) while (stream >> app_id)
{ {
app_info_ = get_desktop_app_info(app_id);
if (app_info_) {
return;
}
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c){ return std::tolower(c); });
app_info_ = get_desktop_app_info(lower_app_id);
if (app_info_) {
return;
}
size_t start = 0, end = app_id.size(); size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end); start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start+1, app_id.size()); std::string app_name = app_id.substr(start+1, app_id.size());
app_info_ = get_desktop_app_info(app_name);
if (app_info_) {
return;
}
auto lower_app_id = app_id; start = app_id.find("-");
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), app_name = app_id.substr(0, start);
[](char c){ return std::tolower(c); }); app_info_ = get_desktop_app_info(app_name);
}
}
std::string icon_name = get_from_icon_theme(icon_theme, app_id); static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
if (icon_name.empty()) return "";
icon_name = get_from_icon_theme(icon_theme, lower_app_id); }
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(lower_app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info_search(app_id);
if (icon_name.empty()) bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
icon_name = "unknown"; {
std::string ret_icon_name = "unknown";
if (app_info) {
std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
if (!icon_name.empty()) {
ret_icon_name = icon_name;
} else {
if (app_info->get_icon()) {
ret_icon_name = app_info->get_icon()->to_string();
}
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf; Glib::RefPtr<Gdk::Pixbuf> pixbuf;
try { try {
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch(...) { } catch(...) {
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
pixbuf = load_icon_from_file(icon_name, size); pixbuf = load_icon_from_file(ret_icon_name, size);
else else
pixbuf = {}; pixbuf = {};
} }
if (pixbuf) { if (pixbuf) {
image.set(pixbuf); image.set(pixbuf);
found = true; return true;
break; }
}
}
return found; return false;
} }
/* Task class implementation */ /* Task class implementation */
@ -289,13 +285,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
content_.show(); content_.show();
button_.add(content_); button_.add(content_);
with_icon_ = false;
format_before_.clear(); format_before_.clear();
format_after_.clear(); format_after_.clear();
if (config_["format"].isString()) { if (config_["format"].isString()) {
/* The user defined a format string, use it */ /* The user defined a format string, use it */
auto format = config_["format"].asString(); auto format = config_["format"].asString();
if (format.find("{name}") != std::string::npos) {
with_name_ = true;
}
auto icon_pos = format.find("{icon}"); auto icon_pos = format.find("{icon}");
if (icon_pos == 0) { if (icon_pos == 0) {
@ -402,13 +400,28 @@ void Task::handle_app_id(const char *app_id)
app_id_ = app_id; app_id_ = app_id;
hide_if_ignored(); hide_if_ignored();
if (!with_icon_) auto ids_replace_map = tbar_->app_ids_replace_map();
if (ids_replace_map.count(app_id_)) {
auto replaced_id = ids_replace_map[app_id_];
spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
app_id_ = replaced_id;
}
if (!with_icon_ && !with_name_) {
return; return;
}
set_app_info_from_app_id_list(app_id_);
name_ = app_info_ ? app_info_->get_display_name() : app_id;
if (!with_icon_) {
return;
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false; bool found = false;
for (auto& icon_theme : tbar_->icon_themes()) { for (auto& icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true; found = true;
break; break;
} }
@ -564,14 +577,17 @@ void Task::update()
{ {
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_; std::string title = title_;
std::string name = name_;
std::string app_id = app_id_; std::string app_id = app_id_;
if (markup) { if (markup) {
title = Glib::Markup::escape_text(title); title = Glib::Markup::escape_text(title);
name = Glib::Markup::escape_text(name);
app_id = Glib::Markup::escape_text(app_id); app_id = Glib::Markup::escape_text(app_id);
} }
if (!format_before_.empty()) { if (!format_before_.empty()) {
auto txt = fmt::format(format_before_, auto txt = fmt::format(format_before_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -585,6 +601,7 @@ void Task::update()
if (!format_after_.empty()) { if (!format_after_.empty()) {
auto txt = fmt::format(format_after_, auto txt = fmt::format(format_after_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -599,6 +616,7 @@ void Task::update()
if (!format_tooltip_.empty()) { if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_, auto txt = fmt::format(format_tooltip_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -726,6 +744,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
} }
} }
// Load app_id remappings
if (config_["app_ids-mapping"].isObject()) {
const Json::Value& mapping = config_["app_ids-mapping"];
const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
for (auto& app_id : app_ids) {
app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default()); icon_themes_.push_back(Gtk::IconTheme::get_default());
} }
@ -857,10 +884,10 @@ 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<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
{
return icon_themes_; const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
}
const std::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; } const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
} /* namespace waybar::modules::wlr */ } /* namespace waybar::modules::wlr */

View File

@ -1,4 +1,3 @@
#define CATCH_CONFIG_RUNNER
#include "util/SafeSignal.hpp" #include "util/SafeSignal.hpp"
#include <glibmm.h> #include <glibmm.h>
@ -138,8 +137,3 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr
producer.join(); producer.join();
REQUIRE(count == NUM_EVENTS); REQUIRE(count == NUM_EVENTS);
} }
int main(int argc, char* argv[]) {
Glib::init();
return Catch::Session().run(argc, argv);
}

View File

@ -1,4 +1,3 @@
#define CATCH_CONFIG_MAIN
#include "config.hpp" #include "config.hpp"
#include <catch2/catch.hpp> #include <catch2/catch.hpp>

26
test/main.cpp Normal file
View File

@ -0,0 +1,26 @@
#define CATCH_CONFIG_RUNNER
#include <glibmm.h>
#include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/spdlog.h>
#include <catch2/catch.hpp>
#include <catch2/catch_reporter_tap.hpp>
#include <memory>
int main(int argc, char* argv[]) {
Catch::Session session;
Glib::init();
session.applyCommandLine(argc, argv);
const auto logger = spdlog::default_logger();
const auto& reporter_name = session.config().getReporterName();
if (reporter_name == "tap") {
spdlog::set_pattern("# [%l] %v");
} else if (reporter_name == "compact") {
logger->sinks().clear();
} else {
logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});
}
return session.run();
}

View File

@ -6,30 +6,27 @@ test_dep = [
jsoncpp, jsoncpp,
spdlog, spdlog,
] ]
test_src = files(
config_test = executable( 'main.cpp',
'config_test', 'SafeSignal.cpp',
'config.cpp', 'config.cpp',
'../src/config.cpp', '../src/config.cpp',
dependencies: test_dep,
include_directories: test_inc,
) )
safesignal_test = executable( if tz_dep.found()
'safesignal_test', test_dep += tz_dep
'SafeSignal.cpp', test_src += files('waybar_time.cpp')
endif
waybar_test = executable(
'waybar_test',
test_src,
dependencies: test_dep, dependencies: test_dep,
include_directories: test_inc, include_directories: test_inc,
) )
test( test(
'Configuration test', 'waybar',
config_test, waybar_test,
workdir: meson.source_root(),
)
test(
'SafeSignal test',
safesignal_test,
workdir: meson.source_root(), workdir: meson.source_root(),
) )

90
test/waybar_time.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "util/waybar_time.hpp"
#include <date/date.h>
#include <date/tz.h>
#include <catch2/catch.hpp>
#include <chrono>
#include <stdexcept>
using namespace std::literals::chrono_literals;
/*
* Check that the date/time formatter with locale and timezone support is working as expected.
*/
const date::zoned_time<std::chrono::seconds> TEST_TIME = date::make_zoned(
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s);
TEST_CASE("Format UTC time", "[clock][util]") {
waybar::waybar_time tm{std::locale("C"), TEST_TIME};
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
tm.locale = std::locale("en_US");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
SECTION("GB locale") {
try {
tm.locale = std::locale("en_GB");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 13:04:05");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
}
TEST_CASE("Format zoned time", "[clock][util]") {
waybar::waybar_time tm{std::locale("C"), date::make_zoned("America/New_York", TEST_TIME)};
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
tm.locale = std::locale("en_US");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
SECTION("GB locale") {
try {
tm.locale = std::locale("en_GB");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 08:04:05");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
}