Merge branch 'master' of github.com:Alexays/Waybar
commit
d22d6a4522
|
@ -4,12 +4,16 @@ 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:
|
||||||
|
mem: 2048
|
||||||
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
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
|
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
|
||||||
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
|
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
|
||||||
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
|
||||||
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
|
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
|
||||||
|
|
||||||
#### Current features
|
#### Current features
|
||||||
|
@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module]
|
||||||
libmpdclient [MPD module]
|
libmpdclient [MPD module]
|
||||||
libsndio [sndio module]
|
libsndio [sndio module]
|
||||||
libevdev [KeyboardState module]
|
libevdev [KeyboardState module]
|
||||||
|
xkbregistry
|
||||||
```
|
```
|
||||||
|
|
||||||
**Build dependencies**
|
**Build dependencies**
|
||||||
|
@ -101,7 +102,8 @@ sudo apt install \
|
||||||
libsigc++-2.0-dev \
|
libsigc++-2.0-dev \
|
||||||
libspdlog-dev \
|
libspdlog-dev \
|
||||||
libwayland-dev \
|
libwayland-dev \
|
||||||
scdoc
|
scdoc \
|
||||||
|
libxkbregistry-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
#include <gtkmm/window.h>
|
#include <gtkmm/window.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
@ -36,6 +39,19 @@ struct bar_margins {
|
||||||
int left = 0;
|
int left = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct bar_mode {
|
||||||
|
bar_layer layer;
|
||||||
|
bool exclusive;
|
||||||
|
bool passthrough;
|
||||||
|
bool visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
namespace modules::sway {
|
||||||
|
class BarIpcClient;
|
||||||
|
}
|
||||||
|
#endif // HAVE_SWAY
|
||||||
|
|
||||||
class BarSurface {
|
class BarSurface {
|
||||||
protected:
|
protected:
|
||||||
BarSurface() = default;
|
BarSurface() = default;
|
||||||
|
@ -54,10 +70,16 @@ class BarSurface {
|
||||||
|
|
||||||
class Bar {
|
class Bar {
|
||||||
public:
|
public:
|
||||||
|
using bar_mode_map = std::map<std::string_view, struct bar_mode>;
|
||||||
|
static const bar_mode_map PRESET_MODES;
|
||||||
|
static const std::string_view MODE_DEFAULT;
|
||||||
|
static const std::string_view MODE_INVISIBLE;
|
||||||
|
|
||||||
Bar(struct waybar_output *w_output, const Json::Value &);
|
Bar(struct waybar_output *w_output, const Json::Value &);
|
||||||
Bar(const Bar &) = delete;
|
Bar(const Bar &) = delete;
|
||||||
~Bar() = default;
|
~Bar();
|
||||||
|
|
||||||
|
void setMode(const std::string_view &);
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
void toggle();
|
void toggle();
|
||||||
void handleSignal(int);
|
void handleSignal(int);
|
||||||
|
@ -65,27 +87,39 @@ class Bar {
|
||||||
struct waybar_output *output;
|
struct waybar_output *output;
|
||||||
Json::Value config;
|
Json::Value config;
|
||||||
struct wl_surface *surface;
|
struct wl_surface *surface;
|
||||||
bool exclusive = true;
|
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
bool vertical = false;
|
bool vertical = false;
|
||||||
Gtk::Window window;
|
Gtk::Window window;
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
std::string bar_id;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onMap(GdkEventAny *);
|
void onMap(GdkEventAny *);
|
||||||
auto setupWidgets() -> void;
|
auto setupWidgets() -> void;
|
||||||
void getModules(const Factory &, const std::string &);
|
void getModules(const Factory &, const std::string &, Gtk::Box*);
|
||||||
void setupAltFormatKeyForModule(const std::string &module_name);
|
void setupAltFormatKeyForModule(const std::string &module_name);
|
||||||
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
void setupAltFormatKeyForModuleList(const char *module_list_name);
|
||||||
|
void setMode(const bar_mode &);
|
||||||
|
|
||||||
|
/* Copy initial set of modes to allow customization */
|
||||||
|
bar_mode_map configured_modes = PRESET_MODES;
|
||||||
|
std::string last_mode_{MODE_DEFAULT};
|
||||||
|
|
||||||
std::unique_ptr<BarSurface> surface_impl_;
|
std::unique_ptr<BarSurface> surface_impl_;
|
||||||
bar_layer layer_;
|
|
||||||
Gtk::Box left_;
|
Gtk::Box left_;
|
||||||
Gtk::Box center_;
|
Gtk::Box center_;
|
||||||
Gtk::Box right_;
|
Gtk::Box right_;
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
|
||||||
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
|
std::vector<std::shared_ptr<waybar::AModule>> modules_right_;
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
using BarIpcClient = modules::sway::BarIpcClient;
|
||||||
|
std::unique_ptr<BarIpcClient> _ipc_client;
|
||||||
|
#endif
|
||||||
|
std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar
|
} // namespace waybar
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Client {
|
||||||
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;
|
Config config;
|
||||||
|
std::string bar_id;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Client() = default;
|
Client() = default;
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -50,6 +51,9 @@
|
||||||
#ifdef HAVE_LIBSNDIO
|
#ifdef HAVE_LIBSNDIO
|
||||||
#include "modules/sndio.hpp"
|
#include "modules/sndio.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_GIO_UNIX
|
||||||
|
#include "modules/inhibitor.hpp"
|
||||||
|
#endif
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
#include "modules/temperature.hpp"
|
#include "modules/temperature.hpp"
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtkmm/widget.h>
|
||||||
|
#include <gtkmm/box.h>
|
||||||
|
#include <json/json.h>
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "factory.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
class Group : public AModule {
|
||||||
|
public:
|
||||||
|
Group(const std::string&, const Bar&, const Json::Value&);
|
||||||
|
~Group() = default;
|
||||||
|
auto update() -> void;
|
||||||
|
operator Gtk::Widget &();
|
||||||
|
Gtk::Box box;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -17,6 +17,8 @@ struct waybar_time {
|
||||||
date::zoned_seconds ztime;
|
date::zoned_seconds ztime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::string kCalendarPlaceholder = "calendar";
|
||||||
|
|
||||||
class Clock : public ALabel {
|
class Clock : public ALabel {
|
||||||
public:
|
public:
|
||||||
Clock(const std::string&, const Json::Value&);
|
Clock(const std::string&, const Json::Value&);
|
||||||
|
@ -26,18 +28,19 @@ class Clock : public ALabel {
|
||||||
private:
|
private:
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
std::locale locale_;
|
std::locale locale_;
|
||||||
const date::time_zone* time_zone_;
|
std::vector<const date::time_zone*> time_zones_;
|
||||||
bool fixed_time_zone_;
|
int current_time_zone_idx_;
|
||||||
int time_zone_idx_;
|
|
||||||
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
||||||
std::string cached_calendar_text_;
|
std::string cached_calendar_text_;
|
||||||
|
bool is_calendar_in_tooltip_;
|
||||||
|
|
||||||
bool handleScroll(GdkEventScroll* e);
|
bool handleScroll(GdkEventScroll* e);
|
||||||
|
|
||||||
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);
|
const date::time_zone* current_timezone();
|
||||||
|
bool is_timezone_fixed();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "bar.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class Inhibitor : public ALabel {
|
||||||
|
public:
|
||||||
|
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||||
|
~Inhibitor() override;
|
||||||
|
auto update() -> void;
|
||||||
|
auto activated() -> bool;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto handleToggle(::GdkEventButton* const& e) -> bool;
|
||||||
|
|
||||||
|
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
|
||||||
|
const std::string inhibitors_;
|
||||||
|
int handle_ = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -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_;
|
||||||
|
@ -72,7 +73,8 @@ class Network : public ALabel {
|
||||||
int cidr_;
|
int cidr_;
|
||||||
int32_t signal_strength_dbm_;
|
int32_t signal_strength_dbm_;
|
||||||
uint8_t signal_strength_;
|
uint8_t signal_strength_;
|
||||||
uint32_t frequency_;
|
std::string signal_strength_app_;
|
||||||
|
float frequency_;
|
||||||
uint32_t route_priority;
|
uint32_t route_priority;
|
||||||
|
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread thread_;
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "modules/sway/ipc/client.hpp"
|
||||||
|
#include "util/SafeSignal.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
class Bar;
|
||||||
|
|
||||||
|
namespace modules::sway {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supported subset of i3/sway IPC barconfig object
|
||||||
|
*/
|
||||||
|
struct swaybar_config {
|
||||||
|
std::string id;
|
||||||
|
std::string mode;
|
||||||
|
std::string hidden_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* swaybar IPC client
|
||||||
|
*/
|
||||||
|
class BarIpcClient {
|
||||||
|
public:
|
||||||
|
BarIpcClient(waybar::Bar& bar);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onInitialConfig(const struct Ipc::ipc_response& res);
|
||||||
|
void onIpcEvent(const struct Ipc::ipc_response&);
|
||||||
|
void onConfigUpdate(const swaybar_config& config);
|
||||||
|
void onVisibilityUpdate(bool visible_by_modifier);
|
||||||
|
void update();
|
||||||
|
|
||||||
|
Bar& bar_;
|
||||||
|
util::JsonParser parser_;
|
||||||
|
Ipc ipc_;
|
||||||
|
|
||||||
|
swaybar_config bar_config_;
|
||||||
|
bool visible_by_modifier_ = false;
|
||||||
|
|
||||||
|
SafeSignal<bool> signal_visible_;
|
||||||
|
SafeSignal<swaybar_config> signal_config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace modules::sway
|
||||||
|
} // namespace waybar
|
|
@ -32,6 +32,7 @@ class Language : public ALabel, public sigc::trackable {
|
||||||
std::string short_name;
|
std::string short_name;
|
||||||
std::string variant;
|
std::string variant;
|
||||||
std::string short_description;
|
std::string short_description;
|
||||||
|
std::string country_flag() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class XKBContext {
|
class XKBContext {
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
|
#include "giomm/desktopappinfo.h"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include <gdk/gdk.h>
|
#include <gdk/gdk.h>
|
||||||
|
@ -61,15 +63,18 @@ class Task
|
||||||
Gtk::Image icon_;
|
Gtk::Image icon_;
|
||||||
Gtk::Label text_before_;
|
Gtk::Label text_before_;
|
||||||
Gtk::Label text_after_;
|
Gtk::Label text_after_;
|
||||||
bool button_visible_;
|
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
|
||||||
bool ignored_;
|
bool button_visible_ = false;
|
||||||
|
bool ignored_ = false;
|
||||||
|
|
||||||
bool with_icon_;
|
bool with_icon_ = false;
|
||||||
|
bool with_name_ = false;
|
||||||
std::string format_before_;
|
std::string format_before_;
|
||||||
std::string format_after_;
|
std::string format_after_;
|
||||||
|
|
||||||
std::string format_tooltip_;
|
std::string format_tooltip_;
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
std::string title_;
|
std::string title_;
|
||||||
std::string app_id_;
|
std::string app_id_;
|
||||||
uint32_t state_ = 0;
|
uint32_t state_ = 0;
|
||||||
|
@ -77,6 +82,9 @@ class Task
|
||||||
private:
|
private:
|
||||||
std::string repr() const;
|
std::string repr() const;
|
||||||
std::string state_string(bool = false) const;
|
std::string state_string(bool = false) const;
|
||||||
|
void set_app_info_from_app_id_list(const std::string& app_id_list);
|
||||||
|
bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
|
||||||
|
void hide_if_ignored();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Getter functions */
|
/* Getter functions */
|
||||||
|
@ -135,6 +143,7 @@ class Taskbar : public waybar::AModule
|
||||||
|
|
||||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
||||||
std::unordered_set<std::string> ignore_list_;
|
std::unordered_set<std::string> ignore_list_;
|
||||||
|
std::map<std::string, std::string> app_ids_replace_map_;
|
||||||
|
|
||||||
struct zwlr_foreign_toplevel_manager_v1 *manager_;
|
struct zwlr_foreign_toplevel_manager_v1 *manager_;
|
||||||
struct wl_seat *seat_;
|
struct wl_seat *seat_;
|
||||||
|
@ -157,8 +166,9 @@ class Taskbar : public waybar::AModule
|
||||||
bool show_output(struct wl_output *) const;
|
bool show_output(struct wl_output *) const;
|
||||||
bool all_outputs() const;
|
bool all_outputs() const;
|
||||||
|
|
||||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const;
|
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const;
|
||||||
const std::unordered_set<std::string>& ignore_list() const;
|
const std::unordered_set<std::string>& ignore_list() const;
|
||||||
|
const std::map<std::string, std::string>& app_ids_replace_map() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace waybar::modules::wlr */
|
} /* namespace waybar::modules::wlr */
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glibmm/dispatcher.h>
|
||||||
|
#include <sigc++/signal.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe signal wrapper.
|
||||||
|
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
|
||||||
|
public:
|
||||||
|
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
|
||||||
|
|
||||||
|
template <typename... EmitArgs>
|
||||||
|
void emit(EmitArgs&&... args) {
|
||||||
|
if (main_tid_ == std::this_thread::get_id()) {
|
||||||
|
/*
|
||||||
|
* Bypass the queue if the method is called the main thread.
|
||||||
|
* Ensures that events emitted from the main thread are processed synchronously and saves a
|
||||||
|
* few CPU cycles on locking/queuing.
|
||||||
|
* As a downside, this makes main thread events prioritized over the other threads and
|
||||||
|
* disrupts chronological order.
|
||||||
|
*/
|
||||||
|
signal_t::emit(std::forward<EmitArgs>(args)...);
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
queue_.emplace(std::forward<EmitArgs>(args)...);
|
||||||
|
}
|
||||||
|
dp_.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... EmitArgs>
|
||||||
|
inline void operator()(EmitArgs&&... args) {
|
||||||
|
emit(std::forward<EmitArgs>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
|
||||||
|
using slot_t = decltype(std::declval<signal_t>().make_slot());
|
||||||
|
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
|
||||||
|
// ensure that unwrapped methods are not accessible
|
||||||
|
using signal_t::emit_reverse;
|
||||||
|
using signal_t::make_slot;
|
||||||
|
|
||||||
|
void handle_event() {
|
||||||
|
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
|
||||||
|
auto args = queue_.front();
|
||||||
|
queue_.pop();
|
||||||
|
lock.unlock();
|
||||||
|
std::apply(cached_fn_, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Glib::Dispatcher dp_;
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::queue<arg_tuple_t> queue_;
|
||||||
|
const std::thread::id main_tid_ = std::this_thread::get_id();
|
||||||
|
// cache functor for signal emission to avoid recreating it on each event
|
||||||
|
const slot_t cached_fn_ = make_slot();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
waybar-inhibitor(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - inhibitor module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *inhibitor* module allows to take an inhibitor lock that logind provides.
|
||||||
|
See *systemd-inhibit*(1) for more information.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
*what*: ++
|
||||||
|
typeof: string or array ++
|
||||||
|
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
|
||||||
|
|
||||||
|
*format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The format, how the state should be displayed.
|
||||||
|
|
||||||
|
*format-icons*: ++
|
||||||
|
typeof: array ++
|
||||||
|
Based on the current state, the corresponding icon gets selected.
|
||||||
|
|
||||||
|
*rotate*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
Positive value to rotate the text label.
|
||||||
|
|
||||||
|
*max-length*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The maximum length in character the module should display.
|
||||||
|
|
||||||
|
*min-length*: ++
|
||||||
|
typeof: integer ++
|
||||||
|
The minimum length in characters the module should take up.
|
||||||
|
|
||||||
|
*align*: ++
|
||||||
|
typeof: float ++
|
||||||
|
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||||
|
|
||||||
|
*on-click*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when clicked on the module. A click also toggles the state
|
||||||
|
|
||||||
|
*on-click-middle*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when middle-clicked on the module using mousewheel.
|
||||||
|
|
||||||
|
*on-click-right*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when you right clicked on the module.
|
||||||
|
|
||||||
|
*on-update*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
|
*on-scroll-up*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling up on the module.
|
||||||
|
|
||||||
|
*on-scroll-down*: ++
|
||||||
|
typeof: string ++
|
||||||
|
Command to execute when scrolling down on the module.
|
||||||
|
|
||||||
|
*smooth-scrolling-threshold*: ++
|
||||||
|
typeof: double ++
|
||||||
|
Threshold to be used when scrolling.
|
||||||
|
|
||||||
|
*tooltip*: ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: true ++
|
||||||
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{status}*: status (*activated* or *deactivated*)
|
||||||
|
|
||||||
|
*{icon}*: Icon, as defined in *format-icons*
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"inhibitor": {
|
||||||
|
"what": "handle-lid-switch",
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"activated": "",
|
||||||
|
"deactivated": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -37,6 +37,8 @@ Addressed by *sway/language*
|
||||||
|
|
||||||
*{variant}*: Variant of layout (e.g. "dvorak").
|
*{variant}*: Variant of layout (e.g. "dvorak").
|
||||||
|
|
||||||
|
*{flag}*: Country flag of layout.
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -69,10 +69,6 @@ Addressed by *sway/workspaces*
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
Command to execute when the module is updated.
|
Command to execute when the module is updated.
|
||||||
|
|
||||||
*numeric-first*: ++
|
|
||||||
typeof: bool ++
|
|
||||||
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
|
|
||||||
|
|
||||||
*disable-auto-back-and-forth*: ++
|
*disable-auto-back-and-forth*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
|
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
|
||||||
|
@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
||||||
"sway/workspaces": {
|
"sway/workspaces": {
|
||||||
"disable-scroll": true,
|
"disable-scroll": true,
|
||||||
"all-outputs": true,
|
"all-outputs": true,
|
||||||
"numeric-first": false,
|
|
||||||
"format": "{name}: {icon}",
|
"format": "{name}: {icon}",
|
||||||
"format-icons": {
|
"format-icons": {
|
||||||
"1": "",
|
"1": "",
|
||||||
|
|
|
@ -70,12 +70,18 @@ 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*: ++
|
||||||
|
typeof: object ++
|
||||||
|
Dictionary of app_id to be replaced with
|
||||||
|
|
||||||
# FORMAT REPLACEMENTS
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
*{icon}*: The icon of the application.
|
*{icon}*: The icon of the application.
|
||||||
|
|
||||||
|
*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id}
|
||||||
|
|
||||||
*{title}*: The title of the application.
|
*{title}*: The title of the application.
|
||||||
|
|
||||||
*{app_id}*: The app_id (== application name) of the application.
|
*{app_id}*: The app_id (== application name) of the application.
|
||||||
|
@ -105,7 +111,10 @@ Addressed by *wlr/taskbar*
|
||||||
"on-click-middle": "close",
|
"on-click-middle": "close",
|
||||||
"ignore-list": [
|
"ignore-list": [
|
||||||
"Alacritty"
|
"Alacritty"
|
||||||
]
|
],
|
||||||
|
"app_ids-mapping": {
|
||||||
|
"firefoxdeveloperedition": "firefox-developer-edition"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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*
|
|
@ -72,14 +72,25 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||||
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.
|
||||||
|
|
||||||
|
*mode* ++
|
||||||
|
typeof: string ++
|
||||||
|
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
|
||||||
|
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
|
||||||
|
|
||||||
*exclusive* ++
|
*exclusive* ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: *true* unless the layer is set to *overlay* ++
|
default: *true* ++
|
||||||
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
|
||||||
|
|
||||||
|
*fixed-center* ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: *true*
|
||||||
|
Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space.
|
||||||
|
When false, the center block is centered in the space between the left and right block.
|
||||||
|
|
||||||
*passthrough* ++
|
*passthrough* ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: *false* unless the layer is set to *overlay* ++
|
default: *false* ++
|
||||||
Option to pass any pointer events to the window under the bar.
|
Option to pass any pointer events to the window under the bar.
|
||||||
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
|
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
|
||||||
|
|
||||||
|
@ -89,6 +100,16 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||||
Option to disable the use of gtk-layer-shell for popups.
|
Option to disable the use of gtk-layer-shell for popups.
|
||||||
Only functional if compiled with gtk-layer-shell support.
|
Only functional if compiled with gtk-layer-shell support.
|
||||||
|
|
||||||
|
*ipc* ++
|
||||||
|
typeof: bool ++
|
||||||
|
default: false ++
|
||||||
|
Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++
|
||||||
|
Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option.
|
||||||
|
|
||||||
|
*id* ++
|
||||||
|
typeof: string ++
|
||||||
|
*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance.
|
||||||
|
|
||||||
*include* ++
|
*include* ++
|
||||||
typeof: string|array ++
|
typeof: string|array ++
|
||||||
Paths to additional configuration files.
|
Paths to additional configuration files.
|
||||||
|
@ -203,6 +224,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
|
||||||
|
|
||||||
Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
||||||
|
|
||||||
|
## Grouping modules
|
||||||
|
|
||||||
|
Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally.
|
||||||
|
|
||||||
|
A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"modules-right": ["group/hardware", "clock"],
|
||||||
|
|
||||||
|
"group/hardware": {
|
||||||
|
"modules": [
|
||||||
|
"cpu",
|
||||||
|
"memory",
|
||||||
|
"battery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# SUPPORTED MODULES
|
# SUPPORTED MODULES
|
||||||
|
|
||||||
- *waybar-backlight(5)*
|
- *waybar-backlight(5)*
|
||||||
|
@ -224,5 +267,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)*
|
||||||
|
|
21
meson.build
21
meson.build
|
@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor')
|
||||||
wayland_protos = dependency('wayland-protocols')
|
wayland_protos = dependency('wayland-protocols')
|
||||||
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
||||||
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
||||||
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
|
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
|
||||||
jsoncpp = dependency('jsoncpp')
|
jsoncpp = dependency('jsoncpp')
|
||||||
sigcpp = dependency('sigc++-2.0')
|
sigcpp = dependency('sigc++-2.0')
|
||||||
libepoll = dependency('epoll-shim', required: false)
|
libepoll = dependency('epoll-shim', required: false)
|
||||||
|
@ -150,6 +150,7 @@ src_files = files(
|
||||||
'src/bar.cpp',
|
'src/bar.cpp',
|
||||||
'src/client.cpp',
|
'src/client.cpp',
|
||||||
'src/config.cpp',
|
'src/config.cpp',
|
||||||
|
'src/group.cpp',
|
||||||
'src/util/ustring_clen.cpp'
|
'src/util/ustring_clen.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -177,6 +178,7 @@ endif
|
||||||
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
||||||
src_files += [
|
src_files += [
|
||||||
'src/modules/sway/ipc/client.cpp',
|
'src/modules/sway/ipc/client.cpp',
|
||||||
|
'src/modules/sway/bar.cpp',
|
||||||
'src/modules/sway/mode.cpp',
|
'src/modules/sway/mode.cpp',
|
||||||
'src/modules/sway/language.cpp',
|
'src/modules/sway/language.cpp',
|
||||||
'src/modules/sway/window.cpp',
|
'src/modules/sway/window.cpp',
|
||||||
|
@ -186,6 +188,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
|
||||||
|
@ -238,6 +242,11 @@ if libsndio.found()
|
||||||
src_files += 'src/modules/sndio.cpp'
|
src_files += 'src/modules/sndio.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if (giounix.found() and not get_option('logind').disabled())
|
||||||
|
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
|
||||||
|
src_files += 'src/modules/inhibitor.cpp'
|
||||||
|
endif
|
||||||
|
|
||||||
if get_option('rfkill').enabled()
|
if get_option('rfkill').enabled()
|
||||||
if is_linux
|
if is_linux
|
||||||
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
||||||
|
@ -255,6 +264,10 @@ else
|
||||||
src_files += 'src/modules/simpleclock.cpp'
|
src_files += 'src/modules/simpleclock.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if get_option('experimental')
|
||||||
|
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('protocol')
|
subdir('protocol')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
|
@ -334,10 +347,15 @@ 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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (giounix.found() and not get_option('logind').disabled())
|
||||||
|
man_files += 'waybar-inhibitor.5.scd'
|
||||||
|
endif
|
||||||
|
|
||||||
foreach file : man_files
|
foreach file : man_files
|
||||||
path = '@0@'.format(file)
|
path = '@0@'.format(file)
|
||||||
basename = path.split('/')[-1]
|
basename = path.split('/')[-1]
|
||||||
|
@ -380,3 +398,4 @@ if clangtidy.found()
|
||||||
'-p', meson.build_root()
|
'-p', meson.build_root()
|
||||||
] + src_files)
|
] + src_files)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t
|
||||||
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
|
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
|
||||||
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||||
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
|
||||||
|
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
|
||||||
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
|
||||||
|
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||||
|
|
|
@ -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>
|
|
@ -110,6 +110,7 @@ def main():
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||||
|
|
||||||
for player in manager.props.player_names:
|
for player in manager.props.player_names:
|
||||||
if arguments.player is not None and arguments.player != player.name:
|
if arguments.player is not None and arguments.player != player.name:
|
||||||
|
|
238
src/bar.cpp
238
src/bar.cpp
|
@ -9,8 +9,13 @@
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
#include "factory.hpp"
|
#include "factory.hpp"
|
||||||
|
#include "group.hpp"
|
||||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SWAY
|
||||||
|
#include "modules/sway/bar.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
static constexpr const char* MIN_HEIGHT_MSG =
|
static constexpr const char* MIN_HEIGHT_MSG =
|
||||||
"Requested height: {} is less than the minimum height: {} required by the modules";
|
"Requested height: {} is less than the minimum height: {} required by the modules";
|
||||||
|
@ -23,6 +28,84 @@ static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height:
|
||||||
static constexpr const char* SIZE_DEFINED =
|
static constexpr const char* SIZE_DEFINED =
|
||||||
"{} size is defined in the config file so it will stay like that";
|
"{} size is defined in the config file so it will stay like that";
|
||||||
|
|
||||||
|
const Bar::bar_mode_map Bar::PRESET_MODES = { //
|
||||||
|
{"default",
|
||||||
|
{// Special mode to hold the global bar configuration
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = true,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"dock",
|
||||||
|
{// Modes supported by the sway config; see man sway-bar(5)
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = true,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"hide",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::TOP,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = false,
|
||||||
|
.visible = true}},
|
||||||
|
{"invisible",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::BOTTOM,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = true,
|
||||||
|
.visible = false}},
|
||||||
|
{"overlay",
|
||||||
|
{//
|
||||||
|
.layer = bar_layer::TOP,
|
||||||
|
.exclusive = false,
|
||||||
|
.passthrough = true,
|
||||||
|
.visible = true}}};
|
||||||
|
|
||||||
|
const std::string_view Bar::MODE_DEFAULT = "default";
|
||||||
|
const std::string_view Bar::MODE_INVISIBLE = "invisible";
|
||||||
|
const std::string_view DEFAULT_BAR_ID = "bar-0";
|
||||||
|
|
||||||
|
/* Deserializer for enum bar_layer */
|
||||||
|
void from_json(const Json::Value& j, bar_layer& l) {
|
||||||
|
if (j == "bottom") {
|
||||||
|
l = bar_layer::BOTTOM;
|
||||||
|
} else if (j == "top") {
|
||||||
|
l = bar_layer::TOP;
|
||||||
|
} else if (j == "overlay") {
|
||||||
|
l = bar_layer::OVERLAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deserializer for struct bar_mode */
|
||||||
|
void from_json(const Json::Value& j, bar_mode& m) {
|
||||||
|
if (j.isObject()) {
|
||||||
|
if (auto v = j["layer"]; v.isString()) {
|
||||||
|
from_json(v, m.layer);
|
||||||
|
}
|
||||||
|
if (auto v = j["exclusive"]; v.isBool()) {
|
||||||
|
m.exclusive = v.asBool();
|
||||||
|
}
|
||||||
|
if (auto v = j["passthrough"]; v.isBool()) {
|
||||||
|
m.passthrough = v.asBool();
|
||||||
|
}
|
||||||
|
if (auto v = j["visible"]; v.isBool()) {
|
||||||
|
m.visible = v.asBool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deserializer for JSON Object -> map<string compatible type, Value>
|
||||||
|
* Assumes that all the values in the object are deserializable to the same type.
|
||||||
|
*/
|
||||||
|
template <typename Key, typename Value,
|
||||||
|
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
|
||||||
|
void from_json(const Json::Value& j, std::map<Key, Value>& m) {
|
||||||
|
if (j.isObject()) {
|
||||||
|
for (auto it = j.begin(); it != j.end(); ++it) {
|
||||||
|
from_json(*it, m[it.key().asString()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_GTK_LAYER_SHELL
|
#ifdef HAVE_GTK_LAYER_SHELL
|
||||||
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||||
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
|
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
|
||||||
|
@ -391,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
: output(w_output),
|
: output(w_output),
|
||||||
config(w_config),
|
config(w_config),
|
||||||
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||||
layer_{bar_layer::BOTTOM},
|
|
||||||
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||||
|
@ -403,27 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
window.get_style_context()->add_class(config["name"].asString());
|
window.get_style_context()->add_class(config["name"].asString());
|
||||||
window.get_style_context()->add_class(config["position"].asString());
|
window.get_style_context()->add_class(config["position"].asString());
|
||||||
|
|
||||||
if (config["layer"] == "top") {
|
|
||||||
layer_ = bar_layer::TOP;
|
|
||||||
} else if (config["layer"] == "overlay") {
|
|
||||||
layer_ = bar_layer::OVERLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config["exclusive"].isBool()) {
|
|
||||||
exclusive = config["exclusive"].asBool();
|
|
||||||
} else if (layer_ == bar_layer::OVERLAY) {
|
|
||||||
// swaybar defaults: overlay mode does not reserve an exclusive zone
|
|
||||||
exclusive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool passthrough = false;
|
|
||||||
if (config["passthrough"].isBool()) {
|
|
||||||
passthrough = config["passthrough"].asBool();
|
|
||||||
} else if (layer_ == bar_layer::OVERLAY) {
|
|
||||||
// swaybar defaults: overlay mode does not accept pointer events.
|
|
||||||
passthrough = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto position = config["position"].asString();
|
auto position = config["position"].asString();
|
||||||
|
|
||||||
if (position == "right" || position == "left") {
|
if (position == "right" || position == "left") {
|
||||||
|
@ -505,15 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
|
||||||
}
|
}
|
||||||
|
|
||||||
surface_impl_->setLayer(layer_);
|
|
||||||
surface_impl_->setExclusiveZone(exclusive);
|
|
||||||
surface_impl_->setMargins(margins_);
|
surface_impl_->setMargins(margins_);
|
||||||
surface_impl_->setPassThrough(passthrough);
|
|
||||||
surface_impl_->setPosition(position);
|
surface_impl_->setPosition(position);
|
||||||
surface_impl_->setSize(width, height);
|
surface_impl_->setSize(width, height);
|
||||||
|
|
||||||
|
/* Read custom modes if available */
|
||||||
|
if (auto modes = config.get("modes", {}); modes.isObject()) {
|
||||||
|
from_json(modes, configured_modes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update "default" mode with the global bar options */
|
||||||
|
from_json(config, configured_modes[MODE_DEFAULT]);
|
||||||
|
|
||||||
|
if (auto mode = config.get("mode", {}); mode.isString()) {
|
||||||
|
setMode(config["mode"].asString());
|
||||||
|
} else {
|
||||||
|
setMode(MODE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
|
||||||
|
|
||||||
|
#if HAVE_SWAY
|
||||||
|
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
|
||||||
|
bar_id = Client::inst()->bar_id;
|
||||||
|
if (auto id = config["id"]; id.isString()) {
|
||||||
|
bar_id = id.asString();
|
||||||
|
}
|
||||||
|
if (bar_id.empty()) {
|
||||||
|
bar_id = DEFAULT_BAR_ID;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_ipc_client = std::make_unique<BarIpcClient>(*this);
|
||||||
|
} catch (const std::exception& exc) {
|
||||||
|
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
setupWidgets();
|
setupWidgets();
|
||||||
window.show_all();
|
window.show_all();
|
||||||
|
|
||||||
|
@ -528,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Need to define it here because of forward declared members */
|
||||||
|
waybar::Bar::~Bar() = default;
|
||||||
|
|
||||||
|
void waybar::Bar::setMode(const std::string_view& mode) {
|
||||||
|
using namespace std::literals::string_literals;
|
||||||
|
|
||||||
|
auto style = window.get_style_context();
|
||||||
|
/* remove styles added by previous setMode calls */
|
||||||
|
style->remove_class("mode-"s + last_mode_);
|
||||||
|
|
||||||
|
auto it = configured_modes.find(mode);
|
||||||
|
if (it != configured_modes.end()) {
|
||||||
|
last_mode_ = mode;
|
||||||
|
style->add_class("mode-"s + last_mode_);
|
||||||
|
setMode(it->second);
|
||||||
|
} else {
|
||||||
|
spdlog::warn("Unknown mode \"{}\" requested", mode);
|
||||||
|
last_mode_ = MODE_DEFAULT;
|
||||||
|
style->add_class("mode-"s + last_mode_);
|
||||||
|
setMode(configured_modes.at(MODE_DEFAULT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::Bar::setMode(const struct bar_mode& mode) {
|
||||||
|
surface_impl_->setLayer(mode.layer);
|
||||||
|
surface_impl_->setExclusiveZone(mode.exclusive);
|
||||||
|
surface_impl_->setPassThrough(mode.passthrough);
|
||||||
|
|
||||||
|
if (mode.visible) {
|
||||||
|
window.get_style_context()->remove_class("hidden");
|
||||||
|
window.set_opacity(1);
|
||||||
|
} else {
|
||||||
|
window.get_style_context()->add_class("hidden");
|
||||||
|
window.set_opacity(0);
|
||||||
|
}
|
||||||
|
surface_impl_->commit();
|
||||||
|
}
|
||||||
|
|
||||||
void waybar::Bar::onMap(GdkEventAny*) {
|
void waybar::Bar::onMap(GdkEventAny*) {
|
||||||
/*
|
/*
|
||||||
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
|
||||||
|
@ -538,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
|
||||||
|
|
||||||
void waybar::Bar::setVisible(bool value) {
|
void waybar::Bar::setVisible(bool value) {
|
||||||
visible = value;
|
visible = value;
|
||||||
if (!visible) {
|
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
|
||||||
window.get_style_context()->add_class("hidden");
|
|
||||||
window.set_opacity(0);
|
|
||||||
surface_impl_->setLayer(bar_layer::BOTTOM);
|
|
||||||
} else {
|
|
||||||
window.get_style_context()->remove_class("hidden");
|
|
||||||
window.set_opacity(1);
|
|
||||||
surface_impl_->setLayer(layer_);
|
|
||||||
}
|
|
||||||
surface_impl_->setExclusiveZone(exclusive && visible);
|
|
||||||
surface_impl_->commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::toggle() { setVisible(!visible); }
|
void waybar::Bar::toggle() { setVisible(!visible); }
|
||||||
|
@ -594,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::handleSignal(int signal) {
|
void waybar::Bar::handleSignal(int signal) {
|
||||||
for (auto& module : modules_left_) {
|
for (auto& module : modules_all_) {
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
|
||||||
if (custom != nullptr) {
|
|
||||||
custom->refresh(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& module : modules_center_) {
|
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
|
||||||
if (custom != nullptr) {
|
|
||||||
custom->refresh(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto& module : modules_right_) {
|
|
||||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||||
if (custom != nullptr) {
|
if (custom != nullptr) {
|
||||||
custom->refresh(signal);
|
custom->refresh(signal);
|
||||||
|
@ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
|
||||||
if (config[pos].isArray()) {
|
auto module_list = group ? config[pos]["modules"] : config[pos];
|
||||||
for (const auto& name : config[pos]) {
|
if (module_list.isArray()) {
|
||||||
|
for (const auto& name : module_list) {
|
||||||
try {
|
try {
|
||||||
auto module = factory.makeModule(name.asString());
|
auto ref = name.asString();
|
||||||
|
AModule* module;
|
||||||
|
|
||||||
|
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
|
||||||
|
auto group_module = new waybar::Group(ref, *this, config[ref]);
|
||||||
|
getModules(factory, ref, &group_module->box);
|
||||||
|
module = group_module;
|
||||||
|
} else {
|
||||||
|
module = factory.makeModule(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AModule> module_sp(module);
|
||||||
|
modules_all_.emplace_back(module_sp);
|
||||||
|
if (group) {
|
||||||
|
group->pack_start(*module, false, false);
|
||||||
|
} else {
|
||||||
if (pos == "modules-left") {
|
if (pos == "modules-left") {
|
||||||
modules_left_.emplace_back(module);
|
modules_left_.emplace_back(module_sp);
|
||||||
}
|
}
|
||||||
if (pos == "modules-center") {
|
if (pos == "modules-center") {
|
||||||
modules_center_.emplace_back(module);
|
modules_center_.emplace_back(module_sp);
|
||||||
}
|
}
|
||||||
if (pos == "modules-right") {
|
if (pos == "modules-right") {
|
||||||
modules_right_.emplace_back(module);
|
modules_right_.emplace_back(module_sp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module->dp.connect([module, &name] {
|
module->dp.connect([module, &name] {
|
||||||
try {
|
try {
|
||||||
|
@ -645,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
||||||
auto waybar::Bar::setupWidgets() -> void {
|
auto waybar::Bar::setupWidgets() -> void {
|
||||||
window.add(box_);
|
window.add(box_);
|
||||||
box_.pack_start(left_, false, false);
|
box_.pack_start(left_, false, false);
|
||||||
|
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
|
||||||
box_.set_center_widget(center_);
|
box_.set_center_widget(center_);
|
||||||
|
} else {
|
||||||
|
box_.pack_start(center_, true, false);
|
||||||
|
}
|
||||||
box_.pack_end(right_, false, false);
|
box_.pack_end(right_, false, false);
|
||||||
|
|
||||||
// Convert to button code for every module that is used.
|
// Convert to button code for every module that is used.
|
||||||
|
|
|
@ -199,7 +199,6 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||||
bool show_version = false;
|
bool show_version = false;
|
||||||
std::string config_opt;
|
std::string config_opt;
|
||||||
std::string style_opt;
|
std::string style_opt;
|
||||||
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") |
|
||||||
|
|
|
@ -30,6 +30,11 @@ 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]);
|
||||||
}
|
}
|
||||||
|
#ifdef USE_EXPERIMENTAL
|
||||||
|
if (ref == "wlr/workspaces") {
|
||||||
|
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_RIVER
|
#ifdef HAVE_RIVER
|
||||||
if (ref == "river/tags") {
|
if (ref == "river/tags") {
|
||||||
|
@ -89,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "sndio") {
|
if (ref == "sndio") {
|
||||||
return new waybar::modules::Sndio(id, config_[name]);
|
return new waybar::modules::Sndio(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_GIO_UNIX
|
||||||
|
if (ref == "inhibitor") {
|
||||||
|
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ref == "temperature") {
|
if (ref == "temperature") {
|
||||||
return new waybar::modules::Temperature(id, config_[name]);
|
return new waybar::modules::Temperature(id, config_[name]);
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "group.hpp"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <util/command.hpp>
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
|
||||||
|
: AModule(config, name, "", false, false),
|
||||||
|
box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Group::update() -> void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
Group::operator Gtk::Widget&() { return box; }
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -14,17 +14,51 @@
|
||||||
using waybar::modules::waybar_time;
|
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),
|
||||||
|
current_time_zone_idx_(0),
|
||||||
|
is_calendar_in_tooltip_(false)
|
||||||
|
{
|
||||||
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
|
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
|
||||||
time_zone_idx_ = 0;
|
for (const auto& zone_name: config_["timezones"]) {
|
||||||
setTimeZone(config_["timezones"][time_zone_idx_]);
|
if (!zone_name.isString() || zone_name.asString().empty()) {
|
||||||
} else {
|
time_zones_.push_back(nullptr);
|
||||||
setTimeZone(config_["timezone"]);
|
continue;
|
||||||
}
|
}
|
||||||
if (fixed_time_zone_) {
|
time_zones_.push_back(
|
||||||
|
date::locate_zone(
|
||||||
|
zone_name.asString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
|
||||||
|
time_zones_.push_back(
|
||||||
|
date::locate_zone(
|
||||||
|
config_["timezone"].asString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
|
||||||
|
if (!time_zones_.size()) {
|
||||||
|
time_zones_.push_back(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_timezone_fixed()) {
|
||||||
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
|
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
|
||||||
|
if (config_["tooltip-format"].isString()) {
|
||||||
|
std::string trimmed_format = config_["tooltip-format"].asString();
|
||||||
|
trimmed_format.erase(std::remove_if(trimmed_format.begin(),
|
||||||
|
trimmed_format.end(),
|
||||||
|
[](unsigned char x){return std::isspace(x);}),
|
||||||
|
trimmed_format.end());
|
||||||
|
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
|
||||||
|
is_calendar_in_tooltip_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config_["locale"].isString()) {
|
if (config_["locale"].isString()) {
|
||||||
locale_ = std::locale(config_["locale"].asString());
|
locale_ = std::locale(config_["locale"].asString());
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::Clock::update() -> void {
|
const date::time_zone* waybar::modules::Clock::current_timezone() {
|
||||||
if (!fixed_time_zone_) {
|
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
|
||||||
// Time zone can change. Be sure to pick that.
|
|
||||||
time_zone_ = date::current_zone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool waybar::modules::Clock::is_timezone_fixed() {
|
||||||
|
return time_zones_[current_time_zone_idx_] != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto waybar::modules::Clock::update() -> void {
|
||||||
|
auto time_zone = current_timezone();
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
waybar_time wtime = {locale_,
|
waybar_time wtime = {locale_,
|
||||||
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
|
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
|
||||||
|
std::string text = "";
|
||||||
std::string text;
|
if (!is_timezone_fixed()) {
|
||||||
if (!fixed_time_zone_) {
|
|
||||||
// As date dep is not fully compatible, prefer fmt
|
// As date dep is not fully compatible, prefer fmt
|
||||||
tzset();
|
tzset();
|
||||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||||
text = fmt::format(format_, localtime);
|
text = fmt::format(format_, localtime);
|
||||||
label_.set_markup(text);
|
|
||||||
} else {
|
} else {
|
||||||
text = fmt::format(format_, wtime);
|
text = fmt::format(format_, wtime);
|
||||||
label_.set_markup(text);
|
|
||||||
}
|
}
|
||||||
|
label_.set_markup(text);
|
||||||
|
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (config_["tooltip-format"].isString()) {
|
if (config_["tooltip-format"].isString()) {
|
||||||
const auto calendar = calendar_text(wtime);
|
std::string calendar_lines = "";
|
||||||
|
if (is_calendar_in_tooltip_) {
|
||||||
|
calendar_lines = calendar_text(wtime);
|
||||||
|
}
|
||||||
auto tooltip_format = config_["tooltip-format"].asString();
|
auto tooltip_format = config_["tooltip-format"].asString();
|
||||||
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
|
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines));
|
||||||
label_.set_tooltip_markup(tooltip_text);
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
label_.set_tooltip_markup(text);
|
label_.set_tooltip_markup(text);
|
||||||
}
|
|
||||||
}
|
|
||||||
// Call parent update
|
// Call parent update
|
||||||
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()) {
|
||||||
|
@ -97,17 +124,18 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
|
||||||
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
|
if (time_zones_.size() == 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
auto nr_zones = config_["timezones"].size();
|
|
||||||
|
auto nr_zones = time_zones_.size();
|
||||||
if (dir == SCROLL_DIR::UP) {
|
if (dir == SCROLL_DIR::UP) {
|
||||||
size_t new_idx = time_zone_idx_ + 1;
|
size_t new_idx = current_time_zone_idx_ + 1;
|
||||||
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||||
} else {
|
} else {
|
||||||
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
|
current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
|
||||||
}
|
}
|
||||||
setTimeZone(config_["timezones"][time_zone_idx_]);
|
|
||||||
update();
|
update();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
#include "modules/inhibitor.hpp"
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <gio/gunixfdlist.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
|
||||||
|
|
||||||
|
auto dbus() -> DBus {
|
||||||
|
GError *error = nullptr;
|
||||||
|
GDBusConnection* connection =
|
||||||
|
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
spdlog::error("g_bus_get_sync() failed: {}", error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
connection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto destructor = [](GDBusConnection* connection) {
|
||||||
|
GError *error = nullptr;
|
||||||
|
g_dbus_connection_close_sync(connection, nullptr, &error);
|
||||||
|
if (error) {
|
||||||
|
spdlog::error(
|
||||||
|
"g_bus_connection_close_sync failed(): {}",
|
||||||
|
error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return DBus{connection, destructor};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
|
||||||
|
GError *error = nullptr;
|
||||||
|
GUnixFDList* fd_list;
|
||||||
|
int handle;
|
||||||
|
|
||||||
|
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
|
||||||
|
"org.freedesktop.login1",
|
||||||
|
"/org/freedesktop/login1",
|
||||||
|
"org.freedesktop.login1.Manager",
|
||||||
|
"Inhibit",
|
||||||
|
g_variant_new(
|
||||||
|
"(ssss)",
|
||||||
|
inhibitors.c_str(),
|
||||||
|
"waybar",
|
||||||
|
"Asked by user",
|
||||||
|
"block"),
|
||||||
|
G_VARIANT_TYPE("(h)"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
-1,
|
||||||
|
nullptr,
|
||||||
|
&fd_list,
|
||||||
|
nullptr,
|
||||||
|
&error);
|
||||||
|
if (error) {
|
||||||
|
spdlog::error(
|
||||||
|
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
|
||||||
|
error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
handle = -1;
|
||||||
|
} else {
|
||||||
|
gint index;
|
||||||
|
g_variant_get(reply, "(h)", &index);
|
||||||
|
g_variant_unref(reply);
|
||||||
|
handle = g_unix_fd_list_get(fd_list, index, nullptr);
|
||||||
|
g_object_unref(fd_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
|
||||||
|
static const auto inhibitors = std::array{
|
||||||
|
"idle",
|
||||||
|
"shutdown",
|
||||||
|
"sleep",
|
||||||
|
"handle-power-key",
|
||||||
|
"handle-suspend-key",
|
||||||
|
"handle-hibernate-key",
|
||||||
|
"handle-lid-switch"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
|
||||||
|
== inhibitors.end()) {
|
||||||
|
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inhibitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto getInhibitors(const Json::Value& config) -> std::string {
|
||||||
|
std::string inhibitors = "idle";
|
||||||
|
|
||||||
|
if (config["what"].empty()) {
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["what"].isString()) {
|
||||||
|
return checkInhibitor(config["what"].asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["what"].isArray()) {
|
||||||
|
inhibitors = checkInhibitor(config["what"][0].asString());
|
||||||
|
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
|
||||||
|
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
|
||||||
|
}
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inhibitors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
|
||||||
|
const Json::Value& config)
|
||||||
|
: ALabel(config, "inhibitor", id, "{status}", true),
|
||||||
|
dbus_(::dbus()),
|
||||||
|
inhibitors_(::getInhibitors(config)) {
|
||||||
|
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
|
event_box_.signal_button_press_event().connect(
|
||||||
|
sigc::mem_fun(*this, &Inhibitor::handleToggle));
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Inhibitor::~Inhibitor() {
|
||||||
|
if (handle_ != -1) {
|
||||||
|
::close(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::activated() -> bool {
|
||||||
|
return handle_ != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::update() -> void {
|
||||||
|
std::string status_text = activated() ? "activated" : "deactivated";
|
||||||
|
|
||||||
|
label_.get_style_context()->remove_class(
|
||||||
|
activated() ? "deactivated" : "activated");
|
||||||
|
label_.set_markup(
|
||||||
|
fmt::format(format_, fmt::arg("status", status_text),
|
||||||
|
fmt::arg("icon", getIcon(0, status_text))));
|
||||||
|
label_.get_style_context()->add_class(status_text);
|
||||||
|
|
||||||
|
if (tooltipEnabled()) {
|
||||||
|
label_.set_tooltip_text(status_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ALabel::update();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
|
||||||
|
if (e->button == 1) {
|
||||||
|
if (activated()) {
|
||||||
|
::close(handle_);
|
||||||
|
handle_ = -1;
|
||||||
|
} else {
|
||||||
|
handle_ = ::getLocks(dbus_, inhibitors_);
|
||||||
|
if (handle_ == -1) {
|
||||||
|
spdlog::error("cannot get inhibitor locks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ALabel::handleToggle(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // waybar::modules
|
|
@ -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),
|
||||||
|
@ -98,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||||
#ifdef WANT_RFKILL
|
#ifdef WANT_RFKILL
|
||||||
rfkill_{RFKILL_TYPE_WLAN},
|
rfkill_{RFKILL_TYPE_WLAN},
|
||||||
#endif
|
#endif
|
||||||
frequency_(0) {
|
frequency_(0.0) {
|
||||||
|
|
||||||
// Start with some "text" in the module's label_, update() will then
|
// Start with some "text" in the module's label_, update() will then
|
||||||
// update it. Since the text should be different, update() will be able
|
// update it. Since the text should be different, update() will be able
|
||||||
|
@ -106,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_)) {
|
||||||
|
@ -345,12 +331,13 @@ auto waybar::modules::Network::update() -> void {
|
||||||
fmt::arg("essid", essid_),
|
fmt::arg("essid", essid_),
|
||||||
fmt::arg("signaldBm", signal_strength_dbm_),
|
fmt::arg("signaldBm", signal_strength_dbm_),
|
||||||
fmt::arg("signalStrength", signal_strength_),
|
fmt::arg("signalStrength", signal_strength_),
|
||||||
|
fmt::arg("signalStrengthApp", signal_strength_app_),
|
||||||
fmt::arg("ifname", ifname_),
|
fmt::arg("ifname", ifname_),
|
||||||
fmt::arg("netmask", netmask_),
|
fmt::arg("netmask", netmask_),
|
||||||
fmt::arg("ipaddr", ipaddr_),
|
fmt::arg("ipaddr", ipaddr_),
|
||||||
fmt::arg("gwaddr", gwaddr_),
|
fmt::arg("gwaddr", gwaddr_),
|
||||||
fmt::arg("cidr", cidr_),
|
fmt::arg("cidr", cidr_),
|
||||||
fmt::arg("frequency", frequency_),
|
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||||
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
|
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
|
||||||
|
@ -374,12 +361,13 @@ auto waybar::modules::Network::update() -> void {
|
||||||
fmt::arg("essid", essid_),
|
fmt::arg("essid", essid_),
|
||||||
fmt::arg("signaldBm", signal_strength_dbm_),
|
fmt::arg("signaldBm", signal_strength_dbm_),
|
||||||
fmt::arg("signalStrength", signal_strength_),
|
fmt::arg("signalStrength", signal_strength_),
|
||||||
|
fmt::arg("signalStrengthApp", signal_strength_app_),
|
||||||
fmt::arg("ifname", ifname_),
|
fmt::arg("ifname", ifname_),
|
||||||
fmt::arg("netmask", netmask_),
|
fmt::arg("netmask", netmask_),
|
||||||
fmt::arg("ipaddr", ipaddr_),
|
fmt::arg("ipaddr", ipaddr_),
|
||||||
fmt::arg("gwaddr", gwaddr_),
|
fmt::arg("gwaddr", gwaddr_),
|
||||||
fmt::arg("cidr", cidr_),
|
fmt::arg("cidr", cidr_),
|
||||||
fmt::arg("frequency", frequency_),
|
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||||
fmt::arg("bandwidthDownBits",
|
fmt::arg("bandwidthDownBits",
|
||||||
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||||
|
@ -417,7 +405,8 @@ void waybar::modules::Network::clearIface() {
|
||||||
cidr_ = 0;
|
cidr_ = 0;
|
||||||
signal_strength_dbm_ = 0;
|
signal_strength_dbm_ = 0;
|
||||||
signal_strength_ = 0;
|
signal_strength_ = 0;
|
||||||
frequency_ = 0;
|
signal_strength_app_.clear();
|
||||||
|
frequency_ = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||||
|
@ -484,7 +473,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||||
net->essid_.clear();
|
net->essid_.clear();
|
||||||
net->signal_strength_dbm_ = 0;
|
net->signal_strength_dbm_ = 0;
|
||||||
net->signal_strength_ = 0;
|
net->signal_strength_ = 0;
|
||||||
net->frequency_ = 0;
|
net->signal_strength_app_.clear();
|
||||||
|
net->frequency_ = 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
net->carrier_ = carrier.value();
|
net->carrier_ = carrier.value();
|
||||||
|
@ -802,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
|
||||||
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
|
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
|
||||||
// signalstrength in dBm from mBm
|
// signalstrength in dBm from mBm
|
||||||
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
|
signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
|
||||||
|
// WiFi-hardware usually operates in the range -90 to -30dBm.
|
||||||
|
|
||||||
// WiFi-hardware usually operates in the range -90 to -20dBm.
|
// If a signal is too strong, it can overwhelm receiving circuity that is designed
|
||||||
const int hardwareMax = -20;
|
// to pick up and process a certain signal level. The following percentage is scaled to
|
||||||
|
// punish signals that are too strong (>= -45dBm) or too weak (<= -45 dBm).
|
||||||
|
const int hardwareOptimum = -45;
|
||||||
const int hardwareMin = -90;
|
const int hardwareMin = -90;
|
||||||
const int strength =
|
const int strength =
|
||||||
((signal_strength_dbm_ - hardwareMin) / double{hardwareMax - hardwareMin}) * 100;
|
100 - ((abs(signal_strength_dbm_ - hardwareOptimum) / double{hardwareOptimum - hardwareMin}) * 100);
|
||||||
signal_strength_ = std::clamp(strength, 0, 100);
|
signal_strength_ = std::clamp(strength, 0, 100);
|
||||||
|
|
||||||
|
if (signal_strength_dbm_ >= -50) {
|
||||||
|
signal_strength_app_ = "Great Connectivity";
|
||||||
|
} else if (signal_strength_dbm_ >= -60) {
|
||||||
|
signal_strength_app_ = "Good Connectivity";
|
||||||
|
} else if (signal_strength_dbm_ >= -67) {
|
||||||
|
signal_strength_app_ = "Streaming";
|
||||||
|
} else if (signal_strength_dbm_ >= -70) {
|
||||||
|
signal_strength_app_ = "Web Surfing";
|
||||||
|
} else if (signal_strength_dbm_ >= -80) {
|
||||||
|
signal_strength_app_ = "Basic Connectivity";
|
||||||
|
} else {
|
||||||
|
signal_strength_app_ = "Poor Connectivity";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
|
if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
|
||||||
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
|
signal_strength_ = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
|
||||||
|
@ -817,8 +824,8 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
|
||||||
|
|
||||||
void waybar::modules::Network::parseFreq(struct nlattr **bss) {
|
void waybar::modules::Network::parseFreq(struct nlattr **bss) {
|
||||||
if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
|
if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
|
||||||
// in MHz
|
// in GHz
|
||||||
frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
|
frequency_ = (double) nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||||
if (dir == SCROLL_DIR::NONE) {
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (config_["reverse-scrolling"].asInt() == 1){
|
||||||
|
if (dir == SCROLL_DIR::UP) {
|
||||||
|
dir = SCROLL_DIR::DOWN;
|
||||||
|
} else if (dir == SCROLL_DIR::DOWN) {
|
||||||
|
dir = SCROLL_DIR::UP;
|
||||||
|
}
|
||||||
|
}
|
||||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||||
pa_volume_t change = volume_tick;
|
pa_volume_t change = volume_tick;
|
||||||
pa_cvolume pa_volume = pa_volume_;
|
pa_cvolume pa_volume = pa_volume_;
|
||||||
|
@ -211,7 +218,7 @@ static const std::array<std::string, 9> ports = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||||
std::vector<std::string> res = {default_source_name_};
|
std::vector<std::string> res = {current_sink_name_, default_source_name_};
|
||||||
std::string nameLC = port_name_ + form_factor_;
|
std::string nameLC = port_name_ + form_factor_;
|
||||||
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||||
for (auto const &port : ports) {
|
for (auto const &port : ports) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "modules/sway/bar.hpp"
|
||||||
|
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "bar.hpp"
|
||||||
|
#include "modules/sway/ipc/ipc.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules::sway {
|
||||||
|
|
||||||
|
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
|
||||||
|
{
|
||||||
|
sigc::connection handle =
|
||||||
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
|
||||||
|
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
|
||||||
|
|
||||||
|
handle.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
|
||||||
|
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
|
||||||
|
|
||||||
|
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
|
||||||
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
|
||||||
|
// Launch worker
|
||||||
|
ipc_.setWorker([this] {
|
||||||
|
try {
|
||||||
|
ipc_.handleEvent();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("BarIpcClient::handleEvent {}", e.what());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct swaybar_config parseConfig(const Json::Value& payload) {
|
||||||
|
swaybar_config conf;
|
||||||
|
if (auto id = payload["id"]; id.isString()) {
|
||||||
|
conf.id = id.asString();
|
||||||
|
}
|
||||||
|
if (auto mode = payload["mode"]; mode.isString()) {
|
||||||
|
conf.mode = mode.asString();
|
||||||
|
}
|
||||||
|
if (auto hs = payload["hidden_state"]; hs.isString()) {
|
||||||
|
conf.hidden_state = hs.asString();
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
|
||||||
|
auto payload = parser_.parse(res.payload);
|
||||||
|
if (auto success = payload.get("success", true); !success.asBool()) {
|
||||||
|
auto err = payload.get("error", "Unknown error");
|
||||||
|
throw std::runtime_error(err.asString());
|
||||||
|
}
|
||||||
|
auto config = parseConfig(payload);
|
||||||
|
onConfigUpdate(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
|
||||||
|
try {
|
||||||
|
auto payload = parser_.parse(res.payload);
|
||||||
|
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
|
||||||
|
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (payload.isMember("visible_by_modifier")) {
|
||||||
|
// visibility change for hidden bar
|
||||||
|
signal_visible_(payload["visible_by_modifier"].asBool());
|
||||||
|
} else {
|
||||||
|
// configuration update
|
||||||
|
auto config = parseConfig(payload);
|
||||||
|
signal_config_(std::move(config));
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
spdlog::error("BarIpcClient::onEvent {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
|
||||||
|
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
|
||||||
|
bar_.bar_id,
|
||||||
|
config.id,
|
||||||
|
config.mode,
|
||||||
|
config.hidden_state);
|
||||||
|
bar_config_ = config;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
|
||||||
|
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
|
||||||
|
visible_by_modifier_ = visible_by_modifier;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BarIpcClient::update() {
|
||||||
|
bool visible = visible_by_modifier_;
|
||||||
|
if (bar_config_.mode == "invisible") {
|
||||||
|
visible = false;
|
||||||
|
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules::sway
|
|
@ -99,7 +99,8 @@ auto Language::update() -> void {
|
||||||
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),
|
||||||
|
fmt::arg("flag", layout_.country_flag())));
|
||||||
label_.set_markup(display_layout);
|
label_.set_markup(display_layout);
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
if (tooltip_format_ != "") {
|
if (tooltip_format_ != "") {
|
||||||
|
@ -107,7 +108,8 @@ auto Language::update() -> void {
|
||||||
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),
|
||||||
|
fmt::arg("flag", layout_.country_flag())));
|
||||||
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);
|
||||||
|
@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() {
|
||||||
rxkb_context_unref(context_);
|
rxkb_context_unref(context_);
|
||||||
delete layout_;
|
delete layout_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Language::Layout::country_flag() const {
|
||||||
|
if (short_name.size() != 2) return "";
|
||||||
|
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
|
||||||
|
result[3] = short_name[0] + 0x45;
|
||||||
|
result[7] = short_name[1] + 0x45;
|
||||||
|
// Check if both emojis are in A-Z symbol bounds
|
||||||
|
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
|
||||||
|
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
|
||||||
|
return std::string{reinterpret_cast<char*>(result)};
|
||||||
|
}
|
||||||
} // namespace waybar::modules::sway
|
} // namespace waybar::modules::sway
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||||
Json::Value v;
|
Json::Value v;
|
||||||
v["name"] = p_w_name;
|
v["name"] = p_w_name;
|
||||||
v["target_output"] = bar_.output->name;
|
v["target_output"] = bar_.output->name;
|
||||||
|
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||||
workspaces_.emplace_back(std::move(v));
|
workspaces_.emplace_back(std::move(v));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||||
Json::Value v;
|
Json::Value v;
|
||||||
v["name"] = p_w_name;
|
v["name"] = p_w_name;
|
||||||
v["target_output"] = "";
|
v["target_output"] = "";
|
||||||
|
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||||
workspaces_.emplace_back(std::move(v));
|
workspaces_.emplace_back(std::move(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// config option to sort numeric workspace names before others
|
// sway has a defined ordering of workspaces that should be preserved in
|
||||||
bool config_numeric_first = config_["numeric-first"].asBool();
|
// the representation displayed by waybar to ensure that commands such
|
||||||
|
// as "workspace prev" or "workspace next" make sense when looking at
|
||||||
|
// the workspace representation in the bar.
|
||||||
|
// Due to waybar's own feature of persistent workspaces unknown to sway,
|
||||||
|
// custom sorting logic is necessary to make these workspaces appear
|
||||||
|
// naturally in the list of workspaces without messing up sway's
|
||||||
|
// sorting. For this purpose, a custom numbering property is created
|
||||||
|
// that preserves the order provided by sway while inserting numbered
|
||||||
|
// persistent workspaces at their natural positions.
|
||||||
|
//
|
||||||
|
// All of this code assumes that sway provides numbered workspaces first
|
||||||
|
// and other workspaces are sorted by their creation time.
|
||||||
|
//
|
||||||
|
// In a first pass, the maximum "num" value is computed to enqueue
|
||||||
|
// unnumbered workspaces behind numbered ones when computing the sort
|
||||||
|
// attribute.
|
||||||
|
int max_num = -1;
|
||||||
|
for (auto & workspace : workspaces_) {
|
||||||
|
max_num = std::max(workspace["num"].asInt(), max_num);
|
||||||
|
}
|
||||||
|
for (auto & workspace : workspaces_) {
|
||||||
|
auto workspace_num = workspace["num"].asInt();
|
||||||
|
if (workspace_num > -1) {
|
||||||
|
workspace["sort"] = workspace_num;
|
||||||
|
} else {
|
||||||
|
workspace["sort"] = ++max_num;
|
||||||
|
}
|
||||||
|
}
|
||||||
std::sort(workspaces_.begin(),
|
std::sort(workspaces_.begin(),
|
||||||
workspaces_.end(),
|
workspaces_.end(),
|
||||||
[config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) {
|
[](const Json::Value &lhs, const Json::Value &rhs) {
|
||||||
// the "num" property (integer type):
|
|
||||||
// The workspace number or -1 for workspaces that do
|
|
||||||
// not start with a number.
|
|
||||||
// We could rely on sway providing this property:
|
|
||||||
//
|
|
||||||
// auto l = lhs["num"].asInt();
|
|
||||||
// auto r = rhs["num"].asInt();
|
|
||||||
//
|
|
||||||
// We cannot rely on the "num" property as provided by sway
|
|
||||||
// via IPC, because persistent workspace might not exist in
|
|
||||||
// sway's view. However, we need this property also for
|
|
||||||
// not-yet created persistent workspace. As such, we simply
|
|
||||||
// duplicate sway's logic of assigning the "num" property
|
|
||||||
// into waybar (see convertWorkspaceNameToNum). This way the
|
|
||||||
// sorting should work out even when we include workspaces
|
|
||||||
// that do not currently exist.
|
|
||||||
auto lname = lhs["name"].asString();
|
auto lname = lhs["name"].asString();
|
||||||
auto rname = rhs["name"].asString();
|
auto rname = rhs["name"].asString();
|
||||||
int l = convertWorkspaceNameToNum(lname);
|
int l = lhs["sort"].asInt();
|
||||||
int r = convertWorkspaceNameToNum(rname);
|
int r = rhs["sort"].asInt();
|
||||||
|
|
||||||
if (l == r) {
|
if (l == r) {
|
||||||
// in case both integers are the same, lexicographical
|
// In case both integers are the same, lexicographical
|
||||||
// sort. This also covers the case when both don't have a
|
// sort. The code above already ensure that this will only
|
||||||
// number (i.e., l == r == -1).
|
// happend in case of explicitly numbered workspaces.
|
||||||
return lname < rname;
|
return lname < rname;
|
||||||
}
|
}
|
||||||
|
|
||||||
// one of the workspaces doesn't begin with a number, so
|
|
||||||
// num is -1.
|
|
||||||
if (l < 0 || r < 0) {
|
|
||||||
if (config_numeric_first) {
|
|
||||||
return r < 0;
|
|
||||||
}
|
|
||||||
return l < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// both workspaces have a "num" so let's just compare those
|
|
||||||
return l < r;
|
return l < r;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
dp.emit();
|
dp.emit();
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
#include <gdkmm/monitor.h>
|
#include <gdkmm/monitor.h>
|
||||||
|
|
||||||
#include <gtkmm/icontheme.h>
|
#include <gtkmm/icontheme.h>
|
||||||
|
@ -86,8 +88,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Method 1 - get the correct icon name from the desktop file */
|
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
|
||||||
static std::string get_from_desktop_app_info(const std::string &app_id)
|
|
||||||
{
|
{
|
||||||
static std::vector<std::string> prefixes = search_prefix();
|
static std::vector<std::string> prefixes = search_prefix();
|
||||||
|
|
||||||
|
@ -103,33 +104,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
|
||||||
".desktop"
|
".desktop"
|
||||||
};
|
};
|
||||||
|
|
||||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
|
for (auto& prefix : prefixes) {
|
||||||
|
for (auto& folder : app_folders) {
|
||||||
for (auto& prefix : prefixes)
|
for (auto& suffix : suffixes) {
|
||||||
for (auto& folder : app_folders)
|
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||||
for (auto& suffix : suffixes)
|
if (!app_info_) {
|
||||||
if (!app_info)
|
continue;
|
||||||
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
|
||||||
|
|
||||||
if (app_info && app_info->get_icon())
|
|
||||||
return app_info->get_icon()->to_string();
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
|
return app_info_;
|
||||||
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
}
|
||||||
const std::string &app_id)
|
}
|
||||||
{
|
|
||||||
if (icon_theme->lookup_icon(app_id, 24))
|
|
||||||
return app_id;
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
|
return {};
|
||||||
static std::string get_from_desktop_app_info_search(const std::string &app_id)
|
}
|
||||||
|
|
||||||
|
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
|
||||||
{
|
{
|
||||||
|
auto app_info = get_app_info_by_name(app_id);
|
||||||
|
if (app_info) {
|
||||||
|
return app_info;
|
||||||
|
}
|
||||||
|
|
||||||
std::string desktop_file = "";
|
std::string desktop_file = "";
|
||||||
|
|
||||||
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
|
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
|
||||||
|
@ -151,65 +148,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
|
||||||
}
|
}
|
||||||
g_free(desktop_list);
|
g_free(desktop_list);
|
||||||
|
|
||||||
return get_from_desktop_app_info(desktop_file);
|
return get_app_info_by_name(desktop_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
|
||||||
const std::string &app_id_list, int size)
|
|
||||||
{
|
|
||||||
std::string app_id;
|
std::string app_id;
|
||||||
std::istringstream stream(app_id_list);
|
std::istringstream stream(app_id_list);
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
/* Wayfire sends a list of app-id's in space separated format, other compositors
|
||||||
* send a single app-id, but in any case this works fine */
|
* send a single app-id, but in any case this works fine */
|
||||||
while (stream >> app_id)
|
while (stream >> app_id)
|
||||||
{
|
{
|
||||||
size_t start = 0, end = app_id.size();
|
app_info_ = get_desktop_app_info(app_id);
|
||||||
start = app_id.rfind(".", end);
|
if (app_info_) {
|
||||||
std::string app_name = app_id.substr(start+1, app_id.size());
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto lower_app_id = app_id;
|
auto lower_app_id = app_id;
|
||||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
||||||
[](char c){ return std::tolower(c); });
|
[](char c){ return std::tolower(c); });
|
||||||
|
app_info_ = get_desktop_app_info(lower_app_id);
|
||||||
|
if (app_info_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string icon_name = get_from_icon_theme(icon_theme, app_id);
|
size_t start = 0, end = app_id.size();
|
||||||
|
start = app_id.rfind(".", end);
|
||||||
|
std::string app_name = app_id.substr(start+1, app_id.size());
|
||||||
|
app_info_ = get_desktop_app_info(app_name);
|
||||||
|
if (app_info_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (icon_name.empty())
|
start = app_id.find("-");
|
||||||
icon_name = get_from_icon_theme(icon_theme, lower_app_id);
|
app_name = app_id.substr(0, start);
|
||||||
if (icon_name.empty())
|
app_info_ = get_desktop_app_info(app_name);
|
||||||
icon_name = get_from_icon_theme(icon_theme, app_name);
|
}
|
||||||
if (icon_name.empty())
|
}
|
||||||
icon_name = get_from_desktop_app_info(app_id);
|
|
||||||
if (icon_name.empty())
|
|
||||||
icon_name = get_from_desktop_app_info(lower_app_id);
|
|
||||||
if (icon_name.empty())
|
|
||||||
icon_name = get_from_desktop_app_info(app_name);
|
|
||||||
if (icon_name.empty())
|
|
||||||
icon_name = get_from_desktop_app_info_search(app_id);
|
|
||||||
|
|
||||||
if (icon_name.empty())
|
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
||||||
icon_name = "unknown";
|
const std::string &app_id)
|
||||||
|
{
|
||||||
|
if (icon_theme->lookup_icon(app_id, 24))
|
||||||
|
return app_id;
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
|
||||||
|
{
|
||||||
|
std::string ret_icon_name = "unknown";
|
||||||
|
if (app_info) {
|
||||||
|
std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
|
||||||
|
if (!icon_name.empty()) {
|
||||||
|
ret_icon_name = icon_name;
|
||||||
|
} else {
|
||||||
|
if (app_info->get_icon()) {
|
||||||
|
ret_icon_name = app_info->get_icon()->to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
|
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
|
||||||
pixbuf = load_icon_from_file(icon_name, size);
|
pixbuf = load_icon_from_file(ret_icon_name, size);
|
||||||
else
|
else
|
||||||
pixbuf = {};
|
pixbuf = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pixbuf) {
|
if (pixbuf) {
|
||||||
image.set(pixbuf);
|
image.set(pixbuf);
|
||||||
found = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Task class implementation */
|
/* Task class implementation */
|
||||||
|
@ -276,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);
|
||||||
|
|
||||||
|
@ -290,13 +305,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
|
||||||
content_.show();
|
content_.show();
|
||||||
button_.add(content_);
|
button_.add(content_);
|
||||||
|
|
||||||
with_icon_ = false;
|
|
||||||
format_before_.clear();
|
format_before_.clear();
|
||||||
format_after_.clear();
|
format_after_.clear();
|
||||||
|
|
||||||
if (config_["format"].isString()) {
|
if (config_["format"].isString()) {
|
||||||
/* The user defined a format string, use it */
|
/* The user defined a format string, use it */
|
||||||
auto format = config_["format"].asString();
|
auto format = config_["format"].asString();
|
||||||
|
if (format.find("{name}") != std::string::npos) {
|
||||||
|
with_name_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
auto icon_pos = format.find("{icon}");
|
auto icon_pos = format.find("{icon}");
|
||||||
if (icon_pos == 0) {
|
if (icon_pos == 0) {
|
||||||
|
@ -377,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());
|
||||||
|
@ -397,14 +413,35 @@ void Task::handle_app_id(const char *app_id)
|
||||||
handle_output_enter(output);
|
handle_output_enter(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!with_icon_)
|
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();
|
||||||
|
if (ids_replace_map.count(app_id_)) {
|
||||||
|
auto replaced_id = ids_replace_map[app_id_];
|
||||||
|
spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
|
||||||
|
app_id_ = replaced_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!with_icon_ && !with_name_) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_app_info_from_app_id_list(app_id_);
|
||||||
|
name_ = app_info_ ? app_info_->get_display_name() : app_id;
|
||||||
|
|
||||||
|
if (!with_icon_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (auto& icon_theme : tbar_->icon_themes()) {
|
for (auto& icon_theme : tbar_->icon_themes()) {
|
||||||
if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) {
|
if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -418,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_);
|
||||||
|
@ -560,14 +597,17 @@ void Task::update()
|
||||||
{
|
{
|
||||||
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
|
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
|
||||||
std::string title = title_;
|
std::string title = title_;
|
||||||
|
std::string name = name_;
|
||||||
std::string app_id = app_id_;
|
std::string app_id = app_id_;
|
||||||
if (markup) {
|
if (markup) {
|
||||||
title = Glib::Markup::escape_text(title);
|
title = Glib::Markup::escape_text(title);
|
||||||
|
name = Glib::Markup::escape_text(name);
|
||||||
app_id = Glib::Markup::escape_text(app_id);
|
app_id = Glib::Markup::escape_text(app_id);
|
||||||
}
|
}
|
||||||
if (!format_before_.empty()) {
|
if (!format_before_.empty()) {
|
||||||
auto txt = fmt::format(format_before_,
|
auto txt = fmt::format(format_before_,
|
||||||
fmt::arg("title", title),
|
fmt::arg("title", title),
|
||||||
|
fmt::arg("name", name),
|
||||||
fmt::arg("app_id", app_id),
|
fmt::arg("app_id", app_id),
|
||||||
fmt::arg("state", state_string()),
|
fmt::arg("state", state_string()),
|
||||||
fmt::arg("short_state", state_string(true))
|
fmt::arg("short_state", state_string(true))
|
||||||
|
@ -581,6 +621,7 @@ void Task::update()
|
||||||
if (!format_after_.empty()) {
|
if (!format_after_.empty()) {
|
||||||
auto txt = fmt::format(format_after_,
|
auto txt = fmt::format(format_after_,
|
||||||
fmt::arg("title", title),
|
fmt::arg("title", title),
|
||||||
|
fmt::arg("name", name),
|
||||||
fmt::arg("app_id", app_id),
|
fmt::arg("app_id", app_id),
|
||||||
fmt::arg("state", state_string()),
|
fmt::arg("state", state_string()),
|
||||||
fmt::arg("short_state", state_string(true))
|
fmt::arg("short_state", state_string(true))
|
||||||
|
@ -595,6 +636,7 @@ void Task::update()
|
||||||
if (!format_tooltip_.empty()) {
|
if (!format_tooltip_.empty()) {
|
||||||
auto txt = fmt::format(format_tooltip_,
|
auto txt = fmt::format(format_tooltip_,
|
||||||
fmt::arg("title", title),
|
fmt::arg("title", title),
|
||||||
|
fmt::arg("name", name),
|
||||||
fmt::arg("app_id", app_id),
|
fmt::arg("app_id", app_id),
|
||||||
fmt::arg("state", state_string()),
|
fmt::arg("state", state_string()),
|
||||||
fmt::arg("short_state", state_string(true))
|
fmt::arg("short_state", state_string(true))
|
||||||
|
@ -722,6 +764,15 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load app_id remappings
|
||||||
|
if (config_["app_ids-mapping"].isObject()) {
|
||||||
|
const Json::Value& mapping = config_["app_ids-mapping"];
|
||||||
|
const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
|
||||||
|
for (auto& app_id : app_ids) {
|
||||||
|
app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
icon_themes_.push_back(Gtk::IconTheme::get_default());
|
icon_themes_.push_back(Gtk::IconTheme::get_default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,10 +904,10 @@ bool Taskbar::all_outputs() const
|
||||||
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
|
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
|
||||||
{
|
|
||||||
return icon_themes_;
|
|
||||||
}
|
|
||||||
const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
|
const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
|
||||||
|
|
||||||
|
const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
|
||||||
|
|
||||||
} /* namespace waybar::modules::wlr */
|
} /* namespace waybar::modules::wlr */
|
||||||
|
|
|
@ -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,24 @@
|
||||||
|
#pragma once
|
||||||
|
#include <glibmm/main.h>
|
||||||
|
/**
|
||||||
|
* Minimal Glib application to be used for tests that require Glib main loop
|
||||||
|
*/
|
||||||
|
class GlibTestsFixture : public sigc::trackable {
|
||||||
|
public:
|
||||||
|
GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {}
|
||||||
|
|
||||||
|
void setTimeout(int timeout) {
|
||||||
|
Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); },
|
||||||
|
timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(std::function<void()> fn) {
|
||||||
|
Glib::signal_idle().connect_once(fn);
|
||||||
|
main_loop_->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void quit() { main_loop_->quit(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Glib::RefPtr<Glib::MainLoop> main_loop_;
|
||||||
|
};
|
|
@ -0,0 +1,145 @@
|
||||||
|
#define CATCH_CONFIG_RUNNER
|
||||||
|
#include "util/SafeSignal.hpp"
|
||||||
|
|
||||||
|
#include <glibmm.h>
|
||||||
|
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "GlibTestsFixture.hpp"
|
||||||
|
|
||||||
|
using namespace waybar;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic sanity test for SafeSignal:
|
||||||
|
* check that type deduction works, events are delivered and the order is right
|
||||||
|
* Running this with -fsanitize=thread should not fail
|
||||||
|
*/
|
||||||
|
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") {
|
||||||
|
const int NUM_EVENTS = 100;
|
||||||
|
int count = 0;
|
||||||
|
int last_value = 0;
|
||||||
|
|
||||||
|
SafeSignal<int, std::string> test_signal;
|
||||||
|
|
||||||
|
const auto main_tid = std::this_thread::get_id();
|
||||||
|
std::thread producer;
|
||||||
|
|
||||||
|
// timeout the test in 500ms
|
||||||
|
setTimeout(500);
|
||||||
|
|
||||||
|
test_signal.connect([&](auto val, auto str) {
|
||||||
|
static_assert(std::is_same<int, decltype(val)>::value);
|
||||||
|
static_assert(std::is_same<std::string, decltype(str)>::value);
|
||||||
|
// check that we're in the same thread as the main loop
|
||||||
|
REQUIRE(std::this_thread::get_id() == main_tid);
|
||||||
|
// check event order
|
||||||
|
REQUIRE(val == last_value + 1);
|
||||||
|
|
||||||
|
last_value = val;
|
||||||
|
if (++count >= NUM_EVENTS) {
|
||||||
|
this->quit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
run([&]() {
|
||||||
|
// check that events from the same thread are delivered and processed synchronously
|
||||||
|
test_signal.emit(1, "test");
|
||||||
|
REQUIRE(count == 1);
|
||||||
|
|
||||||
|
// start another thread and generate events
|
||||||
|
producer = std::thread([&]() {
|
||||||
|
for (auto i = 2; i <= NUM_EVENTS; ++i) {
|
||||||
|
test_signal.emit(i, "test");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
producer.join();
|
||||||
|
REQUIRE(count == NUM_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TestObject {
|
||||||
|
T value;
|
||||||
|
unsigned copied = 0;
|
||||||
|
unsigned moved = 0;
|
||||||
|
|
||||||
|
TestObject(const T& v) : value(v){};
|
||||||
|
~TestObject() = default;
|
||||||
|
|
||||||
|
TestObject(const TestObject& other)
|
||||||
|
: value(other.value), copied(other.copied + 1), moved(other.moved) {}
|
||||||
|
|
||||||
|
TestObject(TestObject&& other) noexcept
|
||||||
|
: value(std::move(other.value)),
|
||||||
|
copied(std::exchange(other.copied, 0)),
|
||||||
|
moved(std::exchange(other.moved, 0) + 1) {}
|
||||||
|
|
||||||
|
TestObject& operator=(const TestObject& other) {
|
||||||
|
value = other.value;
|
||||||
|
copied = other.copied + 1;
|
||||||
|
moved = other.moved;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestObject& operator=(TestObject&& other) noexcept {
|
||||||
|
value = std::move(other.value);
|
||||||
|
copied = std::exchange(other.copied, 0);
|
||||||
|
moved = std::exchange(other.moved, 0) + 1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(T other) const { return value == other; }
|
||||||
|
operator T() const { return value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check the number of copies/moves performed on the object passed through SafeSignal
|
||||||
|
*/
|
||||||
|
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") {
|
||||||
|
const int NUM_EVENTS = 3;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
SafeSignal<TestObject<int>> test_signal;
|
||||||
|
|
||||||
|
std::thread producer;
|
||||||
|
|
||||||
|
// timeout the test in 500ms
|
||||||
|
setTimeout(500);
|
||||||
|
|
||||||
|
test_signal.connect([&](auto& val) {
|
||||||
|
static_assert(std::is_same<TestObject<int>, remove_cvref_t<decltype(val)>>::value);
|
||||||
|
|
||||||
|
/* explicit move in the producer thread */
|
||||||
|
REQUIRE(val.moved <= 1);
|
||||||
|
/* copy within the SafeSignal queuing code */
|
||||||
|
REQUIRE(val.copied <= 1);
|
||||||
|
|
||||||
|
if (++count >= NUM_EVENTS) {
|
||||||
|
this->quit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
run([&]() {
|
||||||
|
test_signal.emit(1);
|
||||||
|
REQUIRE(count == 1);
|
||||||
|
producer = std::thread([&]() {
|
||||||
|
for (auto i = 2; i <= NUM_EVENTS; ++i) {
|
||||||
|
TestObject<int> t{i};
|
||||||
|
// check that signal.emit accepts moved objects
|
||||||
|
test_signal.emit(std::move(t));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
producer.join();
|
||||||
|
REQUIRE(count == NUM_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
Glib::init();
|
||||||
|
return Catch::Session().run(argc, argv);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ test_inc = include_directories('../include')
|
||||||
test_dep = [
|
test_dep = [
|
||||||
catch2,
|
catch2,
|
||||||
fmt,
|
fmt,
|
||||||
|
gtkmm,
|
||||||
jsoncpp,
|
jsoncpp,
|
||||||
spdlog,
|
spdlog,
|
||||||
]
|
]
|
||||||
|
@ -14,8 +15,21 @@ config_test = executable(
|
||||||
include_directories: test_inc,
|
include_directories: test_inc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
safesignal_test = executable(
|
||||||
|
'safesignal_test',
|
||||||
|
'SafeSignal.cpp',
|
||||||
|
dependencies: test_dep,
|
||||||
|
include_directories: test_inc,
|
||||||
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Configuration test',
|
'Configuration test',
|
||||||
config_test,
|
config_test,
|
||||||
workdir: meson.source_root(),
|
workdir: meson.source_root(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'SafeSignal test',
|
||||||
|
safesignal_test,
|
||||||
|
workdir: meson.source_root(),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue