Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/add-name-to-taskbar

This commit is contained in:
dmitry 2021-11-23 23:10:49 +03:00
commit 166504256a
64 changed files with 2104 additions and 360 deletions

View File

@ -4,20 +4,24 @@ on: [ push, pull_request ]
jobs: jobs:
clang: clang:
runs-on: macos-latest # until https://github.com/actions/runner/issues/385 # Run actions in a FreeBSD vm on the macos-10.15 runner
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
runs-on: macos-10.15
steps: 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.4 # aka FreeBSD 12.2 uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0
with: with:
usesh: true usesh: true
prepare: | prepare: |
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
pkg install -y git # subprojects/date pkg install -y git # subprojects/date
pkg install -y evdev-proto gtk-layer-shell gtkmm30 jsoncpp libdbusmenu \ pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
libevdev libfmt libmpdclient libudev-devd meson pkgconf pulseaudio \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
scdoc sndio spdlog pkgconf pulseaudio scdoc sndio spdlog
run: | run: |
meson build -Dman-pages=enabled meson build -Dman-pages=enabled
ninja -C build ninja -C build
meson test -C build --no-rebuild --print-errorlogs --suite waybar

View File

@ -23,3 +23,5 @@ jobs:
run: meson -Dman-pages=enabled build run: meson -Dman-pages=enabled build
- name: build - name: build
run: ninja -C build run: ninja -C build
- name: test
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar

View File

@ -16,5 +16,8 @@ install: build
run: build run: build
./build/waybar ./build/waybar
debug-run: build-debug
./build/waybar --log-level debug
clean: clean:
rm -rf build rm -rf build

View File

@ -3,11 +3,10 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp" #include "bar.hpp"
#include "config.hpp"
struct zwlr_layer_shell_v1; struct zwlr_layer_shell_v1;
struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibitor_v1;
@ -29,18 +28,13 @@ class Client {
struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars; std::vector<std::unique_ptr<Bar>> bars;
Config config;
private: private:
Client() = default; Client() = default;
std::tuple<const std::string, const std::string> getConfigs(const std::string &config, const std::string getStyle(const std::string &style);
const std::string &style) const;
void bindInterfaces(); void bindInterfaces();
const std::string getValidPath(const std::vector<std::string> &paths) const;
void handleOutput(struct waybar_output &output); void handleOutput(struct waybar_output &output);
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
auto setupConfig(const std::string &config_file, int depth) -> void;
auto resolveConfigIncludes(Json::Value &config, int depth) -> void;
auto mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void;
auto setupCss(const std::string &css_file) -> void; auto setupCss(const std::string &css_file) -> void;
struct waybar_output & getOutput(void *); struct waybar_output & getOutput(void *);
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output); std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
@ -55,7 +49,6 @@ class Client {
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor); void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor); void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_; Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_; Glib::RefPtr<Gtk::CssProvider> css_provider_;
std::list<struct waybar_output> outputs_; std::list<struct waybar_output> outputs_;

39
include/config.hpp Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <json/json.h>
#include <optional>
#include <string>
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
namespace waybar {
class Config {
public:
static const std::vector<std::string> CONFIG_DIRS;
/* Try to find any of provided names in the supported set of config directories */
static std::optional<std::string> findConfigPath(
const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS);
Config() = default;
void load(const std::string &config);
Json::Value &getConfig() { return config_; }
std::vector<Json::Value> getOutputConfigs(const std::string &name, const std::string &identifier);
private:
void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
void resolveConfigIncludes(Json::Value &config, int depth);
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
std::string config_file_;
Json::Value config_;
};
} // namespace waybar

View File

@ -14,6 +14,7 @@
#endif #endif
#ifdef HAVE_WLR #ifdef HAVE_WLR
#include "modules/wlr/taskbar.hpp" #include "modules/wlr/taskbar.hpp"
#include "modules/wlr/workspace_manager.hpp"
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
#include "modules/river/tags.hpp" #include "modules/river/tags.hpp"

View File

@ -37,6 +37,7 @@ class Clock : public ALabel {
auto calendar_text(const waybar_time& wtime) -> std::string; auto calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday; auto first_day_of_week() -> date::weekday;
bool setTimeZone(Json::Value zone_name);
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -20,7 +20,7 @@ class Cpu : public ALabel {
private: private:
double getCpuLoad(); double getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage(); std::tuple<std::vector<uint16_t>, std::string> getCpuUsage();
std::tuple<float, float, float> getCpuFrequency(); std::tuple<float, float, float> getCpuFrequency();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo(); std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<float> parseCpuFrequencies(); std::vector<float> parseCpuFrequencies();

View File

@ -43,6 +43,7 @@ class Network : public ALabel {
const std::string getNetworkState() const; const std::string getNetworkState() const;
void clearIface(); void clearIface();
bool wildcardMatch(const std::string& pattern, const std::string& text) const; bool wildcardMatch(const std::string& pattern, const std::string& text) const;
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
int ifid_; int ifid_;
sa_family_t family_; sa_family_t family_;
@ -67,6 +68,7 @@ class Network : public ALabel {
bool carrier_; bool carrier_;
std::string ifname_; std::string ifname_;
std::string ipaddr_; std::string ipaddr_;
std::string gwaddr_;
std::string netmask_; std::string netmask_;
int cidr_; int cidr_;
int32_t signal_strength_dbm_; int32_t signal_strength_dbm_;

View File

@ -6,6 +6,7 @@
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h" #include "river-status-unstable-v1-client-protocol.h"
#include "river-control-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river { namespace waybar::modules::river {
@ -20,7 +21,12 @@ class Tags : public waybar::AModule {
void handle_view_tags(struct wl_array *tags); void handle_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags); void handle_urgent_tags(uint32_t tags);
void handle_primary_clicked(uint32_t tag);
bool handle_button_press(GdkEventButton *event_button, uint32_t tag);
struct zriver_status_manager_v1 *status_manager_; struct zriver_status_manager_v1 *status_manager_;
struct zriver_control_v1 *control_;
struct wl_seat *seat_;
private: private:
const waybar::Bar & bar_; const waybar::Bar & bar_;

View File

@ -84,6 +84,7 @@ class Task
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); 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); 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();
public: public:
/* Getter functions */ /* Getter functions */

View File

@ -0,0 +1,160 @@
#pragma once
#include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <functional>
#include <map>
#include <memory>
#include <vector>
#include "AModule.hpp"
#include "bar.hpp"
#include "ext-workspace-unstable-v1-client-protocol.h"
namespace waybar::modules::wlr {
class WorkspaceManager;
class WorkspaceGroup;
class Workspace {
public:
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id);
~Workspace();
auto update() -> void;
auto id() const -> uint32_t { return id_; }
auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); }
auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); }
auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); }
// wlr stuff
auto handle_name(const std::string &name) -> void;
auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void;
auto handle_state(const std::vector<uint32_t> &state) -> void;
auto handle_remove() -> void;
auto handle_done() -> void;
auto handle_clicked(GdkEventButton *bt) -> bool;
auto show() -> void;
auto hide() -> void;
auto get_button_ref() -> Gtk::Button & { return button_; }
auto get_name() -> std::string & { return name_; }
auto get_coords() -> std::vector<uint32_t> & { return coordinates_; }
enum class State {
ACTIVE = (1 << 0),
URGENT = (1 << 1),
HIDDEN = (1 << 2),
};
private:
auto get_icon() -> std::string;
const Bar &bar_;
const Json::Value &config_;
WorkspaceGroup &workspace_group_;
// wlr stuff
zext_workspace_handle_v1 *workspace_handle_;
uint32_t state_ = 0;
uint32_t id_;
std::string name_;
std::vector<uint32_t> coordinates_;
static std::map<std::string, std::string> icons_map_;
std::string format_;
bool with_icon_ = false;
Gtk::Button button_;
Gtk::Box content_;
Gtk::Label label_;
};
class WorkspaceGroup {
public:
WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle,
uint32_t id);
~WorkspaceGroup();
auto update() -> void;
auto id() const -> uint32_t { return id_; }
auto is_visible() const -> bool;
auto remove_workspace(uint32_t id_) -> void;
auto active_only() const -> bool;
auto creation_delayed() const -> bool;
auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; }
auto sort_workspaces() -> void;
auto set_need_to_sort() -> void { need_to_sort = true; }
auto add_button(Gtk::Button &button) -> void;
auto remove_button(Gtk::Button &button) -> void;
// wlr stuff
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
auto handle_remove() -> void;
auto handle_output_enter(wl_output *output) -> void;
auto handle_output_leave() -> void;
auto handle_done() -> void;
auto commit() -> void;
private:
static uint32_t workspace_global_id;
const waybar::Bar &bar_;
Gtk::Box &box_;
const Json::Value &config_;
WorkspaceManager &workspace_manager_;
// wlr stuff
zext_workspace_group_handle_v1 *workspace_group_handle_;
wl_output *output_ = nullptr;
uint32_t id_;
std::vector<std::unique_ptr<Workspace>> workspaces_;
bool need_to_sort = false;
};
class WorkspaceManager : public AModule {
public:
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
~WorkspaceManager() override;
auto update() -> void override;
auto all_outputs() const -> bool { return all_outputs_; }
auto active_only() const -> bool { return active_only_; }
auto workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>;
auto creation_delayed() const -> bool { return creation_delayed_; }
auto sort_workspaces() -> void;
auto remove_workspace_group(uint32_t id_) -> void;
// wlr stuff
auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void;
auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle)
-> void;
auto handle_done() -> void;
auto handle_finished() -> void;
auto commit() -> void;
private:
const waybar::Bar &bar_;
Gtk::Box box_;
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
// wlr stuff
zext_workspace_manager_v1 *workspace_manager_ = nullptr;
static uint32_t group_global_id;
bool sort_by_name_ = true;
bool sort_by_coordinates_ = true;
bool all_outputs_ = false;
bool active_only_ = false;
bool creation_delayed_ = false;
};
} // namespace waybar::modules::wlr

View File

@ -0,0 +1,8 @@
#include "ext-workspace-unstable-v1-client-protocol.h"
namespace waybar::modules::wlr {
void add_registry_listener(void *data);
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data);
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data);
zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data);
}

View File

@ -120,7 +120,7 @@ The two arguments are:
# CUSTOM FORMATS # CUSTOM FORMATS
The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected. The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected.
*format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery.

View File

@ -24,7 +24,8 @@ The *clock* module displays the current date and time.
*timezone*: ++ *timezone*: ++
typeof: string ++ typeof: string ++
default: inferred local timezone ++ default: inferred local timezone ++
The timezone to display the time in, e.g. America/New_York. The timezone to display the time in, e.g. America/New_York. ++
This field will be ignored if *timezones* field is set and have at least one value.
*timezones*: ++ *timezones*: ++
typeof: list of strings ++ typeof: list of strings ++

View File

@ -82,7 +82,9 @@ The *cpu* module displays the current cpu utilization.
*{load}*: Current cpu load. *{load}*: Current cpu load.
*{usage}*: Current cpu usage. *{usage}*: Current overall cpu usage.
*{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
*{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz. *{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz.
@ -90,7 +92,13 @@ The *cpu* module displays the current cpu utilization.
*{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz. *{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz.
# EXAMPLE *{icon}*: Icon for overall cpu usage.
*{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}.
# EXAMPLES
Basic configuration:
``` ```
"cpu": { "cpu": {
@ -100,6 +108,16 @@ The *cpu* module displays the current cpu utilization.
} }
``` ```
Cpu usage per core rendered as icons:
```
"cpu": {
"interval": 1,
"format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ",
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
},
```
# STYLE # STYLE
- *#cpu* - *#cpu*

View File

@ -131,6 +131,8 @@ Addressed by *network*
*{ipaddr}*: The first IP of the interface. *{ipaddr}*: The first IP of the interface.
*{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP. *{netmask}*: The subnetmask corresponding to the IP.
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. *{cidr}*: The subnetmask corresponding to the IP in CIDR notation.

View File

@ -21,6 +21,11 @@ Addressed by *river/tags*
typeof: array ++ typeof: array ++
The label to display for each tag. The label to display for each tag.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled.
# EXAMPLE # EXAMPLE
``` ```

View File

@ -70,7 +70,7 @@ Addressed by *wlr/taskbar*
*ignore-list*: ++ *ignore-list*: ++
typeof: array ++ typeof: array ++
List of app_id to be invisible. List of app_id/titles to be invisible.
*app_ids-mapping*: ++ *app_ids-mapping*: ++
typeof: object ++ typeof: object ++

View File

@ -0,0 +1,87 @@
waybar-wlr-workspaces(5)
# NAME
waybar - wlr workspaces module
# DESCRIPTION
The *workspaces* module displays the currently used workspaces in wayland compositor.
# CONFIGURATION
Addressed by *wlr/workspaces*
*format*: ++
typeof: string ++
default: {name} ++
The format, how information should be displayed.
*format-icons*: ++
typeof: array ++
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
*sort-by-name*: ++
typeof: bool ++
default: true ++
Should workspaces be sorted by name.
*sort-by-coordinates*: ++
typeof: bool ++
default: true ++
Should workspaces be sorted by coordinates.
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first.
If both are false - sort by id will be performed.
*all-outputs*: ++
typeof: bool ++
default: false ++
If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown.
*active-only*: ++
typeof: bool ++
default: false ++
If set to true only active or urgent workspaces will be shown.
# FORMAT REPLACEMENTS
*{name}*: Name of workspace assigned by compositor
*{icon}*: Icon, as defined in *format-icons*.
# CLICK ACTIONS
*activate*: Switch to workspace.
*close*: Close the workspace.
# ICONS
Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string match is found.
- *focused*: Will be shown, when workspace is focused
# EXAMPLES
```
"wlr/workspaces": {
"format": "{name}: {icon}",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"focused": "",
"default": ""
}
}
```
# Style
- *#workspaces*
- *#workspaces button*
- *#workspaces button.active*
- *#workspaces button.urgent*
- *#workspaces button.hidden*

View File

@ -64,6 +64,10 @@ Also a minimal example configuration can be found on the at the bottom of this m
typeof: integer ++ typeof: integer ++
Margins value without units. Margins value without units.
*spacing* ++
typeof: integer ++
Size of gaps in between of the different modules.
*name* ++ *name* ++
typeof: string ++ typeof: string ++
Optional name added as a CSS class, for styling multiple waybars. Optional name added as a CSS class, for styling multiple waybars.
@ -87,8 +91,9 @@ Also a minimal example configuration can be found on the at the bottom of this m
*include* ++ *include* ++
typeof: string|array ++ typeof: string|array ++
Paths to additional configuration files. In case of duplicate options, the including file's value takes precedence. Make sure to avoid circular imports. Paths to additional configuration files.
For a multi-bar config, specify at least an empty object for each bar also in every file being included. Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports.
For a multi-bar config, the include directive affects only current bar configuration object.
# MODULE FORMAT # MODULE FORMAT
@ -219,5 +224,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)* - *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)* - *waybar-temperature(5)*
- *waybar-tray(5)* - *waybar-tray(5)*

View File

@ -149,6 +149,7 @@ src_files = files(
'src/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp', 'src/client.cpp',
'src/config.cpp',
'src/util/ustring_clen.cpp' 'src/util/ustring_clen.cpp'
) )
@ -185,6 +186,8 @@ src_files += [
if true if true
add_project_arguments('-DHAVE_WLR', language: 'cpp') add_project_arguments('-DHAVE_WLR', language: 'cpp')
src_files += 'src/modules/wlr/taskbar.cpp' src_files += 'src/modules/wlr/taskbar.cpp'
src_files += 'src/modules/wlr/workspace_manager.cpp'
src_files += 'src/modules/wlr/workspace_manager_binding.cpp'
endif endif
if true if true
@ -333,6 +336,7 @@ if scdoc.found()
'waybar-tray.5.scd', 'waybar-tray.5.scd',
'waybar-states.5.scd', 'waybar-states.5.scd',
'waybar-wlr-taskbar.5.scd', 'waybar-wlr-taskbar.5.scd',
'waybar-wlr-workspaces.5.scd',
'waybar-bluetooth.5.scd', 'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd', 'waybar-sndio.5.scd',
] ]
@ -359,6 +363,15 @@ if scdoc.found()
endforeach endforeach
endif endif
catch2 = dependency(
'catch2',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)
if catch2.found()
subdir('test')
endif
clangtidy = find_program('clang-tidy', required: false) clangtidy = find_program('clang-tidy', required: false)
if clangtidy.found() if clangtidy.found()

View File

@ -10,3 +10,4 @@ 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('tests', type: 'feature', value: 'auto', description: 'Enable tests')

View File

@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_workspace_unstable_v1">
<copyright>
Copyright © 2019 Christopher Billington
Copyright © 2020 Ilia Bozhinov
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zext_workspace_manager_v1" version="1">
<description summary="list and control workspaces">
Workspaces, also called virtual desktops, are groups of surfaces. A
compositor with a concept of workspaces may only show some such groups of
surfaces (those of 'active' workspaces) at a time. 'Activating' a
workspace is a request for the compositor to display that workspace's
surfaces as normal, whereas the compositor may hide or otherwise
de-emphasise surfaces that are associated only with 'inactive' workspaces.
Workspaces are grouped by which sets of outputs they correspond to, and
may contain surfaces only from those outputs. In this way, it is possible
for each output to have its own set of workspaces, or for all outputs (or
any other arbitrary grouping) to share workspaces. Compositors may
optionally conceptually arrange each group of workspaces in an
N-dimensional grid.
The purpose of this protocol is to enable the creation of taskbars and
docks by providing them with a list of workspaces and their properties,
and allowing them to activate and deactivate workspaces.
After a client binds the zext_workspace_manager_v1, each workspace will be
sent via the workspace event.
</description>
<event name="workspace_group">
<description summary="a workspace group has been created">
This event is emitted whenever a new workspace group has been created.
All initial details of the workspace group (workspaces, outputs) will be
sent immediately after this event via the corresponding events in
zext_workspace_group_handle_v1.
</description>
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
</event>
<request name="commit">
<description summary="all requests about the workspaces have been sent">
The client must send this request after it has finished sending other
requests. The compositor must process a series of requests preceding a
commit request atomically.
This allows changes to the workspace properties to be seen as atomic,
even if they happen via multiple events, and even if they involve
multiple zext_workspace_handle_v1 objects, for example, deactivating one
workspace and activating another.
</description>
</request>
<event name="done">
<description summary="all information about the workspace groups has been sent">
This event is sent after all changes in all workspace groups have been
sent.
This allows changes to one or more zext_workspace_group_handle_v1
properties to be seen as atomic, even if they happen via multiple
events. In particular, an output moving from one workspace group to
another sends an output_enter event and an output_leave event to the two
zext_workspace_group_handle_v1 objects in question. The compositor sends
the done event only after updating the output information in both
workspace groups.
</description>
</event>
<event name="finished">
<description summary="the compositor has finished with the workspace_manager">
This event indicates that the compositor is done sending events to the
zext_workspace_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</description>
</event>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for new
workspace groups. However the compositor may emit further workspace
events, until the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
</interface>
<interface name="zext_workspace_group_handle_v1" version="1">
<description summary="a workspace group assigned to a set of outputs">
A zext_workspace_group_handle_v1 object represents a a workspace group
that is assigned a set of outputs and contains a number of workspaces.
The set of outputs assigned to the workspace group is conveyed to the client via
output_enter and output_leave events, and its workspaces are conveyed with
workspace events.
For example, a compositor which has a set of workspaces for each output may
advertise a workspace group (and its workspaces) per output, whereas a compositor
where a workspace spans all outputs may advertise a single workspace group for all
outputs.
</description>
<event name="output_enter">
<description summary="output assigned to workspace group">
This event is emitted whenever an output is assigned to the workspace
group.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="output_leave">
<description summary="output removed from workspace group">
This event is emitted whenever an output is removed from the workspace
group.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="workspace">
<description summary="workspace added to workspace group">
This event is emitted whenever a new workspace has been created.
All initial details of the workspace (name, coordinates, state) will
be sent immediately after this event via the corresponding events in
zext_workspace_handle_v1.
</description>
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
</event>
<event name="remove">
<description summary="this workspace group has been destroyed">
This event means the zext_workspace_group_handle_v1 has been destroyed.
It is guaranteed there won't be any more events for this
zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
inert so any requests will be ignored except the destroy request.
The compositor must remove all workspaces belonging to a workspace group
before removing the workspace group.
</description>
</event>
<request name="create_workspace">
<description summary="create a new workspace">
Request that the compositor create a new workspace with the given name.
There is no guarantee that the compositor will create a new workspace,
or that the created workspace will have the provided name.
</description>
<arg name="workspace" type="string"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the zext_workspace_handle_v1 object">
Destroys the zext_workspace_handle_v1 object.
This request should be called either when the client does not want to
use the workspace object any more or after the remove event to finalize
the destruction of the object.
</description>
</request>
</interface>
<interface name="zext_workspace_handle_v1" version="1">
<description summary="a workspace handing a group of surfaces">
A zext_workspace_handle_v1 object represents a a workspace that handles a
group of surfaces.
Each workspace has a name, conveyed to the client with the name event; a
list of states, conveyed to the client with the state event; and
optionally a set of coordinates, conveyed to the client with the
coordinates event. The client may request that the compositor activate or
deactivate the workspace.
Each workspace can belong to only a single workspace group.
Depepending on the compositor policy, there might be workspaces with
the same name in different workspace groups, but these workspaces are still
separate (e.g. one of them might be active while the other is not).
</description>
<event name="name">
<description summary="workspace name changed">
This event is emitted immediately after the zext_workspace_handle_v1 is
created and whenever the name of the workspace changes.
</description>
<arg name="name" type="string"/>
</event>
<event name="coordinates">
<description summary="workspace coordinates changed">
This event is used to organize workspaces into an N-dimensional grid
within a workspace group, and if supported, is emitted immediately after
the zext_workspace_handle_v1 is created and whenever the coordinates of
the workspace change. Compositors may not send this event if they do not
conceptually arrange workspaces in this way. If compositors simply
number workspaces, without any geometric interpretation, they may send
1D coordinates, which clients should not interpret as implying any
geometry. Sending an empty array means that the compositor no longer
orders the workspace geometrically.
Coordinates have an arbitrary number of dimensions N with an uint32
position along each dimension. By convention if N > 1, the first
dimension is X, the second Y, the third Z, and so on. The compositor may
chose to utilize these events for a more novel workspace layout
convention, however. No guarantee is made about the grid being filled or
bounded; there may be a workspace at coordinate 1 and another at
coordinate 1000 and none in between. Within a workspace group, however,
workspaces must have unique coordinates of equal dimensionality.
</description>
<arg name="coordinates" type="array"/>
</event>
<event name="state">
<description summary="the state of the workspace changed">
This event is emitted immediately after the zext_workspace_handle_v1 is
created and each time the workspace state changes, either because of a
compositor action or because of a request in this protocol.
</description>
<arg name="state" type="array"/>
</event>
<enum name="state">
<description summary="types of states on the workspace">
The different states that a workspace can have.
</description>
<entry name="active" value="0" summary="the workspace is active"/>
<entry name="urgent" value="1" summary="the workspace requests attention"/>
<entry name="hidden" value="2">
<description summary="the workspace is not visible">
The workspace is not visible in its workspace group, and clients
attempting to visualize the compositor workspace state should not
display such workspaces.
</description>
</entry>
</enum>
<event name="remove">
<description summary="this workspace has been destroyed">
This event means the zext_workspace_handle_v1 has been destroyed. It is
guaranteed there won't be any more events for this
zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
any requests will be ignored except the destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zext_workspace_handle_v1 object">
Destroys the zext_workspace_handle_v1 object.
This request should be called either when the client does not want to
use the workspace object any more or after the remove event to finalize
the destruction of the object.
</description>
</request>
<request name="activate">
<description summary="activate the workspace">
Request that this workspace be activated.
There is no guarantee the workspace will be actually activated, and
behaviour may be compositor-dependent. For example, activating a
workspace may or may not deactivate all other workspaces in the same
group.
</description>
</request>
<request name="deactivate">
<description summary="activate the workspace">
Request that this workspace be deactivated.
There is no guarantee the workspace will be actually deactivated.
</description>
</request>
<request name="remove">
<description summary="remove the workspace">
Request that this workspace be removed.
There is no guarantee the workspace will be actually removed.
</description>
</request>
</interface>
</protocol>

View File

@ -27,7 +27,9 @@ client_protocols = [
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'],
['wlr-foreign-toplevel-management-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'],
['ext-workspace-unstable-v1.xml'],
['river-status-unstable-v1.xml'], ['river-status-unstable-v1.xml'],
['river-control-unstable-v1.xml'],
] ]
client_protos_src = [] client_protos_src = []

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_control_unstable_v1">
<copyright>
Copyright 2020 The River Developers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_control_v1" version="1">
<description summary="run compositor commands">
This interface allows clients to run compositor commands and receive a
success/failure response with output or a failure message respectively.
Each command is built up in a series of add_argument requests and
executed with a run_command request. The first argument is the command
to be run.
A complete list of commands should be made available in the man page of
the compositor.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_control object">
This request indicates that the client will not use the
river_control object any more. Objects that have been created
through this instance are not affected.
</description>
</request>
<request name="add_argument">
<description summary="add an argument to the current command">
Arguments are stored by the server in the order they were sent until
the run_command request is made.
</description>
<arg name="argument" type="string" summary="the argument to add"/>
</request>
<request name="run_command">
<description summary="run the current command">
Execute the command built up using the add_argument request for the
given seat.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
summary="callback object"/>
</request>
</interface>
<interface name="zriver_command_callback_v1" version="1">
<description summary="callback object">
This object is created by the run_command request. Exactly one of the
success or failure events will be sent. This object will be destroyed
by the compositor after one of the events is sent.
</description>
<event name="success">
<description summary="command successful">
Sent when the command has been successfully received and executed by
the compositor. Some commands may produce output, in which case the
output argument will be a non-empty string.
</description>
<arg name="output" type="string" summary="the output of the command"/>
</event>
<event name="failure">
<description summary="command failed">
Sent when the command could not be carried out. This could be due to
sending a non-existent command, no command, not enough arguments, too
many arguments, invalid arguments, etc.
</description>
<arg name="failure_message" type="string"
summary="a message explaining why failure occurred"/>
</event>
</interface>
</protocol>

View File

@ -3,6 +3,7 @@
// "position": "bottom", // Waybar position (top|bottom|left|right) // "position": "bottom", // Waybar position (top|bottom|left|right)
"height": 30, // Waybar height (to be removed for auto height) "height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
@ -117,7 +118,8 @@
"network": { "network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface // "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ", "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ", "format-ethernet": "{ipaddr}/{cidr} ",
"tooltip-format": "{ifname} via {gwaddr} ",
"format-linked": "{ifname} (No IP) ", "format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ⚠", "format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}" "format-alt": "{ifname}: {ipaddr}/{cidr}"

View File

@ -79,7 +79,7 @@ def signal_handler(sig, frame):
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# Increase verbosity with every occurence of -v # Increase verbosity with every occurrence of -v
parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('-v', '--verbose', action='count', default=0)
# Define for which player we're listening # Define for which player we're listening

View File

@ -80,7 +80,6 @@ window#waybar.chromium {
#idle_inhibitor, #idle_inhibitor,
#mpd { #mpd {
padding: 0 10px; padding: 0 10px;
margin: 0 4px;
color: #ffffff; color: #ffffff;
} }

View File

@ -67,9 +67,11 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
} }
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
}
if (format_icons.isString()) { if (format_icons.isString()) {
return format_icons.asString(); return format_icons.asString();
} }
@ -90,9 +92,11 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>&
} }
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx]; format_icons = format_icons[idx];
} }
}
if (format_icons.isString()) { if (format_icons.isString()) {
return format_icons.asString(); return format_icons.asString();
} }

View File

@ -13,10 +13,10 @@
namespace waybar { namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG = static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules"; "Requested height: {} is less than the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG = static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules"; "Requested width: {} is less than the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
@ -438,6 +438,13 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
center_.get_style_context()->add_class("modules-center"); center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right"); right_.get_style_context()->add_class("modules-right");
if (config["spacing"].isInt()) {
int spacing = config["spacing"].asInt();
left_.set_spacing(spacing);
center_.set_spacing(spacing);
right_.set_spacing(spacing);
}
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;

View File

@ -3,12 +3,10 @@
#include <fmt/ostream.h> #include <fmt/ostream.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <fstream>
#include <iostream> #include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() { waybar::Client *waybar::Client::inst() {
@ -16,23 +14,6 @@ waybar::Client *waybar::Client::inst() {
return c; return c;
} }
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
wordexp_t p;
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
}
return std::string();
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data); auto client = static_cast<Client *>(data);
@ -70,29 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);
} }
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != output.name &&
config_output.substr(1) != output.identifier;
}
return config_output == output.name || config_output == output.identifier;
}
}
return true;
}
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
auto it = std::find_if( auto it = std::find_if(
outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; });
@ -103,17 +61,7 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
} }
std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) { std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) {
std::vector<Json::Value> configs; return config.getOutputConfigs(output.name, output.identifier);
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, output)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, output)) {
configs.push_back(config_);
}
return configs;
} }
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
@ -203,94 +151,14 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
} }
std::tuple<const std::string, const std::string> waybar::Client::getConfigs( const std::string waybar::Client::getStyle(const std::string &style) {
const std::string &config, const std::string &style) const { auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
auto config_file = config.empty() ? getValidPath({ if (!css_file) {
"$XDG_CONFIG_HOME/waybar/config", throw std::runtime_error("Missing required resource files");
"$XDG_CONFIG_HOME/waybar/config.jsonc",
"$HOME/.config/waybar/config",
"$HOME/.config/waybar/config.jsonc",
"$HOME/waybar/config",
"$HOME/waybar/config.jsonc",
"/etc/xdg/waybar/config",
"/etc/xdg/waybar/config.jsonc",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
: config;
auto css_file = style.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
: style;
if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files");
}
spdlog::info("Resources files: {}, {}", config_file, css_file);
return {config_file, css_file};
}
auto waybar::Client::setupConfig(const std::string &config_file, int depth) -> void {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
Json::Value tmp_config_ = parser.parse(str);
if (tmp_config_.isArray()) {
for (auto &config_part : tmp_config_) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config_, depth);
}
mergeConfig(config_, tmp_config_);
}
auto waybar::Client::resolveConfigIncludes(Json::Value &config, int depth) -> void {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(getValidPath({include.asString()}), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(getValidPath({includes.asString()}), ++depth);
}
}
auto waybar::Client::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
if (a_config_[key].isObject() && b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else {
a_config_[key] = b_config_[key];
}
}
} else if (a_config_.isArray() && b_config_.isArray()) {
// This can happen only on the top-level array of a multi-bar config
for (Json::Value::ArrayIndex i = 0; i < b_config_.size(); i++) {
if (a_config_[i].isObject() && b_config_[i].isObject()) {
mergeConfig(a_config_[i], b_config_[i]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
} }
spdlog::info("Using CSS file {}", css_file.value());
return css_file.value();
};
auto waybar::Client::setupCss(const std::string &css_file) -> void { auto waybar::Client::setupCss(const std::string &css_file) -> void {
css_provider_ = Gtk::CssProvider::create(); css_provider_ = Gtk::CssProvider::create();
@ -329,14 +197,14 @@ void waybar::Client::bindInterfaces() {
int waybar::Client::main(int argc, char *argv[]) { int waybar::Client::main(int argc, char *argv[]) {
bool show_help = false; bool show_help = false;
bool show_version = false; bool show_version = false;
std::string config; std::string config_opt;
std::string style; std::string style_opt;
std::string bar_id; std::string bar_id;
std::string log_level; std::string log_level;
auto cli = clara::detail::Help(show_help) | auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") | clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") | clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt( clara::detail::Opt(
log_level, log_level,
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") | "trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
@ -367,8 +235,8 @@ int waybar::Client::main(int argc, char *argv[]) {
throw std::runtime_error("Bar need to run under Wayland"); throw std::runtime_error("Bar need to run under Wayland");
} }
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style); config.load(config_opt);
setupConfig(config_file, 0); auto css_file = getStyle(style_opt);
setupCss(css_file); setupCss(css_file);
bindInterfaces(); bindInterfaces();
gtk_app->hold(); gtk_app->hold();

153
src/config.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <wordexp.h>
#include <fstream>
#include <stdexcept>
#include "util/json.hpp"
namespace waybar {
const std::vector<std::string> Config::CONFIG_DIRS = {
"$XDG_CONFIG_HOME/waybar/",
"$HOME/.config/waybar/",
"$HOME/waybar/",
"/etc/xdg/waybar/",
SYSCONFDIR "/xdg/waybar/",
"./resources/",
};
std::optional<std::string> tryExpandPath(const std::string &path) {
wordexp_t p;
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
return std::nullopt;
}
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) {
std::vector<std::string> paths;
for (const auto &dir : dirs) {
for (const auto &name : names) {
if (auto res = tryExpandPath(dir + name); res) {
return res;
}
}
}
return std::nullopt;
}
void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
Json::Value tmp_config = parser.parse(str);
if (tmp_config.isArray()) {
for (auto &config_part : tmp_config) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config, depth);
}
mergeConfig(dst, tmp_config);
}
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth);
}
}
void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
// [] creates key with default value. Use `get` to avoid that.
if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&
b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else if (!a_config_.isMember(key)) {
// do not allow overriding value set by top or previously included config
a_config_[key] = b_config_[key];
} else {
spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
bool isValidOutput(const Json::Value &config, const std::string &name,
const std::string &identifier) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == name || output_conf.asString() == identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != name && config_output.substr(1) != identifier;
}
return config_output == name || config_output == identifier;
}
}
return true;
}
void Config::load(const std::string &config) {
auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config;
if (!file) {
throw std::runtime_error("Missing required resource files");
}
config_file_ = file.value();
spdlog::info("Using configuration file {}", config_file_);
setupConfig(config_, config_file_, 0);
}
std::vector<Json::Value> Config::getOutputConfigs(const std::string &name,
const std::string &identifier) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, name, identifier)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, name, identifier)) {
configs.push_back(config_);
}
return configs;
}
} // namespace waybar

View File

@ -30,6 +30,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "wlr/taskbar") { if (ref == "wlr/taskbar") {
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
} }
if (ref == "wlr/workspaces") {
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
}
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
if (ref == "river/tags") { if (ref == "river/tags") {

View File

@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void {
return; return;
} }
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format( label_.set_markup(fmt::format(
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
getState(percent); getState(percent);

View File

@ -15,10 +15,14 @@ using waybar::modules::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), fixed_time_zone_(false) { : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
if (config_["timezone"].isString()) { if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018."); time_zone_idx_ = 0;
time_zone_ = date::locate_zone(config_["timezone"].asString()); setTimeZone(config_["timezones"][time_zone_idx_]);
fixed_time_zone_ = true; } else {
setTimeZone(config_["timezone"]);
}
if (fixed_time_zone_) {
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
} }
if (config_["locale"].isString()) { if (config_["locale"].isString()) {
@ -72,6 +76,17 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update(); ALabel::update();
} }
bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) {
if (!zone_name.isString() || zone_name.asString().empty()) {
fixed_time_zone_ = false;
return false;
}
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
return true;
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
// defer to user commands if set // defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
@ -92,14 +107,7 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
} else { } else {
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
} }
auto zone_name = config_["timezones"][time_zone_idx_]; setTimeZone(config_["timezones"][time_zone_idx_]);
if (!zone_name.isString() || zone_name.empty()) {
fixed_time_zone_ = false;
} else {
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
}
update(); update();
return true; return true;
} }

View File

@ -1,5 +1,14 @@
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) { : ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] { thread_ = [this] {
@ -17,7 +26,8 @@ auto waybar::modules::Cpu::update() -> void {
label_.set_tooltip_text(tooltip); label_.set_tooltip_text(tooltip);
} }
auto format = format_; auto format = format_;
auto state = getState(cpu_usage); auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage);
if (!state.empty() && config_["format-" + state].isString()) { if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString(); format = config_["format-" + state].asString();
} }
@ -27,13 +37,22 @@ auto waybar::modules::Cpu::update() -> void {
} else { } else {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{state}; auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(format, fmt::dynamic_format_arg_store<fmt::format_context> store;
fmt::arg("load", cpu_load), store.push_back(fmt::arg("load", cpu_load));
fmt::arg("usage", cpu_usage), store.push_back(fmt::arg("load", cpu_load));
fmt::arg("icon", getIcon(cpu_usage, icons)), store.push_back(fmt::arg("usage", total_usage));
fmt::arg("max_frequency", max_frequency), store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
fmt::arg("min_frequency", min_frequency), store.push_back(fmt::arg("max_frequency", max_frequency));
fmt::arg("avg_frequency", avg_frequency))); store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency));
for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i);
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_.set_markup(fmt::vformat(format, store));
} }
// Call parent update // Call parent update
@ -48,14 +67,14 @@ double waybar::modules::Cpu::getCpuLoad() {
throw std::runtime_error("Can't get Cpu load"); throw std::runtime_error("Can't get Cpu load");
} }
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() { std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) { if (prev_times_.empty()) {
prev_times_ = parseCpuinfo(); prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo(); std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip; std::string tooltip;
uint16_t usage = 0; std::vector<uint16_t> usage;
for (size_t i = 0; i < curr_times.size(); ++i) { for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i]; auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i]; auto [prev_idle, prev_total] = prev_times_[i];
@ -63,11 +82,11 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) { if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
} else { } else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
} }
usage.push_back(tmp);
} }
prev_times_ = curr_times; prev_times_ = curr_times;
return {usage, tooltip}; return {usage, tooltip};

View File

@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void {
} }
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
auto format = format_; auto format = format_;
auto state = getState(percentage_used); auto state = getState(percentage_used);

View File

@ -15,11 +15,11 @@ auto waybar::modules::Memory::update() -> void {
unsigned long memfree; unsigned long memfree;
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"]; memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
} else { } else {
// Old kernel; give a best-effort approximation of available memory. // Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"]; meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"];
} }
if (memtotal > 0 && memfree >= 0) { if (memtotal > 0 && memfree >= 0) {

View File

@ -1,5 +1,26 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
static unsigned zfsArcSize() {
std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"};
if (zfs_arc_stats.is_open()) {
std::string name;
std::string type;
unsigned long data{0};
std::string line;
while (std::getline(zfs_arc_stats, line)) {
std::stringstream(line) >> name >> type >> data;
if (name == "size") {
return data / 1024; // convert to kB
}
}
}
return 0;
}
void waybar::modules::Memory::parseMeminfo() { void waybar::modules::Memory::parseMeminfo() {
const std::string data_dir_ = "/proc/meminfo"; const std::string data_dir_ = "/proc/meminfo";
std::ifstream info(data_dir_); std::ifstream info(data_dir_);
@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() {
int64_t value = std::stol(line.substr(posDelim + 1)); int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value; meminfo_[name] = value;
} }
meminfo_["zfs_size"] = zfsArcSize();
} }

View File

@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() {
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());
volume = mpd_status_get_volume(status_.get()); volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
volume = 0;
}
queue_length = mpd_status_get_queue_length(status_.get()); queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));

View File

@ -1,87 +1,77 @@
#include "modules/network.hpp" #include <linux/if.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <linux/if.h>
#include <fstream>
#include <cassert> #include <cassert>
#include <fstream>
#include <sstream>
#include <optional> #include <optional>
#include "modules/network.hpp"
#include "util/format.hpp" #include "util/format.hpp"
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif #endif
namespace { namespace {
using namespace waybar::util; using namespace waybar::util;
constexpr const char *NETSTAT_FILE =
"/proc/net/netstat"; // std::ifstream does not take std::string_view as param
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
constexpr const char *DEFAULT_FORMAT = "{ifname}"; constexpr const char *DEFAULT_FORMAT = "{ifname}";
std::ifstream netstat(NETSTAT_FILE);
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
if (!netstat) {
spdlog::warn("Failed to open netstat file {}", NETSTAT_FILE);
return {};
}
netstat.seekg(std::ios_base::beg);
// finding corresponding line (category)
// looks into the file for the first line starting by the 'category' string
auto starts_with = [](const std::string &str, std::string_view start) {
return start == std::string_view{str.data(), std::min(str.size(), start.size())};
};
std::string read;
while (std::getline(netstat, read) && !starts_with(read, category))
;
if (!starts_with(read, category)) {
spdlog::warn("Category '{}' not found in netstat file {}", category, NETSTAT_FILE);
return {};
}
// finding corresponding column (key)
// looks into the fetched line for the first word (space separated) equal to 'key'
int index = 0;
auto r_it = read.begin();
auto k_it = key.begin();
while (k_it != key.end() && r_it != read.end()) {
if (*r_it != *k_it) {
r_it = std::find(r_it, read.end(), ' ');
if (r_it != read.end()) {
++r_it;
}
k_it = key.begin();
++index;
} else {
++r_it;
++k_it;
}
}
if (r_it == read.end() && k_it != key.end()) {
spdlog::warn(
"Key '{}' not found in category '{}' of netstat file {}", key, category, NETSTAT_FILE);
return {};
}
// finally accessing value
// accesses the line right under the fetched one
std::getline(netstat, read);
assert(starts_with(read, category));
std::istringstream iss(read);
while (index--) {
std::getline(iss, read, ' ');
}
unsigned long long value;
iss >> value;
return value;
}
} // namespace } // namespace
constexpr const char *NETDEV_FILE =
"/proc/net/dev"; // std::ifstream does not take std::string_view as param
std::optional<std::pair<unsigned long long, unsigned long long>>
waybar::modules::Network::readBandwidthUsage() {
std::ifstream netdev(NETDEV_FILE);
if (!netdev) {
spdlog::warn("Failed to open netdev file {}", NETDEV_FILE);
return {};
}
std::string line;
// skip the headers (first two lines)
std::getline(netdev, line);
std::getline(netdev, line);
unsigned long long receivedBytes = 0ull;
unsigned long long transmittedBytes = 0ull;
while (std::getline(netdev, line)) {
std::istringstream iss(line);
std::string ifacename;
iss >> ifacename; // ifacename contains "eth0:"
ifacename.pop_back(); // remove trailing ':'
if (!checkInterface(ifacename)) {
continue;
}
// The rest of the line consists of whitespace separated counts divided
// into two groups (receive and transmit). Each group has the following
// columns: bytes, packets, errs, drop, fifo, frame, compressed, multicast
//
// We only care about the bytes count, so we'll just ignore the 7 other
// columns.
unsigned long long r = 0ull;
unsigned long long t = 0ull;
// Read received bytes
iss >> r;
// Skip all the other columns in the received group
for (int colsToSkip = 7; colsToSkip > 0; colsToSkip--) {
// skip whitespace between columns
while (iss.peek() == ' ') { iss.ignore(); }
// skip the irrelevant column
while (iss.peek() != ' ') { iss.ignore(); }
}
// Read transmit bytes
iss >> t;
receivedBytes += r;
transmittedBytes += t;
}
return {{receivedBytes, transmittedBytes}};
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config) waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60), : ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1), ifid_(-1),
@ -106,17 +96,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// the module start with no text, but the the event_box_ is shown. // the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>"); label_.set_markup("<s></s>");
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY); auto bandwidth = readBandwidthUsage();
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY); if (bandwidth.has_value()) {
if (down_octets) { bandwidth_down_total_ = (*bandwidth).first;
bandwidth_down_total_ = *down_octets; bandwidth_up_total_ = (*bandwidth).second;
} else { } else {
bandwidth_down_total_ = 0; bandwidth_down_total_ = 0;
}
if (up_octets) {
bandwidth_up_total_ = *up_octets;
} else {
bandwidth_up_total_ = 0; bandwidth_up_total_ = 0;
} }
@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const {
auto waybar::modules::Network::update() -> void { auto waybar::modules::Network::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
std::string tooltip_format; std::string tooltip_format;
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
unsigned long long bandwidth_down = 0; auto bandwidth = readBandwidthUsage();
if (down_octets) { auto bandwidth_down = 0ull;
bandwidth_down = *down_octets - bandwidth_down_total_; auto bandwidth_up = 0ull;
bandwidth_down_total_ = *down_octets; if (bandwidth.has_value()) {
auto down_octets = (*bandwidth).first;
auto up_octets = (*bandwidth).second;
bandwidth_down = down_octets - bandwidth_down_total_;
bandwidth_down_total_ = down_octets;
bandwidth_up = up_octets - bandwidth_up_total_;
bandwidth_up_total_ = up_octets;
} }
unsigned long long bandwidth_up = 0;
if (up_octets) {
bandwidth_up = *up_octets - bandwidth_up_total_;
bandwidth_up_total_ = *up_octets;
}
if (!alt_) { if (!alt_) {
auto state = getNetworkState(); auto state = getNetworkState();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) { if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
@ -348,6 +334,7 @@ auto waybar::modules::Network::update() -> void {
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("cidr", cidr_), fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_), fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
@ -376,6 +363,7 @@ auto waybar::modules::Network::update() -> void {
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("cidr", cidr_), fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_), fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
@ -409,6 +397,7 @@ void waybar::modules::Network::clearIface() {
ifname_.clear(); ifname_.clear();
essid_.clear(); essid_.clear();
ipaddr_.clear(); ipaddr_.clear();
gwaddr_.clear();
netmask_.clear(); netmask_.clear();
carrier_ = false; carrier_ = false;
cidr_ = 0; cidr_ = 0;
@ -466,7 +455,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
} }
if (!is_del_event && ifi->ifi_index == net->ifid_) { if (!is_del_event && ifi->ifi_index == net->ifid_) {
// Update inferface information // Update interface information
if (net->ifname_.empty() && ifname != NULL) { if (net->ifname_.empty() && ifname != NULL) {
std::string new_ifname (ifname, ifname_len); std::string new_ifname (ifname, ifname_len);
net->ifname_ = new_ifname; net->ifname_ = new_ifname;
@ -581,6 +570,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
break; break;
} }
char temp_gw_addr[INET6_ADDRSTRLEN];
case RTM_DELROUTE: case RTM_DELROUTE:
is_del_event = true; is_del_event = true;
case RTM_NEWROUTE: { case RTM_NEWROUTE: {
@ -595,6 +585,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
int temp_idx = -1; int temp_idx = -1;
uint32_t priority = 0; uint32_t priority = 0;
/* Find the message(s) concerting the main routing table, each message /* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry. * corresponds to a single routing table entry.
*/ */
@ -612,9 +603,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case RTA_GATEWAY: case RTA_GATEWAY:
/* The gateway of the route. /* The gateway of the route.
* *
* If someone every needs to figure out the gateway address as well, * If someone ever needs to figure out the gateway address as well,
* it's here as the attribute payload. * it's here as the attribute payload.
*/ */
inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
has_gateway = true; has_gateway = true;
break; break;
case RTA_DST: { case RTA_DST: {
@ -655,8 +647,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->clearIface(); net->clearIface();
net->ifid_ = temp_idx; net->ifid_ = temp_idx;
net->route_priority = priority; net->route_priority = priority;
net->gwaddr_ = temp_gw_addr;
spdlog::debug("network: new default route via if{} metric {}", temp_idx, priority); spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr, temp_idx, priority);
/* Ask ifname associated with temp_idx as well as carrier status */ /* Ask ifname associated with temp_idx as well as carrier status */
struct ifinfomsg ifinfo_hdr = { struct ifinfomsg ifinfo_hdr = {

View File

@ -7,7 +7,6 @@
#include "client.hpp" #include "client.hpp"
#include "modules/river/tags.hpp" #include "modules/river/tags.hpp"
#include "river-status-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river { namespace waybar::modules::river {
@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
.urgent_tags = listen_urgent_tags, .urgent_tags = listen_urgent_tags,
}; };
static void listen_command_success(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
// Do nothing but keep listener to avoid crashing when command was successful
}
static void listen_command_failure(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
spdlog::error("failure when selecting/toggling tags {}", output);
}
static const zriver_command_callback_v1_listener command_callback_listener_impl {
.success = listen_command_success,
.failure = listen_command_failure,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
@ -43,18 +59,33 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
} }
if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
} }
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */ /* Ignore event */
} }
static const wl_registry_listener registry_listener_impl = {.global = handle_global, static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove}; .global_remove = handle_global_remove};
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "tags", id, false, false), : waybar::AModule(config, "tags", id, false, false),
status_manager_{nullptr}, status_manager_{nullptr},
control_{nullptr},
seat_{nullptr},
bar_(bar), bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
output_status_{nullptr} { output_status_{nullptr} {
@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
return; return;
} }
if (!control_) {
spdlog::error("river_control_v1 not advertised");
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
box_.set_name("tags"); box_.set_name("tags");
if (!id.empty()) { if (!id.empty()) {
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
@ -89,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
} }
} }
uint32_t i = 1;
for (const auto &tag_label : tag_labels) { for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label); Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE); button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0); box_.pack_start(button, false, false, 0);
if (!config_["disable-click"].asBool()) {
button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
}
button.show(); button.show();
i <<= 1;
} }
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -107,6 +152,31 @@ Tags::~Tags() {
if (output_status_) { if (output_status_) {
zriver_output_status_v1_destroy(output_status_); zriver_output_status_v1_destroy(output_status_);
} }
if (control_) {
zriver_control_v1_destroy(control_);
}
}
void Tags::handle_primary_clicked(uint32_t tag) {
// Send river command to select tag on left mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "set-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
// Send river command to toggle tag on right mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "toggle-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
return true;
} }
void Tags::handle_focused_tags(uint32_t tags) { void Tags::handle_focused_tags(uint32_t tags) {

View File

@ -62,6 +62,7 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll)); event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility // initial visibility
event_box.show_all();
event_box.set_visible(show_passive_); event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create(); cancellable_ = Gio::Cancellable::create();
@ -287,7 +288,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
if (array != nullptr) { if (array != nullptr) {
g_free(array); g_free(array);
} }
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68
array = static_cast<guchar*>(g_memdup2(data, size));
#else
array = static_cast<guchar*>(g_memdup(data, size)); array = static_cast<guchar*>(g_memdup(data, size));
#endif
lwidth = width; lwidth = width;
lheight = height; lheight = height;
} }

View File

@ -35,11 +35,8 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
} }
auto Tray::update() -> void { auto Tray::update() -> void {
if (box_.get_children().empty()) { // Show tray only when items are available
box_.hide(); box_.set_visible(!box_.get_children().empty());
} else {
box_.show_all();
}
// Call parent update // Call parent update
AModule::update(); AModule::update();
} }

View File

@ -68,15 +68,22 @@ auto Window::update() -> void {
int leafNodesInWorkspace(const Json::Value& node) { int leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"]; auto const& nodes = node["nodes"];
if(nodes.empty()) { auto const& floating_nodes = node["floating_nodes"];
if(nodes.empty() && floating_nodes.empty()) {
if(node["type"] == "workspace") if(node["type"] == "workspace")
return 0; return 0;
else else
return 1; return 1;
} }
int sum = 0; int sum = 0;
if (!nodes.empty()) {
for(auto const& node : nodes) for(auto const& node : nodes)
sum += leafNodesInWorkspace(node); sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for(auto const& node : floating_nodes)
sum += leafNodesInWorkspace(node);
}
return sum; return sum;
} }

View File

@ -292,8 +292,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
id_{global_id++}, id_{global_id++},
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
button_visible_{false}, ignored_{false}
{ {
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
@ -395,13 +394,12 @@ std::string Task::state_string(bool shortened) const
void Task::handle_title(const char *title) void Task::handle_title(const char *title)
{ {
title_ = title; title_ = title;
hide_if_ignored();
} }
void Task::handle_app_id(const char *app_id) void Task::hide_if_ignored()
{ {
app_id_ = app_id; if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
if (tbar_->ignore_list().count(app_id)) {
ignored_ = true; ignored_ = true;
if (button_visible_) { if (button_visible_) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -415,6 +413,12 @@ void Task::handle_app_id(const char *app_id)
handle_output_enter(output); handle_output_enter(output);
} }
} }
}
void Task::handle_app_id(const char *app_id)
{
app_id_ = app_id;
hide_if_ignored();
auto ids_replace_map = tbar_->app_ids_replace_map(); auto ids_replace_map = tbar_->app_ids_replace_map();
if (ids_replace_map.count(app_id_)) { if (ids_replace_map.count(app_id_)) {
@ -451,13 +455,13 @@ void Task::handle_app_id(const char *app_id)
void Task::handle_output_enter(struct wl_output *output) void Task::handle_output_enter(struct wl_output *output)
{ {
spdlog::debug("{} entered output {}", repr(), (void*)output);
if (ignored_) { if (ignored_) {
spdlog::debug("{} is ignored", repr()); spdlog::debug("{} is ignored", repr());
return; return;
} }
spdlog::debug("{} entered output {}", repr(), (void*)output);
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
/* The task entered the output of the current bar make the button visible */ /* The task entered the output of the current bar make the button visible */
tbar_->add_button(button_); tbar_->add_button(button_);

View File

@ -0,0 +1,472 @@
#include "modules/wlr/workspace_manager.hpp"
#include <gdk/gdkwayland.h>
#include <gtkmm.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <iterator>
#include <vector>
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
namespace waybar::modules::wlr {
uint32_t WorkspaceGroup::workspace_global_id = 0;
uint32_t WorkspaceManager::group_global_id = 0;
std::map<std::string, std::string> Workspace::icons_map_;
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
const Json::Value &config)
: waybar::AModule(config, "workspaces", id, false, false),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
}
auto config_active_only = config_["active-only"];
if (config_active_only.isBool()) {
active_only_ = config_active_only.asBool();
creation_delayed_ = active_only_;
}
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
add_registry_listener(this);
if (!workspace_manager_) {
return;
}
}
auto WorkspaceManager::workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
if (sort_by_name_) {
if (sort_by_coordinates_) {
return is_name_eq ? is_coords_less : is_name_less;
} else {
return is_name_less;
}
}
if (sort_by_coordinates_) {
return is_coords_less;
}
return lhs->id() < rhs->id();
};
}
auto WorkspaceManager::sort_workspaces() -> void {
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
for (auto &group : groups_) {
auto &group_workspaces = group->workspaces();
all_workspaces.reserve(all_workspaces.size() +
std::distance(group_workspaces.begin(), group_workspaces.end()));
if (!active_only()) {
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
continue;
}
for (auto &workspace : group_workspaces) {
if (!workspace->is_active()) {
continue;
}
all_workspaces.push_back(workspace);
}
}
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
for (size_t i = 0; i < all_workspaces.size(); ++i) {
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
}
}
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
-> void {
if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!");
return;
}
if (version != 1) {
spdlog::warn("Using different workspace manager protocol version: {}", version);
}
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
}
auto WorkspaceManager::handle_workspace_group_create(
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
auto new_id = ++group_global_id;
groups_.push_back(
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
spdlog::debug("Workspace group {} created", new_id);
}
auto WorkspaceManager::handle_finished() -> void {
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::handle_done() -> void {
for (auto &group : groups_) {
group->handle_done();
}
dp.emit();
}
auto WorkspaceManager::update() -> void {
for (auto &group : groups_) {
group->update();
}
if (creation_delayed()) {
creation_delayed_ = false;
sort_workspaces();
}
AModule::update();
}
WorkspaceManager::~WorkspaceManager() {
if (!workspace_manager_) {
return;
}
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
auto it = std::find_if(groups_.begin(),
groups_.end(),
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
if (it == groups_.end()) {
spdlog::warn("Can't find group with id {}", id);
return;
}
groups_.erase(it);
}
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager,
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
: bar_(bar),
box_(box),
config_(config),
workspace_manager_(manager),
workspace_group_handle_(workspace_group_handle),
id_(id) {
add_workspace_group_listener(workspace_group_handle, this);
}
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
auto WorkspaceGroup::creation_delayed() const -> bool {
return workspace_manager_.creation_delayed();
}
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
box_.pack_start(button, false, false);
}
WorkspaceGroup::~WorkspaceGroup() {
if (!workspace_group_handle_) {
return;
}
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
}
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
auto new_id = ++workspace_global_id;
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
spdlog::debug("Workspace {} created", new_id);
}
auto WorkspaceGroup::handle_remove() -> void {
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
workspace_manager_.remove_workspace_group(id_);
}
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
output_ = output;
if (!is_visible() || workspace_manager_.creation_delayed()) {
return;
}
for (auto &workspace : workspaces_) {
add_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::is_visible() const -> bool {
return output_ != nullptr &&
(workspace_manager_.all_outputs() ||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
}
auto WorkspaceGroup::handle_output_leave() -> void {
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
output_ = nullptr;
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
return;
}
for (auto &workspace : workspaces_) {
remove_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::update() -> void {
for (auto &workspace : workspaces_) {
if (workspace_manager_.creation_delayed()) {
add_button(workspace->get_button_ref());
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
workspace->show();
}
}
workspace->update();
}
}
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
auto it = std::find_if(workspaces_.begin(),
workspaces_.end(),
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
if (it == workspaces_.end()) {
spdlog::warn("Can't find workspace with id {}", id);
return;
}
workspaces_.erase(it);
}
auto WorkspaceGroup::handle_done() -> void {
need_to_sort = false;
if (!is_visible()) {
return;
}
for (auto &workspace : workspaces_) {
workspace->handle_done();
}
if (creation_delayed()) {
return;
}
if (!workspace_manager_.all_outputs()) {
sort_workspaces();
} else {
workspace_manager_.sort_workspaces();
}
}
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
auto WorkspaceGroup::sort_workspaces() -> void {
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
for (size_t i = 0; i < workspaces_.size(); ++i) {
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
}
}
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id)
: bar_(bar),
config_(config),
workspace_group_(workspace_group),
workspace_handle_(workspace),
id_(id) {
add_workspace_listener(workspace, this);
auto config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{name}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icons_map_.empty()) {
auto format_icons = config["format-icons"];
for (auto &name : format_icons.getMemberNames()) {
icons_map_.emplace(name, format_icons[name].asString());
}
}
/* Handle click events if configured */
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-right"].isString()) {
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
false);
}
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
if (!workspace_group.is_visible()) {
return;
}
workspace_group.add_button(button_);
button_.show_all();
}
Workspace::~Workspace() {
workspace_group_.remove_button(button_);
if (!workspace_handle_) {
return;
}
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
}
auto Workspace::update() -> void {
label_.set_markup(fmt::format(
format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : "")));
}
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
state_ = 0;
for (auto state_entry : state) {
switch (state_entry) {
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
state_ |= (uint32_t)State::ACTIVE;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
state_ |= (uint32_t)State::URGENT;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
state_ |= (uint32_t)State::HIDDEN;
break;
}
}
}
auto Workspace::handle_remove() -> void {
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
workspace_group_.remove_workspace(id_);
}
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
const std::string &class_name) {
if (condition) {
context->add_class(class_name);
} else {
context->remove_class(class_name);
}
}
auto Workspace::handle_done() -> void {
spdlog::debug("Workspace {} changed to state {}", id_, state_);
auto style_context = button_.get_style_context();
add_or_remove_class(style_context, is_active(), "active");
add_or_remove_class(style_context, is_urgent(), "urgent");
add_or_remove_class(style_context, is_hidden(), "hidden");
if (workspace_group_.creation_delayed()) {
return;
}
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
button_.show_all();
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
button_.hide();
}
}
auto Workspace::get_icon() -> std::string {
if (is_active()) {
auto active_icon_it = icons_map_.find("active");
if (active_icon_it != icons_map_.end()) {
return active_icon_it->second;
}
}
auto named_icon_it = icons_map_.find(name_);
if (named_icon_it != icons_map_.end()) {
return named_icon_it->second;
}
auto default_icon_it = icons_map_.find("default");
if (default_icon_it != icons_map_.end()) {
return default_icon_it->second;
}
return name_;
}
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
std::string action;
if (config_["on-click"].isString() && bt->button == 1) {
action = config_["on-click"].asString();
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
action = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && bt->button == 3) {
action = config_["on-click-right"].asString();
}
if (action.empty())
return true;
else if (action == "activate") {
zext_workspace_handle_v1_activate(workspace_handle_);
} else if (action == "close") {
zext_workspace_handle_v1_remove(workspace_handle_);
} else {
spdlog::warn("Unknown action {}", action);
}
workspace_group_.commit();
return true;
}
auto Workspace::show() -> void { button_.show_all(); }
auto Workspace::hide() -> void { button_.hide(); }
auto Workspace::handle_name(const std::string &name) -> void {
if (name_ != name) {
workspace_group_.set_need_to_sort();
}
name_ = name;
}
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
if (coordinates_ != coordinates) {
workspace_group_.set_need_to_sort();
}
coordinates_ = coordinates;
}
} // namespace waybar::modules::wlr

View File

@ -0,0 +1,135 @@
#include "modules/wlr/workspace_manager_binding.hpp"
#include <spdlog/spdlog.h>
#include <cstdint>
#include "client.hpp"
#include "modules/wlr/workspace_manager.hpp"
namespace waybar::modules::wlr {
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
uint32_t version) {
if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) {
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
}
}
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
/* Nothing to do here */
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
void add_registry_listener(void *data) {
wl_display * display = Client::inst()->wl_display;
wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, data);
wl_display_roundtrip(display);
wl_display_roundtrip(display);
}
static void workspace_manager_handle_workspace_group(
void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) {
static_cast<WorkspaceManager *>(data)->handle_workspace_group_create(workspace_group);
}
static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_done();
}
static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) {
static_cast<WorkspaceManager *>(data)->handle_finished();
}
static const zext_workspace_manager_v1_listener workspace_manager_impl = {
.workspace_group = workspace_manager_handle_workspace_group,
.done = workspace_manager_handle_done,
.finished = workspace_manager_handle_finished,
};
zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name,
uint32_t version, void *data) {
auto *workspace_manager = static_cast<zext_workspace_manager_v1 *>(
wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version));
if (workspace_manager)
zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data);
else
spdlog::error("Failed to register manager");
return workspace_manager;
}
static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_enter(output);
}
static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_,
wl_output *output) {
static_cast<WorkspaceGroup *>(data)->handle_output_leave();
}
static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_,
zext_workspace_handle_v1 *workspace) {
static_cast<WorkspaceGroup *>(data)->handle_workspace_create(workspace);
}
static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) {
static_cast<WorkspaceGroup *>(data)->handle_remove();
}
static const zext_workspace_group_handle_v1_listener workspace_group_impl = {
.output_enter = workspace_group_handle_output_enter,
.output_leave = workspace_group_handle_output_leave,
.workspace = workspace_group_handle_workspace,
.remove = workspace_group_handle_remove};
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle,
void * data) {
zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data);
}
void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) {
static_cast<Workspace *>(data)->handle_name(name);
}
void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_,
struct wl_array *coordinates) {
std::vector<uint32_t> coords_vec;
auto coords = static_cast<uint32_t *>(coordinates->data);
for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) {
coords_vec.push_back(coords[i]);
}
static_cast<Workspace *>(data)->handle_coordinates(coords_vec);
}
void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle,
struct wl_array *state) {
std::vector<uint32_t> state_vec;
auto states = static_cast<uint32_t *>(state->data);
for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) {
state_vec.push_back(states[i]);
}
static_cast<Workspace *>(data)->handle_state(state_vec);
}
void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) {
static_cast<Workspace *>(data)->handle_remove();
}
static const zext_workspace_handle_v1_listener workspace_impl = {
.name = workspace_handle_name,
.coordinates = workspace_handle_coordinates,
.state = workspace_handle_state,
.remove = workspace_handle_remove};
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) {
zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data);
}
} // namespace waybar::modules::wlr

12
subprojects/catch2.wrap Normal file
View File

@ -0,0 +1,12 @@
[wrap-file]
directory = Catch2-2.13.7
source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip
source_filename = Catch2-2.13.7.zip
source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9
patch_filename = catch2_2.13.7-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch
patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158
[provide]
catch2 = catch2_dep

115
test/config.cpp Normal file
View File

@ -0,0 +1,115 @@
#define CATCH_CONFIG_MAIN
#include "config.hpp"
#include <catch2/catch.hpp>
TEST_CASE("Load simple config", "[config]") {
waybar::Config conf;
conf.load("test/config/simple.json");
SECTION("validate the config data") {
auto& data = conf.getConfig();
REQUIRE(data["layer"].asString() == "top");
REQUIRE(data["height"].asInt() == 30);
}
SECTION("select configs for configured output") {
auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0");
REQUIRE(configs.size() == 1);
}
SECTION("select configs for missing output") {
auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1");
REQUIRE(configs.empty());
}
}
TEST_CASE("Load config with multiple bars", "[config]") {
waybar::Config conf;
conf.load("test/config/multi.json");
SECTION("select multiple configs #1") {
auto data = conf.getOutputConfigs("DP-0", "Fake DisplayPort output #0");
REQUIRE(data.size() == 3);
REQUIRE(data[0]["layer"].asString() == "bottom");
REQUIRE(data[0]["height"].asInt() == 20);
REQUIRE(data[1]["layer"].asString() == "top");
REQUIRE(data[1]["position"].asString() == "bottom");
REQUIRE(data[1]["height"].asInt() == 21);
REQUIRE(data[2]["layer"].asString() == "overlay");
REQUIRE(data[2]["position"].asString() == "right");
REQUIRE(data[2]["height"].asInt() == 23);
}
SECTION("select multiple configs #2") {
auto data = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0");
REQUIRE(data.size() == 2);
REQUIRE(data[0]["layer"].asString() == "bottom");
REQUIRE(data[0]["height"].asInt() == 20);
REQUIRE(data[1]["layer"].asString() == "overlay");
REQUIRE(data[1]["position"].asString() == "right");
REQUIRE(data[1]["height"].asInt() == 23);
}
SECTION("select single config by output description") {
auto data = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1");
REQUIRE(data.size() == 1);
REQUIRE(data[0]["layer"].asString() == "overlay");
REQUIRE(data[0]["position"].asString() == "left");
REQUIRE(data[0]["height"].asInt() == 22);
}
}
TEST_CASE("Load simple config with include", "[config]") {
waybar::Config conf;
conf.load("test/config/include.json");
SECTION("validate the config data") {
auto& data = conf.getConfig();
// config override behavior: preserve first included value
REQUIRE(data["layer"].asString() == "top");
REQUIRE(data["height"].asInt() == 30);
// config override behavior: preserve value from the top config
REQUIRE(data["position"].asString() == "top");
// config override behavior: explicit null is still a value and should be preserved
REQUIRE((data.isMember("nullOption") && data["nullOption"].isNull()));
}
SECTION("select configs for configured output") {
auto configs = conf.getOutputConfigs("HDMI-0", "Fake HDMI output #0");
REQUIRE(configs.size() == 1);
}
SECTION("select configs for missing output") {
auto configs = conf.getOutputConfigs("HDMI-1", "Fake HDMI output #1");
REQUIRE(configs.empty());
}
}
TEST_CASE("Load multiple bar config with include", "[config]") {
waybar::Config conf;
conf.load("test/config/include-multi.json");
SECTION("bar config with sole include") {
auto data = conf.getOutputConfigs("OUT-0", "Fake output #0");
REQUIRE(data.size() == 1);
REQUIRE(data[0]["height"].asInt() == 20);
}
SECTION("bar config with output and include") {
auto data = conf.getOutputConfigs("OUT-1", "Fake output #1");
REQUIRE(data.size() == 1);
REQUIRE(data[0]["height"].asInt() == 21);
}
SECTION("bar config with output override") {
auto data = conf.getOutputConfigs("OUT-2", "Fake output #2");
REQUIRE(data.size() == 1);
REQUIRE(data[0]["height"].asInt() == 22);
}
SECTION("multiple levels of include") {
auto data = conf.getOutputConfigs("OUT-3", "Fake output #3");
REQUIRE(data.size() == 1);
REQUIRE(data[0]["height"].asInt() == 23);
}
auto& data = conf.getConfig();
REQUIRE(data.isArray());
REQUIRE(data.size() == 4);
REQUIRE(data[0]["output"].asString() == "OUT-0");
}

View File

@ -0,0 +1,7 @@
{
"layer": "top",
"position": "bottom",
"height": 30,
"output": ["HDMI-0", "DP-0"],
"nullOption": "not null"
}

View File

@ -0,0 +1,3 @@
{
"layer": "bottom"
}

View File

@ -0,0 +1,4 @@
{
"output": "OUT-0",
"height": 20
}

View File

@ -0,0 +1,3 @@
{
"height": 21
}

View File

@ -0,0 +1,4 @@
{
"output": "OUT-1",
"height": 22
}

View File

@ -0,0 +1,3 @@
{
"height": 23
}

View File

@ -0,0 +1,4 @@
{
"output": "OUT-3",
"include": "test/config/include-multi-3-0.json"
}

View File

@ -0,0 +1,16 @@
[
{
"include": "test/config/include-multi-0.json"
},
{
"output": "OUT-1",
"include": "test/config/include-multi-1.json"
},
{
"output": "OUT-2",
"include": "test/config/include-multi-2.json"
},
{
"include": "test/config/include-multi-3.json"
}
]

5
test/config/include.json Normal file
View File

@ -0,0 +1,5 @@
{
"include": ["test/config/include-1.json", "test/config/include-2.json"],
"position": "top",
"nullOption": null
}

25
test/config/multi.json Normal file
View File

@ -0,0 +1,25 @@
[
{
"layer": "bottom",
"height": 20,
"output": ["HDMI-0", "DP-0"]
},
{
"position": "bottom",
"layer": "top",
"height": 21,
"output": ["DP-0"]
},
{
"position": "left",
"layer": "overlay",
"height": 22,
"output": "Fake HDMI output #1"
},
{
"position": "right",
"layer": "overlay",
"height": 23,
"output": "!HDMI-1"
}
]

5
test/config/simple.json Normal file
View File

@ -0,0 +1,5 @@
{
"layer": "top",
"height": 30,
"output": ["HDMI-0", "DP-0"]
}

21
test/meson.build Normal file
View File

@ -0,0 +1,21 @@
test_inc = include_directories('../include')
test_dep = [
catch2,
fmt,
jsoncpp,
spdlog,
]
config_test = executable(
'config_test',
'config.cpp',
'../src/config.cpp',
dependencies: test_dep,
include_directories: test_inc,
)
test(
'Configuration test',
config_test,
workdir: meson.source_root(),
)