Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/add-name-to-taskbar
commit
166504256a
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -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
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -19,11 +19,11 @@ class Cpu : public ALabel {
|
||||||
auto update() -> void;
|
auto update() -> void;
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
std::vector<std::tuple<size_t, size_t>> prev_times_;
|
std::vector<std::tuple<size_t, size_t>> prev_times_;
|
||||||
|
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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 ++
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -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 ++
|
||||||
|
|
|
@ -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*
|
|
@ -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)*
|
||||||
|
|
13
meson.build
13
meson.build
|
@ -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()
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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>
|
|
@ -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 = []
|
||||||
|
|
|
@ -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>
|
|
@ -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}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,8 +67,10 @@ 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();
|
||||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
if (size) {
|
||||||
format_icons = format_icons[idx];
|
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||||
|
format_icons = format_icons[idx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (format_icons.isString()) {
|
if (format_icons.isString()) {
|
||||||
return format_icons.asString();
|
return format_icons.asString();
|
||||||
|
@ -90,8 +92,10 @@ 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();
|
||||||
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
if (size) {
|
||||||
format_icons = format_icons[idx];
|
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
|
||||||
|
format_icons = format_icons[idx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (format_icons.isString()) {
|
if (format_icons.isString()) {
|
||||||
return format_icons.asString();
|
return format_icons.asString();
|
||||||
|
|
11
src/bar.cpp
11
src/bar.cpp
|
@ -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;
|
||||||
|
|
||||||
|
|
160
src/client.cpp
160
src/client.cpp
|
@ -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);
|
spdlog::info("Using CSS file {}", css_file.value());
|
||||||
return {config_file, css_file};
|
return css_file.value();
|
||||||
}
|
};
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -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
|
|
@ -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") {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1,8 +1,29 @@
|
||||||
#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_);
|
||||||
if (!info.is_open()) {
|
if (!info.is_open()) {
|
||||||
throw std::runtime_error("Can't open " + data_dir_);
|
throw std::runtime_error("Can't open " + 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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,15 @@ const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name
|
||||||
Language::Language(const std::string& id, const Json::Value& config)
|
Language::Language(const std::string& id, const Json::Value& config)
|
||||||
: ALabel(config, "language", id, "{}", 0, true) {
|
: ALabel(config, "language", id, "{}", 0, true) {
|
||||||
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
||||||
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
|
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
|
||||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
|
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
|
||||||
}
|
}
|
||||||
if (format_.find("{shortDescription}") != std::string::npos) {
|
if (format_.find("{shortDescription}") != std::string::npos) {
|
||||||
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
|
||||||
}
|
}
|
||||||
if (config.isMember("tooltip-format")) {
|
if (config.isMember("tooltip-format")) {
|
||||||
tooltip_format_ = config["tooltip-format"].asString();
|
tooltip_format_ = config["tooltip-format"].asString();
|
||||||
}
|
}
|
||||||
ipc_.subscribe(R"(["input"])");
|
ipc_.subscribe(R"(["input"])");
|
||||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
||||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
||||||
|
@ -102,16 +102,16 @@ auto Language::update() -> void {
|
||||||
fmt::arg("variant", layout_.variant)));
|
fmt::arg("variant", layout_.variant)));
|
||||||
label_.set_markup(display_layout);
|
label_.set_markup(display_layout);
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (tooltip_format_ != "") {
|
if (tooltip_format_ != "") {
|
||||||
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
|
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
|
||||||
fmt::arg("short", layout_.short_name),
|
fmt::arg("short", layout_.short_name),
|
||||||
fmt::arg("shortDescription", layout_.short_description),
|
fmt::arg("shortDescription", layout_.short_description),
|
||||||
fmt::arg("long", layout_.full_name),
|
fmt::arg("long", layout_.full_name),
|
||||||
fmt::arg("variant", layout_.variant)));
|
fmt::arg("variant", layout_.variant)));
|
||||||
label_.set_tooltip_markup(tooltip_display_layout);
|
label_.set_tooltip_markup(tooltip_display_layout);
|
||||||
} else {
|
} else {
|
||||||
label_.set_tooltip_markup(display_layout);
|
label_.set_tooltip_markup(display_layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event_box_.show();
|
event_box_.show();
|
||||||
|
@ -126,7 +126,7 @@ auto Language::set_current_layout(std::string current_layout) -> void {
|
||||||
|
|
||||||
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
||||||
std::map<std::string, std::vector<Layout*>> found_by_short_names;
|
std::map<std::string, std::vector<Layout*>> found_by_short_names;
|
||||||
XKBContext xkb_context;
|
XKBContext xkb_context;
|
||||||
auto layout = xkb_context.next_layout();
|
auto layout = xkb_context.next_layout();
|
||||||
for (; layout != nullptr; layout = xkb_context.next_layout()) {
|
for (; layout != nullptr; layout = xkb_context.next_layout()) {
|
||||||
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
|
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
|
||||||
|
@ -161,15 +161,15 @@ auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) ->
|
||||||
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
|
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
|
||||||
short_name_to_number_map[used_layout->short_name] = 1;
|
short_name_to_number_map[used_layout->short_name] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayed_short_flag != static_cast<std::byte>(0)) {
|
if (displayed_short_flag != static_cast<std::byte>(0)) {
|
||||||
int& number = short_name_to_number_map[used_layout->short_name];
|
int& number = short_name_to_number_map[used_layout->short_name];
|
||||||
used_layout->short_name =
|
used_layout->short_name =
|
||||||
used_layout->short_name + std::to_string(number);
|
used_layout->short_name + std::to_string(number);
|
||||||
used_layout->short_description =
|
used_layout->short_description =
|
||||||
used_layout->short_description + std::to_string(number);
|
used_layout->short_description + std::to_string(number);
|
||||||
++number;
|
++number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,14 +195,14 @@ auto Language::XKBContext::next_layout() -> Layout* {
|
||||||
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
|
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
|
||||||
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
|
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
|
||||||
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
|
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
|
||||||
std::string short_description;
|
std::string short_description;
|
||||||
if (short_description_ != nullptr) {
|
if (short_description_ != nullptr) {
|
||||||
short_description = std::string(short_description_);
|
short_description = std::string(short_description_);
|
||||||
base_layouts_by_name_.emplace(name, xkb_layout_);
|
base_layouts_by_name_.emplace(name, xkb_layout_);
|
||||||
} else {
|
} else {
|
||||||
auto base_layout = base_layouts_by_name_[name];
|
auto base_layout = base_layouts_by_name_[name];
|
||||||
short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
|
short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
|
||||||
}
|
}
|
||||||
delete layout_;
|
delete layout_;
|
||||||
layout_ = new Layout{description, name, variant, short_description};
|
layout_ = new Layout{description, name, variant, short_description};
|
||||||
return layout_;
|
return layout_;
|
||||||
|
|
|
@ -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;
|
||||||
for(auto const& node : nodes)
|
if (!nodes.empty()) {
|
||||||
sum += leafNodesInWorkspace(node);
|
for(auto const& node : nodes)
|
||||||
|
sum += leafNodesInWorkspace(node);
|
||||||
|
}
|
||||||
|
if (!floating_nodes.empty()) {
|
||||||
|
for(auto const& node : floating_nodes)
|
||||||
|
sum += leafNodesInWorkspace(node);
|
||||||
|
}
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_);
|
||||||
|
|
|
@ -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
|
|
@ -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, ®istry_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
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"layer": "top",
|
||||||
|
"position": "bottom",
|
||||||
|
"height": 30,
|
||||||
|
"output": ["HDMI-0", "DP-0"],
|
||||||
|
"nullOption": "not null"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"layer": "bottom"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"output": "OUT-0",
|
||||||
|
"height": 20
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"height": 21
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"output": "OUT-1",
|
||||||
|
"height": 22
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"height": 23
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"output": "OUT-3",
|
||||||
|
"include": "test/config/include-multi-3-0.json"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"include": ["test/config/include-1.json", "test/config/include-2.json"],
|
||||||
|
"position": "top",
|
||||||
|
"nullOption": null
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"layer": "top",
|
||||||
|
"height": 30,
|
||||||
|
"output": ["HDMI-0", "DP-0"]
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
)
|
Loading…
Reference in New Issue