mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
Merge branch 'master' of github.com:Alexays/Waybar
This commit is contained in:
commit
d22d6a4522
8
.github/workflows/freebsd.yml
vendored
8
.github/workflows/freebsd.yml
vendored
@ -4,12 +4,16 @@ on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
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:
|
||||
- uses: actions/checkout@v2
|
||||
- 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:
|
||||
mem: 2048
|
||||
usesh: true
|
||||
prepare: |
|
||||
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>
|
||||
> 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)*
|
||||
|
||||
#### Current features
|
||||
@ -69,6 +69,7 @@ libdbusmenu-gtk3 [Tray module]
|
||||
libmpdclient [MPD module]
|
||||
libsndio [sndio module]
|
||||
libevdev [KeyboardState module]
|
||||
xkbregistry
|
||||
```
|
||||
|
||||
**Build dependencies**
|
||||
@ -101,7 +102,8 @@ sudo apt install \
|
||||
libsigc++-2.0-dev \
|
||||
libspdlog-dev \
|
||||
libwayland-dev \
|
||||
scdoc
|
||||
scdoc \
|
||||
libxkbregistry-dev
|
||||
```
|
||||
|
||||
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <gtkmm/window.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
@ -36,6 +39,19 @@ struct bar_margins {
|
||||
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 {
|
||||
protected:
|
||||
BarSurface() = default;
|
||||
@ -54,10 +70,16 @@ class BarSurface {
|
||||
|
||||
class Bar {
|
||||
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(const Bar &) = delete;
|
||||
~Bar() = default;
|
||||
~Bar();
|
||||
|
||||
void setMode(const std::string_view &);
|
||||
void setVisible(bool visible);
|
||||
void toggle();
|
||||
void handleSignal(int);
|
||||
@ -65,27 +87,39 @@ class Bar {
|
||||
struct waybar_output *output;
|
||||
Json::Value config;
|
||||
struct wl_surface *surface;
|
||||
bool exclusive = true;
|
||||
bool visible = true;
|
||||
bool vertical = false;
|
||||
Gtk::Window window;
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
std::string bar_id;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void onMap(GdkEventAny *);
|
||||
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 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_;
|
||||
bar_layer layer_;
|
||||
Gtk::Box left_;
|
||||
Gtk::Box center_;
|
||||
Gtk::Box right_;
|
||||
Gtk::Box box_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
|
||||
std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
|
||||
std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
|
||||
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
|
||||
|
@ -29,6 +29,7 @@ class Client {
|
||||
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
|
||||
std::vector<std::unique_ptr<Bar>> bars;
|
||||
Config config;
|
||||
std::string bar_id;
|
||||
|
||||
private:
|
||||
Client() = default;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#endif
|
||||
#ifdef HAVE_WLR
|
||||
#include "modules/wlr/taskbar.hpp"
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_RIVER
|
||||
#include "modules/river/tags.hpp"
|
||||
@ -50,6 +51,9 @@
|
||||
#ifdef HAVE_LIBSNDIO
|
||||
#include "modules/sndio.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_GIO_UNIX
|
||||
#include "modules/inhibitor.hpp"
|
||||
#endif
|
||||
#include "bar.hpp"
|
||||
#include "modules/custom.hpp"
|
||||
#include "modules/temperature.hpp"
|
||||
|
21
include/group.hpp
Normal file
21
include/group.hpp
Normal file
@ -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;
|
||||
};
|
||||
|
||||
const std::string kCalendarPlaceholder = "calendar";
|
||||
|
||||
class Clock : public ALabel {
|
||||
public:
|
||||
Clock(const std::string&, const Json::Value&);
|
||||
@ -26,18 +28,19 @@ class Clock : public ALabel {
|
||||
private:
|
||||
util::SleeperThread thread_;
|
||||
std::locale locale_;
|
||||
const date::time_zone* time_zone_;
|
||||
bool fixed_time_zone_;
|
||||
int time_zone_idx_;
|
||||
std::vector<const date::time_zone*> time_zones_;
|
||||
int current_time_zone_idx_;
|
||||
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
|
||||
std::string cached_calendar_text_;
|
||||
bool is_calendar_in_tooltip_;
|
||||
|
||||
bool handleScroll(GdkEventScroll* e);
|
||||
|
||||
auto calendar_text(const waybar_time& wtime) -> std::string;
|
||||
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
|
||||
auto first_day_of_week() -> date::weekday;
|
||||
bool setTimeZone(Json::Value zone_name);
|
||||
const date::time_zone* current_timezone();
|
||||
bool is_timezone_fixed();
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
|
27
include/modules/inhibitor.hpp
Normal file
27
include/modules/inhibitor.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "bar.hpp"
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class Inhibitor : public ALabel {
|
||||
public:
|
||||
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~Inhibitor() override;
|
||||
auto update() -> void;
|
||||
auto activated() -> bool;
|
||||
|
||||
private:
|
||||
auto handleToggle(::GdkEventButton* const& e) -> bool;
|
||||
|
||||
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
|
||||
const std::string inhibitors_;
|
||||
int handle_ = -1;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
@ -43,6 +43,7 @@ class Network : public ALabel {
|
||||
const std::string getNetworkState() const;
|
||||
void clearIface();
|
||||
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
|
||||
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
|
||||
|
||||
int ifid_;
|
||||
sa_family_t family_;
|
||||
@ -72,7 +73,8 @@ class Network : public ALabel {
|
||||
int cidr_;
|
||||
int32_t signal_strength_dbm_;
|
||||
uint8_t signal_strength_;
|
||||
uint32_t frequency_;
|
||||
std::string signal_strength_app_;
|
||||
float frequency_;
|
||||
uint32_t route_priority;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "river-status-unstable-v1-client-protocol.h"
|
||||
#include "river-control-unstable-v1-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::river {
|
||||
@ -20,7 +21,12 @@ class Tags : public waybar::AModule {
|
||||
void handle_view_tags(struct wl_array *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_control_v1 *control_;
|
||||
struct wl_seat *seat_;
|
||||
|
||||
private:
|
||||
const waybar::Bar & bar_;
|
||||
|
49
include/modules/sway/bar.hpp
Normal file
49
include/modules/sway/bar.hpp
Normal file
@ -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 variant;
|
||||
std::string short_description;
|
||||
std::string country_flag() const;
|
||||
};
|
||||
|
||||
class XKBContext {
|
||||
|
@ -3,11 +3,13 @@
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
#include "giomm/desktopappinfo.h"
|
||||
#include "util/json.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
@ -61,15 +63,18 @@ class Task
|
||||
Gtk::Image icon_;
|
||||
Gtk::Label text_before_;
|
||||
Gtk::Label text_after_;
|
||||
bool button_visible_;
|
||||
bool ignored_;
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
|
||||
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_after_;
|
||||
|
||||
std::string format_tooltip_;
|
||||
|
||||
std::string name_;
|
||||
std::string title_;
|
||||
std::string app_id_;
|
||||
uint32_t state_ = 0;
|
||||
@ -77,6 +82,9 @@ class Task
|
||||
private:
|
||||
std::string repr() 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:
|
||||
/* Getter functions */
|
||||
@ -135,6 +143,7 @@ class Taskbar : public waybar::AModule
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
|
||||
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 wl_seat *seat_;
|
||||
@ -157,8 +166,9 @@ class Taskbar : public waybar::AModule
|
||||
bool show_output(struct wl_output *) 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::map<std::string, std::string>& app_ids_replace_map() const;
|
||||
};
|
||||
|
||||
} /* namespace waybar::modules::wlr */
|
||||
|
160
include/modules/wlr/workspace_manager.hpp
Normal file
160
include/modules/wlr/workspace_manager.hpp
Normal file
@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/image.h>
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
class WorkspaceManager;
|
||||
class WorkspaceGroup;
|
||||
|
||||
class Workspace {
|
||||
public:
|
||||
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id);
|
||||
~Workspace();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); }
|
||||
auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); }
|
||||
auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); }
|
||||
// wlr stuff
|
||||
auto handle_name(const std::string &name) -> void;
|
||||
auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void;
|
||||
auto handle_state(const std::vector<uint32_t> &state) -> void;
|
||||
auto handle_remove() -> void;
|
||||
|
||||
auto handle_done() -> void;
|
||||
auto handle_clicked(GdkEventButton *bt) -> bool;
|
||||
auto show() -> void;
|
||||
auto hide() -> void;
|
||||
auto get_button_ref() -> Gtk::Button & { return button_; }
|
||||
auto get_name() -> std::string & { return name_; }
|
||||
auto get_coords() -> std::vector<uint32_t> & { return coordinates_; }
|
||||
|
||||
enum class State {
|
||||
ACTIVE = (1 << 0),
|
||||
URGENT = (1 << 1),
|
||||
HIDDEN = (1 << 2),
|
||||
};
|
||||
|
||||
private:
|
||||
auto get_icon() -> std::string;
|
||||
|
||||
const Bar &bar_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceGroup &workspace_group_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_handle_v1 *workspace_handle_;
|
||||
uint32_t state_ = 0;
|
||||
|
||||
uint32_t id_;
|
||||
std::string name_;
|
||||
std::vector<uint32_t> coordinates_;
|
||||
static std::map<std::string, std::string> icons_map_;
|
||||
std::string format_;
|
||||
bool with_icon_ = false;
|
||||
|
||||
Gtk::Button button_;
|
||||
Gtk::Box content_;
|
||||
Gtk::Label label_;
|
||||
};
|
||||
|
||||
class WorkspaceGroup {
|
||||
public:
|
||||
WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle,
|
||||
uint32_t id);
|
||||
~WorkspaceGroup();
|
||||
auto update() -> void;
|
||||
|
||||
auto id() const -> uint32_t { return id_; }
|
||||
auto is_visible() const -> bool;
|
||||
auto remove_workspace(uint32_t id_) -> void;
|
||||
auto active_only() const -> bool;
|
||||
auto creation_delayed() const -> bool;
|
||||
auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto set_need_to_sort() -> void { need_to_sort = true; }
|
||||
auto add_button(Gtk::Button &button) -> void;
|
||||
auto remove_button(Gtk::Button &button) -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
|
||||
auto handle_remove() -> void;
|
||||
auto handle_output_enter(wl_output *output) -> void;
|
||||
auto handle_output_leave() -> void;
|
||||
auto handle_done() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
static uint32_t workspace_global_id;
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box &box_;
|
||||
const Json::Value &config_;
|
||||
WorkspaceManager &workspace_manager_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle_;
|
||||
wl_output *output_ = nullptr;
|
||||
|
||||
uint32_t id_;
|
||||
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
||||
bool need_to_sort = false;
|
||||
};
|
||||
|
||||
class WorkspaceManager : public AModule {
|
||||
public:
|
||||
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
|
||||
~WorkspaceManager() override;
|
||||
auto update() -> void override;
|
||||
|
||||
auto all_outputs() const -> bool { return all_outputs_; }
|
||||
auto active_only() const -> bool { return active_only_; }
|
||||
auto workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>;
|
||||
auto creation_delayed() const -> bool { return creation_delayed_; }
|
||||
|
||||
auto sort_workspaces() -> void;
|
||||
auto remove_workspace_group(uint32_t id_) -> void;
|
||||
|
||||
// wlr stuff
|
||||
auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void;
|
||||
auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle)
|
||||
-> void;
|
||||
auto handle_done() -> void;
|
||||
auto handle_finished() -> void;
|
||||
auto commit() -> void;
|
||||
|
||||
private:
|
||||
const waybar::Bar &bar_;
|
||||
Gtk::Box box_;
|
||||
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
|
||||
|
||||
// wlr stuff
|
||||
zext_workspace_manager_v1 *workspace_manager_ = nullptr;
|
||||
|
||||
static uint32_t group_global_id;
|
||||
|
||||
bool sort_by_name_ = true;
|
||||
bool sort_by_coordinates_ = true;
|
||||
bool all_outputs_ = false;
|
||||
bool active_only_ = false;
|
||||
bool creation_delayed_ = false;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::wlr
|
8
include/modules/wlr/workspace_manager_binding.hpp
Normal file
8
include/modules/wlr/workspace_manager_binding.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "ext-workspace-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
void add_registry_listener(void *data);
|
||||
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data);
|
||||
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data);
|
||||
zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data);
|
||||
}
|
75
include/util/SafeSignal.hpp
Normal file
75
include/util/SafeSignal.hpp
Normal file
@ -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
|
||||
|
||||
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.
|
||||
|
||||
|
92
man/waybar-inhibitor.5.scd
Normal file
92
man/waybar-inhibitor.5.scd
Normal file
@ -0,0 +1,92 @@
|
||||
waybar-inhibitor(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - inhibitor module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *inhibitor* module allows to take an inhibitor lock that logind provides.
|
||||
See *systemd-inhibit*(1) for more information.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*what*: ++
|
||||
typeof: string or array ++
|
||||
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
The format, how the state should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the current state, the corresponding icon gets selected.
|
||||
|
||||
*rotate*: ++
|
||||
typeof: integer ++
|
||||
Positive value to rotate the text label.
|
||||
|
||||
*max-length*: ++
|
||||
typeof: integer ++
|
||||
The maximum length in character the module should display.
|
||||
|
||||
*min-length*: ++
|
||||
typeof: integer ++
|
||||
The minimum length in characters the module should take up.
|
||||
|
||||
*align*: ++
|
||||
typeof: float ++
|
||||
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
||||
|
||||
*on-click*: ++
|
||||
typeof: string ++
|
||||
Command to execute when clicked on the module. A click also toggles the state
|
||||
|
||||
*on-click-middle*: ++
|
||||
typeof: string ++
|
||||
Command to execute when middle-clicked on the module using mousewheel.
|
||||
|
||||
*on-click-right*: ++
|
||||
typeof: string ++
|
||||
Command to execute when you right clicked on the module.
|
||||
|
||||
*on-update*: ++
|
||||
typeof: string ++
|
||||
Command to execute when the module is updated.
|
||||
|
||||
*on-scroll-up*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling up on the module.
|
||||
|
||||
*on-scroll-down*: ++
|
||||
typeof: string ++
|
||||
Command to execute when scrolling down on the module.
|
||||
|
||||
*smooth-scrolling-threshold*: ++
|
||||
typeof: double ++
|
||||
Threshold to be used when scrolling.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Option to disable tooltip on hover.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{status}*: status (*activated* or *deactivated*)
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"inhibitor": {
|
||||
"what": "handle-lid-switch",
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"activated": "",
|
||||
"deactivated": ""
|
||||
}
|
||||
}
|
||||
```
|
@ -21,6 +21,11 @@ Addressed by *river/tags*
|
||||
typeof: array ++
|
||||
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
|
||||
|
||||
```
|
||||
|
@ -37,6 +37,8 @@ Addressed by *sway/language*
|
||||
|
||||
*{variant}*: Variant of layout (e.g. "dvorak").
|
||||
|
||||
*{flag}*: Country flag of layout.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
|
@ -69,10 +69,6 @@ Addressed by *sway/workspaces*
|
||||
typeof: string ++
|
||||
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*: ++
|
||||
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.
|
||||
@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
|
||||
"sway/workspaces": {
|
||||
"disable-scroll": true,
|
||||
"all-outputs": true,
|
||||
"numeric-first": false,
|
||||
"format": "{name}: {icon}",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
|
@ -70,12 +70,18 @@ Addressed by *wlr/taskbar*
|
||||
|
||||
*ignore-list*: ++
|
||||
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
|
||||
|
||||
*{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.
|
||||
|
||||
*{app_id}*: The app_id (== application name) of the application.
|
||||
@ -105,7 +111,10 @@ Addressed by *wlr/taskbar*
|
||||
"on-click-middle": "close",
|
||||
"ignore-list": [
|
||||
"Alacritty"
|
||||
]
|
||||
],
|
||||
"app_ids-mapping": {
|
||||
"firefoxdeveloperedition": "firefox-developer-edition"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
87
man/waybar-wlr-workspaces.5.scd
Normal file
87
man/waybar-wlr-workspaces.5.scd
Normal file
@ -0,0 +1,87 @@
|
||||
waybar-wlr-workspaces(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - wlr workspaces module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *workspaces* module displays the currently used workspaces in wayland compositor.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
Addressed by *wlr/workspaces*
|
||||
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
default: {name} ++
|
||||
The format, how information should be displayed.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: array ++
|
||||
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
|
||||
|
||||
*sort-by-name*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Should workspaces be sorted by name.
|
||||
|
||||
*sort-by-coordinates*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
Should workspaces be sorted by coordinates.
|
||||
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first.
|
||||
If both are false - sort by id will be performed.
|
||||
|
||||
*all-outputs*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown.
|
||||
|
||||
*active-only*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
If set to true only active or urgent workspaces will be shown.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{name}*: Name of workspace assigned by compositor
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
# CLICK ACTIONS
|
||||
|
||||
*activate*: Switch to workspace.
|
||||
*close*: Close the workspace.
|
||||
|
||||
# ICONS
|
||||
|
||||
Additional to workspace name matching, the following *format-icons* can be set.
|
||||
|
||||
- *default*: Will be shown, when no string match is found.
|
||||
- *focused*: Will be shown, when workspace is focused
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
```
|
||||
"wlr/workspaces": {
|
||||
"format": "{name}: {icon}",
|
||||
"format-icons": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"focused": "",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Style
|
||||
|
||||
- *#workspaces*
|
||||
- *#workspaces button*
|
||||
- *#workspaces button.active*
|
||||
- *#workspaces button.urgent*
|
||||
- *#workspaces button.hidden*
|
@ -72,14 +72,25 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||
typeof: string ++
|
||||
Optional name added as a CSS class, for styling multiple waybars.
|
||||
|
||||
*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* ++
|
||||
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.
|
||||
|
||||
*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* ++
|
||||
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.
|
||||
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.
|
||||
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* ++
|
||||
typeof: string|array ++
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
- *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-workspaces(5)*
|
||||
- *waybar-wlr-taskbar(5)*
|
||||
- *waybar-wlr-workspaces(5)*
|
||||
- *waybar-temperature(5)*
|
||||
- *waybar-tray(5)*
|
||||
|
21
meson.build
21
meson.build
@ -86,7 +86,7 @@ wayland_cursor = dependency('wayland-cursor')
|
||||
wayland_protos = dependency('wayland-protocols')
|
||||
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
|
||||
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
|
||||
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk'))
|
||||
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
|
||||
jsoncpp = dependency('jsoncpp')
|
||||
sigcpp = dependency('sigc++-2.0')
|
||||
libepoll = dependency('epoll-shim', required: false)
|
||||
@ -150,6 +150,7 @@ src_files = files(
|
||||
'src/bar.cpp',
|
||||
'src/client.cpp',
|
||||
'src/config.cpp',
|
||||
'src/group.cpp',
|
||||
'src/util/ustring_clen.cpp'
|
||||
)
|
||||
|
||||
@ -177,6 +178,7 @@ endif
|
||||
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
|
||||
src_files += [
|
||||
'src/modules/sway/ipc/client.cpp',
|
||||
'src/modules/sway/bar.cpp',
|
||||
'src/modules/sway/mode.cpp',
|
||||
'src/modules/sway/language.cpp',
|
||||
'src/modules/sway/window.cpp',
|
||||
@ -186,6 +188,8 @@ src_files += [
|
||||
if true
|
||||
add_project_arguments('-DHAVE_WLR', language: '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
|
||||
|
||||
if true
|
||||
@ -238,6 +242,11 @@ if libsndio.found()
|
||||
src_files += 'src/modules/sndio.cpp'
|
||||
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 is_linux
|
||||
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
|
||||
@ -255,6 +264,10 @@ else
|
||||
src_files += 'src/modules/simpleclock.cpp'
|
||||
endif
|
||||
|
||||
if get_option('experimental')
|
||||
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
|
||||
endif
|
||||
|
||||
subdir('protocol')
|
||||
|
||||
executable(
|
||||
@ -334,10 +347,15 @@ if scdoc.found()
|
||||
'waybar-tray.5.scd',
|
||||
'waybar-states.5.scd',
|
||||
'waybar-wlr-taskbar.5.scd',
|
||||
'waybar-wlr-workspaces.5.scd',
|
||||
'waybar-bluetooth.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
|
||||
path = '@0@'.format(file)
|
||||
basename = path.split('/')[-1]
|
||||
@ -380,3 +398,4 @@ if clangtidy.found()
|
||||
'-p', meson.build_root()
|
||||
] + src_files)
|
||||
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('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
|
||||
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('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
|
||||
|
306
protocol/ext-workspace-unstable-v1.xml
Normal file
306
protocol/ext-workspace-unstable-v1.xml
Normal file
@ -0,0 +1,306 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_workspace_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Christopher Billington
|
||||
Copyright © 2020 Ilia Bozhinov
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zext_workspace_manager_v1" version="1">
|
||||
<description summary="list and control workspaces">
|
||||
Workspaces, also called virtual desktops, are groups of surfaces. A
|
||||
compositor with a concept of workspaces may only show some such groups of
|
||||
surfaces (those of 'active' workspaces) at a time. 'Activating' a
|
||||
workspace is a request for the compositor to display that workspace's
|
||||
surfaces as normal, whereas the compositor may hide or otherwise
|
||||
de-emphasise surfaces that are associated only with 'inactive' workspaces.
|
||||
Workspaces are grouped by which sets of outputs they correspond to, and
|
||||
may contain surfaces only from those outputs. In this way, it is possible
|
||||
for each output to have its own set of workspaces, or for all outputs (or
|
||||
any other arbitrary grouping) to share workspaces. Compositors may
|
||||
optionally conceptually arrange each group of workspaces in an
|
||||
N-dimensional grid.
|
||||
|
||||
The purpose of this protocol is to enable the creation of taskbars and
|
||||
docks by providing them with a list of workspaces and their properties,
|
||||
and allowing them to activate and deactivate workspaces.
|
||||
|
||||
After a client binds the zext_workspace_manager_v1, each workspace will be
|
||||
sent via the workspace event.
|
||||
</description>
|
||||
|
||||
<event name="workspace_group">
|
||||
<description summary="a workspace group has been created">
|
||||
This event is emitted whenever a new workspace group has been created.
|
||||
|
||||
All initial details of the workspace group (workspaces, outputs) will be
|
||||
sent immediately after this event via the corresponding events in
|
||||
zext_workspace_group_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="commit">
|
||||
<description summary="all requests about the workspaces have been sent">
|
||||
The client must send this request after it has finished sending other
|
||||
requests. The compositor must process a series of requests preceding a
|
||||
commit request atomically.
|
||||
|
||||
This allows changes to the workspace properties to be seen as atomic,
|
||||
even if they happen via multiple events, and even if they involve
|
||||
multiple zext_workspace_handle_v1 objects, for example, deactivating one
|
||||
workspace and activating another.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the workspace groups has been sent">
|
||||
This event is sent after all changes in all workspace groups have been
|
||||
sent.
|
||||
|
||||
This allows changes to one or more zext_workspace_group_handle_v1
|
||||
properties to be seen as atomic, even if they happen via multiple
|
||||
events. In particular, an output moving from one workspace group to
|
||||
another sends an output_enter event and an output_leave event to the two
|
||||
zext_workspace_group_handle_v1 objects in question. The compositor sends
|
||||
the done event only after updating the output information in both
|
||||
workspace groups.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the workspace_manager">
|
||||
This event indicates that the compositor is done sending events to the
|
||||
zext_workspace_manager_v1. The server will destroy the object
|
||||
immediately after sending this request, so it will become invalid and
|
||||
the client should free any resources associated with it.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
Indicates the client no longer wishes to receive events for new
|
||||
workspace groups. However the compositor may emit further workspace
|
||||
events, until the finished event is emitted.
|
||||
|
||||
The client must not send any more requests after this one.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_group_handle_v1" version="1">
|
||||
<description summary="a workspace group assigned to a set of outputs">
|
||||
A zext_workspace_group_handle_v1 object represents a a workspace group
|
||||
that is assigned a set of outputs and contains a number of workspaces.
|
||||
|
||||
The set of outputs assigned to the workspace group is conveyed to the client via
|
||||
output_enter and output_leave events, and its workspaces are conveyed with
|
||||
workspace events.
|
||||
|
||||
For example, a compositor which has a set of workspaces for each output may
|
||||
advertise a workspace group (and its workspaces) per output, whereas a compositor
|
||||
where a workspace spans all outputs may advertise a single workspace group for all
|
||||
outputs.
|
||||
</description>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="output assigned to workspace group">
|
||||
This event is emitted whenever an output is assigned to the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="output removed from workspace group">
|
||||
This event is emitted whenever an output is removed from the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="workspace">
|
||||
<description summary="workspace added to workspace group">
|
||||
This event is emitted whenever a new workspace has been created.
|
||||
|
||||
All initial details of the workspace (name, coordinates, state) will
|
||||
be sent immediately after this event via the corresponding events in
|
||||
zext_workspace_handle_v1.
|
||||
</description>
|
||||
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace group has been destroyed">
|
||||
This event means the zext_workspace_group_handle_v1 has been destroyed.
|
||||
It is guaranteed there won't be any more events for this
|
||||
zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
|
||||
inert so any requests will be ignored except the destroy request.
|
||||
|
||||
The compositor must remove all workspaces belonging to a workspace group
|
||||
before removing the workspace group.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_workspace">
|
||||
<description summary="create a new workspace">
|
||||
Request that the compositor create a new workspace with the given name.
|
||||
|
||||
There is no guarantee that the compositor will create a new workspace,
|
||||
or that the created workspace will have the provided name.
|
||||
</description>
|
||||
<arg name="workspace" type="string"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_handle_v1" version="1">
|
||||
<description summary="a workspace handing a group of surfaces">
|
||||
A zext_workspace_handle_v1 object represents a a workspace that handles a
|
||||
group of surfaces.
|
||||
|
||||
Each workspace has a name, conveyed to the client with the name event; a
|
||||
list of states, conveyed to the client with the state event; and
|
||||
optionally a set of coordinates, conveyed to the client with the
|
||||
coordinates event. The client may request that the compositor activate or
|
||||
deactivate the workspace.
|
||||
|
||||
Each workspace can belong to only a single workspace group.
|
||||
Depepending on the compositor policy, there might be workspaces with
|
||||
the same name in different workspace groups, but these workspaces are still
|
||||
separate (e.g. one of them might be active while the other is not).
|
||||
</description>
|
||||
|
||||
<event name="name">
|
||||
<description summary="workspace name changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and whenever the name of the workspace changes.
|
||||
</description>
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="coordinates">
|
||||
<description summary="workspace coordinates changed">
|
||||
This event is used to organize workspaces into an N-dimensional grid
|
||||
within a workspace group, and if supported, is emitted immediately after
|
||||
the zext_workspace_handle_v1 is created and whenever the coordinates of
|
||||
the workspace change. Compositors may not send this event if they do not
|
||||
conceptually arrange workspaces in this way. If compositors simply
|
||||
number workspaces, without any geometric interpretation, they may send
|
||||
1D coordinates, which clients should not interpret as implying any
|
||||
geometry. Sending an empty array means that the compositor no longer
|
||||
orders the workspace geometrically.
|
||||
|
||||
Coordinates have an arbitrary number of dimensions N with an uint32
|
||||
position along each dimension. By convention if N > 1, the first
|
||||
dimension is X, the second Y, the third Z, and so on. The compositor may
|
||||
chose to utilize these events for a more novel workspace layout
|
||||
convention, however. No guarantee is made about the grid being filled or
|
||||
bounded; there may be a workspace at coordinate 1 and another at
|
||||
coordinate 1000 and none in between. Within a workspace group, however,
|
||||
workspaces must have unique coordinates of equal dimensionality.
|
||||
</description>
|
||||
<arg name="coordinates" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the state of the workspace changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and each time the workspace state changes, either because of a
|
||||
compositor action or because of a request in this protocol.
|
||||
</description>
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the workspace">
|
||||
The different states that a workspace can have.
|
||||
</description>
|
||||
|
||||
<entry name="active" value="0" summary="the workspace is active"/>
|
||||
<entry name="urgent" value="1" summary="the workspace requests attention"/>
|
||||
<entry name="hidden" value="2">
|
||||
<description summary="the workspace is not visible">
|
||||
The workspace is not visible in its workspace group, and clients
|
||||
attempting to visualize the compositor workspace state should not
|
||||
display such workspaces.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace has been destroyed">
|
||||
This event means the zext_workspace_handle_v1 has been destroyed. It is
|
||||
guaranteed there won't be any more events for this
|
||||
zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
|
||||
any requests will be ignored except the destroy request.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
Destroys the zext_workspace_handle_v1 object.
|
||||
|
||||
This request should be called either when the client does not want to
|
||||
use the workspace object any more or after the remove event to finalize
|
||||
the destruction of the object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be activated.
|
||||
|
||||
There is no guarantee the workspace will be actually activated, and
|
||||
behaviour may be compositor-dependent. For example, activating a
|
||||
workspace may or may not deactivate all other workspaces in the same
|
||||
group.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="deactivate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be deactivated.
|
||||
|
||||
There is no guarantee the workspace will be actually deactivated.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="remove">
|
||||
<description summary="remove the workspace">
|
||||
Request that this workspace be removed.
|
||||
|
||||
There is no guarantee the workspace will be actually removed.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
@ -27,7 +27,9 @@ client_protocols = [
|
||||
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
|
||||
['wlr-layer-shell-unstable-v1.xml'],
|
||||
['wlr-foreign-toplevel-management-unstable-v1.xml'],
|
||||
['ext-workspace-unstable-v1.xml'],
|
||||
['river-status-unstable-v1.xml'],
|
||||
['river-control-unstable-v1.xml'],
|
||||
]
|
||||
|
||||
client_protos_src = []
|
||||
|
85
protocol/river-control-unstable-v1.xml
Normal file
85
protocol/river-control-unstable-v1.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="river_control_unstable_v1">
|
||||
<copyright>
|
||||
Copyright 2020 The River Developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zriver_control_v1" version="1">
|
||||
<description summary="run compositor commands">
|
||||
This interface allows clients to run compositor commands and receive a
|
||||
success/failure response with output or a failure message respectively.
|
||||
|
||||
Each command is built up in a series of add_argument requests and
|
||||
executed with a run_command request. The first argument is the command
|
||||
to be run.
|
||||
|
||||
A complete list of commands should be made available in the man page of
|
||||
the compositor.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the river_control object">
|
||||
This request indicates that the client will not use the
|
||||
river_control object any more. Objects that have been created
|
||||
through this instance are not affected.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add_argument">
|
||||
<description summary="add an argument to the current command">
|
||||
Arguments are stored by the server in the order they were sent until
|
||||
the run_command request is made.
|
||||
</description>
|
||||
<arg name="argument" type="string" summary="the argument to add"/>
|
||||
</request>
|
||||
|
||||
<request name="run_command">
|
||||
<description summary="run the current command">
|
||||
Execute the command built up using the add_argument request for the
|
||||
given seat.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
|
||||
summary="callback object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zriver_command_callback_v1" version="1">
|
||||
<description summary="callback object">
|
||||
This object is created by the run_command request. Exactly one of the
|
||||
success or failure events will be sent. This object will be destroyed
|
||||
by the compositor after one of the events is sent.
|
||||
</description>
|
||||
|
||||
<event name="success">
|
||||
<description summary="command successful">
|
||||
Sent when the command has been successfully received and executed by
|
||||
the compositor. Some commands may produce output, in which case the
|
||||
output argument will be a non-empty string.
|
||||
</description>
|
||||
<arg name="output" type="string" summary="the output of the command"/>
|
||||
</event>
|
||||
|
||||
<event name="failure">
|
||||
<description summary="command failed">
|
||||
Sent when the command could not be carried out. This could be due to
|
||||
sending a non-existent command, no command, not enough arguments, too
|
||||
many arguments, invalid arguments, etc.
|
||||
</description>
|
||||
<arg name="failure_message" type="string"
|
||||
summary="a message explaining why failure occurred"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
@ -110,6 +110,7 @@ def main():
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
for player in manager.props.player_names:
|
||||
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 "client.hpp"
|
||||
#include "factory.hpp"
|
||||
#include "group.hpp"
|
||||
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
|
||||
#ifdef HAVE_SWAY
|
||||
#include "modules/sway/bar.hpp"
|
||||
#endif
|
||||
|
||||
namespace waybar {
|
||||
static constexpr const char* MIN_HEIGHT_MSG =
|
||||
"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 =
|
||||
"{} 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
|
||||
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
|
||||
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),
|
||||
config(w_config),
|
||||
window{Gtk::WindowType::WINDOW_TOPLEVEL},
|
||||
layer_{bar_layer::BOTTOM},
|
||||
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
center_(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["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();
|
||||
|
||||
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_->setLayer(layer_);
|
||||
surface_impl_->setExclusiveZone(exclusive);
|
||||
surface_impl_->setMargins(margins_);
|
||||
surface_impl_->setPassThrough(passthrough);
|
||||
surface_impl_->setPosition(position);
|
||||
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));
|
||||
|
||||
#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();
|
||||
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*) {
|
||||
/*
|
||||
* 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) {
|
||||
visible = value;
|
||||
if (!visible) {
|
||||
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();
|
||||
setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
|
||||
}
|
||||
|
||||
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) {
|
||||
for (auto& module : modules_left_) {
|
||||
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_) {
|
||||
for (auto& module : modules_all_) {
|
||||
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
|
||||
if (custom != nullptr) {
|
||||
custom->refresh(signal);
|
||||
@ -614,19 +719,36 @@ void waybar::Bar::handleSignal(int signal) {
|
||||
}
|
||||
}
|
||||
|
||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
||||
if (config[pos].isArray()) {
|
||||
for (const auto& name : config[pos]) {
|
||||
void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
|
||||
auto module_list = group ? config[pos]["modules"] : config[pos];
|
||||
if (module_list.isArray()) {
|
||||
for (const auto& name : module_list) {
|
||||
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") {
|
||||
modules_left_.emplace_back(module);
|
||||
modules_left_.emplace_back(module_sp);
|
||||
}
|
||||
if (pos == "modules-center") {
|
||||
modules_center_.emplace_back(module);
|
||||
modules_center_.emplace_back(module_sp);
|
||||
}
|
||||
if (pos == "modules-right") {
|
||||
modules_right_.emplace_back(module);
|
||||
modules_right_.emplace_back(module_sp);
|
||||
}
|
||||
}
|
||||
module->dp.connect([module, &name] {
|
||||
try {
|
||||
@ -645,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
|
||||
auto waybar::Bar::setupWidgets() -> void {
|
||||
window.add(box_);
|
||||
box_.pack_start(left_, false, false);
|
||||
if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
|
||||
box_.set_center_widget(center_);
|
||||
} else {
|
||||
box_.pack_start(center_, true, false);
|
||||
}
|
||||
box_.pack_end(right_, false, false);
|
||||
|
||||
// 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;
|
||||
std::string config_opt;
|
||||
std::string style_opt;
|
||||
std::string bar_id;
|
||||
std::string log_level;
|
||||
auto cli = clara::detail::Help(show_help) |
|
||||
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") {
|
||||
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
|
||||
#ifdef HAVE_RIVER
|
||||
if (ref == "river/tags") {
|
||||
@ -89,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
if (ref == "sndio") {
|
||||
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
|
||||
if (ref == "temperature") {
|
||||
return new waybar::modules::Temperature(id, config_[name]);
|
||||
|
19
src/group.cpp
Normal file
19
src/group.cpp
Normal file
@ -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;
|
||||
|
||||
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()) {
|
||||
time_zone_idx_ = 0;
|
||||
setTimeZone(config_["timezones"][time_zone_idx_]);
|
||||
} else {
|
||||
setTimeZone(config_["timezone"]);
|
||||
for (const auto& zone_name: config_["timezones"]) {
|
||||
if (!zone_name.isString() || zone_name.asString().empty()) {
|
||||
time_zones_.push_back(nullptr);
|
||||
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.");
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
locale_ = std::locale(config_["locale"].asString());
|
||||
} else {
|
||||
@ -40,53 +74,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
|
||||
};
|
||||
}
|
||||
|
||||
auto waybar::modules::Clock::update() -> void {
|
||||
if (!fixed_time_zone_) {
|
||||
// Time zone can change. Be sure to pick that.
|
||||
time_zone_ = date::current_zone();
|
||||
const date::time_zone* waybar::modules::Clock::current_timezone() {
|
||||
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : 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();
|
||||
waybar_time wtime = {locale_,
|
||||
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))};
|
||||
|
||||
std::string text;
|
||||
if (!fixed_time_zone_) {
|
||||
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
|
||||
std::string text = "";
|
||||
if (!is_timezone_fixed()) {
|
||||
// As date dep is not fully compatible, prefer fmt
|
||||
tzset();
|
||||
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
|
||||
text = fmt::format(format_, localtime);
|
||||
label_.set_markup(text);
|
||||
} else {
|
||||
text = fmt::format(format_, wtime);
|
||||
label_.set_markup(text);
|
||||
}
|
||||
label_.set_markup(text);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
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_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar));
|
||||
label_.set_tooltip_markup(tooltip_text);
|
||||
} else {
|
||||
text = fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines));
|
||||
}
|
||||
}
|
||||
|
||||
label_.set_tooltip_markup(text);
|
||||
}
|
||||
}
|
||||
// Call parent 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) {
|
||||
// defer to user commands if set
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
|
||||
if (time_zones_.size() == 1) {
|
||||
return true;
|
||||
}
|
||||
auto nr_zones = config_["timezones"].size();
|
||||
|
||||
auto nr_zones = time_zones_.size();
|
||||
if (dir == SCROLL_DIR::UP) {
|
||||
size_t new_idx = time_zone_idx_ + 1;
|
||||
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||
size_t new_idx = current_time_zone_idx_ + 1;
|
||||
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
|
||||
} 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();
|
||||
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 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 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 state = getState(percentage_used);
|
||||
|
175
src/modules/inhibitor.cpp
Normal file
175
src/modules/inhibitor.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
#include "modules/inhibitor.hpp"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
|
||||
|
||||
auto dbus() -> DBus {
|
||||
GError *error = nullptr;
|
||||
GDBusConnection* connection =
|
||||
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
||||
|
||||
if (error) {
|
||||
spdlog::error("g_bus_get_sync() failed: {}", error->message);
|
||||
g_error_free(error);
|
||||
connection = nullptr;
|
||||
}
|
||||
|
||||
auto destructor = [](GDBusConnection* connection) {
|
||||
GError *error = nullptr;
|
||||
g_dbus_connection_close_sync(connection, nullptr, &error);
|
||||
if (error) {
|
||||
spdlog::error(
|
||||
"g_bus_connection_close_sync failed(): {}",
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
};
|
||||
|
||||
return DBus{connection, destructor};
|
||||
}
|
||||
|
||||
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
|
||||
GError *error = nullptr;
|
||||
GUnixFDList* fd_list;
|
||||
int handle;
|
||||
|
||||
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
|
||||
"org.freedesktop.login1",
|
||||
"/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager",
|
||||
"Inhibit",
|
||||
g_variant_new(
|
||||
"(ssss)",
|
||||
inhibitors.c_str(),
|
||||
"waybar",
|
||||
"Asked by user",
|
||||
"block"),
|
||||
G_VARIANT_TYPE("(h)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
nullptr,
|
||||
&fd_list,
|
||||
nullptr,
|
||||
&error);
|
||||
if (error) {
|
||||
spdlog::error(
|
||||
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
handle = -1;
|
||||
} else {
|
||||
gint index;
|
||||
g_variant_get(reply, "(h)", &index);
|
||||
g_variant_unref(reply);
|
||||
handle = g_unix_fd_list_get(fd_list, index, nullptr);
|
||||
g_object_unref(fd_list);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
|
||||
static const auto inhibitors = std::array{
|
||||
"idle",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"handle-power-key",
|
||||
"handle-suspend-key",
|
||||
"handle-hibernate-key",
|
||||
"handle-lid-switch"
|
||||
};
|
||||
|
||||
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
|
||||
== inhibitors.end()) {
|
||||
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
|
||||
}
|
||||
|
||||
return inhibitor;
|
||||
}
|
||||
|
||||
auto getInhibitors(const Json::Value& config) -> std::string {
|
||||
std::string inhibitors = "idle";
|
||||
|
||||
if (config["what"].empty()) {
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
if (config["what"].isString()) {
|
||||
return checkInhibitor(config["what"].asString());
|
||||
}
|
||||
|
||||
if (config["what"].isArray()) {
|
||||
inhibitors = checkInhibitor(config["what"][0].asString());
|
||||
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
|
||||
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
|
||||
}
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
return inhibitors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
|
||||
const Json::Value& config)
|
||||
: ALabel(config, "inhibitor", id, "{status}", true),
|
||||
dbus_(::dbus()),
|
||||
inhibitors_(::getInhibitors(config)) {
|
||||
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
event_box_.signal_button_press_event().connect(
|
||||
sigc::mem_fun(*this, &Inhibitor::handleToggle));
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Inhibitor::~Inhibitor() {
|
||||
if (handle_ != -1) {
|
||||
::close(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
auto Inhibitor::activated() -> bool {
|
||||
return handle_ != -1;
|
||||
}
|
||||
|
||||
auto Inhibitor::update() -> void {
|
||||
std::string status_text = activated() ? "activated" : "deactivated";
|
||||
|
||||
label_.get_style_context()->remove_class(
|
||||
activated() ? "deactivated" : "activated");
|
||||
label_.set_markup(
|
||||
fmt::format(format_, fmt::arg("status", status_text),
|
||||
fmt::arg("icon", getIcon(0, status_text))));
|
||||
label_.get_style_context()->add_class(status_text);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(status_text);
|
||||
}
|
||||
|
||||
return ALabel::update();
|
||||
}
|
||||
|
||||
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
|
||||
if (e->button == 1) {
|
||||
if (activated()) {
|
||||
::close(handle_);
|
||||
handle_ = -1;
|
||||
} else {
|
||||
handle_ = ::getLocks(dbus_, inhibitors_);
|
||||
if (handle_ == -1) {
|
||||
spdlog::error("cannot get inhibitor locks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ALabel::handleToggle(e);
|
||||
}
|
||||
|
||||
} // waybar::modules
|
@ -131,6 +131,9 @@ void waybar::modules::MPD::setLabel() {
|
||||
date = getTag(MPD_TAG_DATE);
|
||||
song_pos = mpd_status_get_song_pos(status_.get());
|
||||
volume = mpd_status_get_volume(status_.get());
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
queue_length = mpd_status_get_queue_length(status_.get());
|
||||
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_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 <sys/eventfd.h>
|
||||
#include <linux/if.h>
|
||||
#include <fstream>
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
|
||||
#include "modules/network.hpp"
|
||||
#include "util/format.hpp"
|
||||
#ifdef WANT_RFKILL
|
||||
#include "util/rfkill.hpp"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
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}";
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
|
||||
ifid_(-1),
|
||||
@ -98,7 +88,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
|
||||
#ifdef WANT_RFKILL
|
||||
rfkill_{RFKILL_TYPE_WLAN},
|
||||
#endif
|
||||
frequency_(0) {
|
||||
frequency_(0.0) {
|
||||
|
||||
// Start with some "text" in the module's label_, update() will then
|
||||
// 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.
|
||||
label_.set_markup("<s></s>");
|
||||
|
||||
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
|
||||
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
|
||||
if (down_octets) {
|
||||
bandwidth_down_total_ = *down_octets;
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
if (bandwidth.has_value()) {
|
||||
bandwidth_down_total_ = (*bandwidth).first;
|
||||
bandwidth_up_total_ = (*bandwidth).second;
|
||||
} else {
|
||||
bandwidth_down_total_ = 0;
|
||||
}
|
||||
|
||||
if (up_octets) {
|
||||
bandwidth_up_total_ = *up_octets;
|
||||
} else {
|
||||
bandwidth_up_total_ = 0;
|
||||
}
|
||||
|
||||
@ -303,20 +288,21 @@ const std::string waybar::modules::Network::getNetworkState() const {
|
||||
auto waybar::modules::Network::update() -> void {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
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;
|
||||
if (down_octets) {
|
||||
bandwidth_down = *down_octets - bandwidth_down_total_;
|
||||
bandwidth_down_total_ = *down_octets;
|
||||
auto bandwidth = readBandwidthUsage();
|
||||
auto bandwidth_down = 0ull;
|
||||
auto bandwidth_up = 0ull;
|
||||
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_) {
|
||||
auto state = getNetworkState();
|
||||
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("signaldBm", signal_strength_dbm_),
|
||||
fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_),
|
||||
fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_),
|
||||
fmt::arg("ipaddr", ipaddr_),
|
||||
fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_),
|
||||
fmt::arg("frequency", frequency_),
|
||||
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 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("signaldBm", signal_strength_dbm_),
|
||||
fmt::arg("signalStrength", signal_strength_),
|
||||
fmt::arg("signalStrengthApp", signal_strength_app_),
|
||||
fmt::arg("ifname", ifname_),
|
||||
fmt::arg("netmask", netmask_),
|
||||
fmt::arg("ipaddr", ipaddr_),
|
||||
fmt::arg("gwaddr", gwaddr_),
|
||||
fmt::arg("cidr", cidr_),
|
||||
fmt::arg("frequency", frequency_),
|
||||
fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
|
||||
fmt::arg("icon", getIcon(signal_strength_, state_)),
|
||||
fmt::arg("bandwidthDownBits",
|
||||
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
|
||||
@ -417,7 +405,8 @@ void waybar::modules::Network::clearIface() {
|
||||
cidr_ = 0;
|
||||
signal_strength_dbm_ = 0;
|
||||
signal_strength_ = 0;
|
||||
frequency_ = 0;
|
||||
signal_strength_app_.clear();
|
||||
frequency_ = 0.0;
|
||||
}
|
||||
|
||||
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->signal_strength_dbm_ = 0;
|
||||
net->signal_strength_ = 0;
|
||||
net->frequency_ = 0;
|
||||
net->signal_strength_app_.clear();
|
||||
net->frequency_ = 0.0;
|
||||
}
|
||||
}
|
||||
net->carrier_ = carrier.value();
|
||||
@ -802,13 +792,30 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) {
|
||||
if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
|
||||
// signalstrength in dBm from mBm
|
||||
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.
|
||||
const int hardwareMax = -20;
|
||||
// If a signal is too strong, it can overwhelm receiving circuity that is designed
|
||||
// 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 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);
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
|
||||
// in MHz
|
||||
frequency_ = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
|
||||
// in GHz
|
||||
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) {
|
||||
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;
|
||||
pa_volume_t change = volume_tick;
|
||||
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 {
|
||||
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::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||
for (auto const &port : ports) {
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
#include "client.hpp"
|
||||
#include "modules/river/tags.hpp"
|
||||
#include "river-status-unstable-v1-client-protocol.h"
|
||||
#include "xdg-output-unstable-v1-client-protocol.h"
|
||||
|
||||
namespace waybar::modules::river {
|
||||
@ -33,6 +32,23 @@ static const zriver_output_status_v1_listener output_status_listener_impl{
|
||||
.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,
|
||||
const char *interface, uint32_t version) {
|
||||
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 *>(
|
||||
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) {
|
||||
/* Ignore event */
|
||||
}
|
||||
|
||||
|
||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
|
||||
.global_remove = handle_global_remove};
|
||||
|
||||
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
|
||||
: waybar::AModule(config, "tags", id, false, false),
|
||||
status_manager_{nullptr},
|
||||
control_{nullptr},
|
||||
seat_{nullptr},
|
||||
bar_(bar),
|
||||
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
output_status_{nullptr} {
|
||||
@ -68,6 +99,14 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
|
||||
return;
|
||||
}
|
||||
|
||||
if (!control_) {
|
||||
spdlog::error("river_control_v1 not advertised");
|
||||
}
|
||||
|
||||
if (!seat_) {
|
||||
spdlog::error("wl_seat not advertised");
|
||||
}
|
||||
|
||||
box_.set_name("tags");
|
||||
if (!id.empty()) {
|
||||
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) {
|
||||
Gtk::Button &button = buttons_.emplace_back(tag_label);
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
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();
|
||||
i <<= 1;
|
||||
}
|
||||
|
||||
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
|
||||
@ -107,6 +152,31 @@ Tags::~Tags() {
|
||||
if (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) {
|
||||
|
107
src/modules/sway/bar.cpp
Normal file
107
src/modules/sway/bar.cpp
Normal file
@ -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("shortDescription", layout_.short_description),
|
||||
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);
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_ != "") {
|
||||
@ -107,7 +108,8 @@ auto Language::update() -> void {
|
||||
fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("shortDescription", layout_.short_description),
|
||||
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);
|
||||
} else {
|
||||
label_.set_tooltip_markup(display_layout);
|
||||
@ -212,4 +214,15 @@ Language::XKBContext::~XKBContext() {
|
||||
rxkb_context_unref(context_);
|
||||
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
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||
Json::Value v;
|
||||
v["name"] = p_w_name;
|
||||
v["target_output"] = bar_.output->name;
|
||||
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||
workspaces_.emplace_back(std::move(v));
|
||||
break;
|
||||
}
|
||||
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
||||
Json::Value v;
|
||||
v["name"] = p_w_name;
|
||||
v["target_output"] = "";
|
||||
v["num"] = convertWorkspaceNameToNum(p_w_name);
|
||||
workspaces_.emplace_back(std::move(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// config option to sort numeric workspace names before others
|
||||
bool config_numeric_first = config_["numeric-first"].asBool();
|
||||
|
||||
// sway has a defined ordering of workspaces that should be preserved in
|
||||
// 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(),
|
||||
workspaces_.end(),
|
||||
[config_numeric_first](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.
|
||||
[](const Json::Value &lhs, const Json::Value &rhs) {
|
||||
auto lname = lhs["name"].asString();
|
||||
auto rname = rhs["name"].asString();
|
||||
int l = convertWorkspaceNameToNum(lname);
|
||||
int r = convertWorkspaceNameToNum(rname);
|
||||
int l = lhs["sort"].asInt();
|
||||
int r = rhs["sort"].asInt();
|
||||
|
||||
if (l == r) {
|
||||
// in case both integers are the same, lexicographical
|
||||
// sort. This also covers the case when both don't have a
|
||||
// number (i.e., l == r == -1).
|
||||
// In case both integers are the same, lexicographical
|
||||
// sort. The code above already ensure that this will only
|
||||
// happend in case of explicitly numbered workspaces.
|
||||
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;
|
||||
});
|
||||
|
||||
}
|
||||
dp.emit();
|
||||
} catch (const std::exception &e) {
|
||||
|
@ -11,7 +11,9 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <gdkmm/monitor.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 std::string get_from_desktop_app_info(const std::string &app_id)
|
||||
static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
|
||||
{
|
||||
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"
|
||||
};
|
||||
|
||||
Glib::RefPtr<Gio::DesktopAppInfo> app_info;
|
||||
|
||||
for (auto& prefix : prefixes)
|
||||
for (auto& folder : app_folders)
|
||||
for (auto& suffix : suffixes)
|
||||
if (!app_info)
|
||||
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
|
||||
if (app_info && app_info->get_icon())
|
||||
return app_info->get_icon()->to_string();
|
||||
|
||||
return "";
|
||||
for (auto& prefix : prefixes) {
|
||||
for (auto& folder : app_folders) {
|
||||
for (auto& suffix : suffixes) {
|
||||
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
|
||||
if (!app_info_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */
|
||||
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 "";
|
||||
return app_info_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
|
||||
static std::string get_from_desktop_app_info_search(const std::string &app_id)
|
||||
return {};
|
||||
}
|
||||
|
||||
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 = "";
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
const std::string &app_id_list, int size)
|
||||
{
|
||||
void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
|
||||
std::string app_id;
|
||||
std::istringstream stream(app_id_list);
|
||||
bool found = false;
|
||||
|
||||
/* 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 */
|
||||
while (stream >> 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_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lower_app_id = app_id;
|
||||
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
|
||||
[](char c){ return std::tolower(c); });
|
||||
app_info_ = get_desktop_app_info(lower_app_id);
|
||||
if (app_info_) {
|
||||
return;
|
||||
}
|
||||
|
||||
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())
|
||||
icon_name = get_from_icon_theme(icon_theme, lower_app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_icon_theme(icon_theme, app_name);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(lower_app_id);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info(app_name);
|
||||
if (icon_name.empty())
|
||||
icon_name = get_from_desktop_app_info_search(app_id);
|
||||
start = app_id.find("-");
|
||||
app_name = app_id.substr(0, start);
|
||||
app_info_ = get_desktop_app_info(app_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (icon_name.empty())
|
||||
icon_name = "unknown";
|
||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
|
||||
const std::string &app_id)
|
||||
{
|
||||
if (icon_theme->lookup_icon(app_id, 24))
|
||||
return app_id;
|
||||
|
||||
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;
|
||||
|
||||
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(...) {
|
||||
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = load_icon_from_file(icon_name, size);
|
||||
if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
|
||||
pixbuf = load_icon_from_file(ret_icon_name, size);
|
||||
else
|
||||
pixbuf = {};
|
||||
}
|
||||
|
||||
if (pixbuf) {
|
||||
image.set(pixbuf);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return found;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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) :
|
||||
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
|
||||
id_{global_id++},
|
||||
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
|
||||
button_visible_{false}, ignored_{false}
|
||||
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
|
||||
{
|
||||
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();
|
||||
button_.add(content_);
|
||||
|
||||
with_icon_ = false;
|
||||
format_before_.clear();
|
||||
format_after_.clear();
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
/* The user defined a format string, use it */
|
||||
auto format = config_["format"].asString();
|
||||
if (format.find("{name}") != std::string::npos) {
|
||||
with_name_ = true;
|
||||
}
|
||||
|
||||
auto icon_pos = format.find("{icon}");
|
||||
if (icon_pos == 0) {
|
||||
@ -377,13 +394,12 @@ std::string Task::state_string(bool shortened) const
|
||||
void Task::handle_title(const char *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)) {
|
||||
if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
|
||||
ignored_ = true;
|
||||
if (button_visible_) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
bool found = false;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@ -418,13 +455,13 @@ void Task::handle_app_id(const char *app_id)
|
||||
|
||||
void Task::handle_output_enter(struct wl_output *output)
|
||||
{
|
||||
spdlog::debug("{} entered output {}", repr(), (void*)output);
|
||||
|
||||
if (ignored_) {
|
||||
spdlog::debug("{} is ignored", repr());
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("{} entered output {}", repr(), (void*)output);
|
||||
|
||||
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
|
||||
/* The task entered the output of the current bar make the button visible */
|
||||
tbar_->add_button(button_);
|
||||
@ -560,14 +597,17 @@ void Task::update()
|
||||
{
|
||||
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
|
||||
std::string title = title_;
|
||||
std::string name = name_;
|
||||
std::string app_id = app_id_;
|
||||
if (markup) {
|
||||
title = Glib::Markup::escape_text(title);
|
||||
name = Glib::Markup::escape_text(name);
|
||||
app_id = Glib::Markup::escape_text(app_id);
|
||||
}
|
||||
if (!format_before_.empty()) {
|
||||
auto txt = fmt::format(format_before_,
|
||||
fmt::arg("title", title),
|
||||
fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id),
|
||||
fmt::arg("state", state_string()),
|
||||
fmt::arg("short_state", state_string(true))
|
||||
@ -581,6 +621,7 @@ void Task::update()
|
||||
if (!format_after_.empty()) {
|
||||
auto txt = fmt::format(format_after_,
|
||||
fmt::arg("title", title),
|
||||
fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id),
|
||||
fmt::arg("state", state_string()),
|
||||
fmt::arg("short_state", state_string(true))
|
||||
@ -595,6 +636,7 @@ void Task::update()
|
||||
if (!format_tooltip_.empty()) {
|
||||
auto txt = fmt::format(format_tooltip_,
|
||||
fmt::arg("title", title),
|
||||
fmt::arg("name", name),
|
||||
fmt::arg("app_id", app_id),
|
||||
fmt::arg("state", state_string()),
|
||||
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());
|
||||
}
|
||||
|
||||
@ -853,10 +904,10 @@ bool Taskbar::all_outputs() const
|
||||
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
|
||||
}
|
||||
|
||||
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const
|
||||
{
|
||||
return icon_themes_;
|
||||
}
|
||||
const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
|
||||
|
||||
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 */
|
||||
|
472
src/modules/wlr/workspace_manager.cpp
Normal file
472
src/modules/wlr/workspace_manager.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
|
||||
#include <gdk/gdkwayland.h>
|
||||
#include <gtkmm.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "gtkmm/widget.h"
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
uint32_t WorkspaceGroup::workspace_global_id = 0;
|
||||
uint32_t WorkspaceManager::group_global_id = 0;
|
||||
std::map<std::string, std::string> Workspace::icons_map_;
|
||||
|
||||
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
|
||||
const Json::Value &config)
|
||||
: waybar::AModule(config, "workspaces", id, false, false),
|
||||
bar_(bar),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
auto config_sort_by_name = config_["sort-by-name"];
|
||||
if (config_sort_by_name.isBool()) {
|
||||
sort_by_name_ = config_sort_by_name.asBool();
|
||||
}
|
||||
|
||||
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
|
||||
if (config_sort_by_coordinates.isBool()) {
|
||||
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
|
||||
}
|
||||
|
||||
auto config_all_outputs = config_["all-outputs"];
|
||||
if (config_all_outputs.isBool()) {
|
||||
all_outputs_ = config_all_outputs.asBool();
|
||||
}
|
||||
|
||||
auto config_active_only = config_["active-only"];
|
||||
if (config_active_only.isBool()) {
|
||||
active_only_ = config_active_only.asBool();
|
||||
creation_delayed_ = active_only_;
|
||||
}
|
||||
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
add_registry_listener(this);
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::workspace_comparator() const
|
||||
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
|
||||
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
|
||||
auto is_name_less = lhs->get_name() < rhs->get_name();
|
||||
auto is_name_eq = lhs->get_name() == rhs->get_name();
|
||||
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
|
||||
if (sort_by_name_) {
|
||||
if (sort_by_coordinates_) {
|
||||
return is_name_eq ? is_coords_less : is_name_less;
|
||||
} else {
|
||||
return is_name_less;
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_by_coordinates_) {
|
||||
return is_coords_less;
|
||||
}
|
||||
|
||||
return lhs->id() < rhs->id();
|
||||
};
|
||||
}
|
||||
|
||||
auto WorkspaceManager::sort_workspaces() -> void {
|
||||
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
|
||||
for (auto &group : groups_) {
|
||||
auto &group_workspaces = group->workspaces();
|
||||
all_workspaces.reserve(all_workspaces.size() +
|
||||
std::distance(group_workspaces.begin(), group_workspaces.end()));
|
||||
if (!active_only()) {
|
||||
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &workspace : group_workspaces) {
|
||||
if (!workspace->is_active()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
all_workspaces.push_back(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
|
||||
for (size_t i = 0; i < all_workspaces.size(); ++i) {
|
||||
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
|
||||
-> void {
|
||||
if (workspace_manager_) {
|
||||
spdlog::warn("Register workspace manager again although already registered!");
|
||||
return;
|
||||
}
|
||||
if (version != 1) {
|
||||
spdlog::warn("Using different workspace manager protocol version: {}", version);
|
||||
}
|
||||
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_workspace_group_create(
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
|
||||
auto new_id = ++group_global_id;
|
||||
groups_.push_back(
|
||||
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
|
||||
spdlog::debug("Workspace group {} created", new_id);
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_finished() -> void {
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceManager::handle_done() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->handle_done();
|
||||
}
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto WorkspaceManager::update() -> void {
|
||||
for (auto &group : groups_) {
|
||||
group->update();
|
||||
}
|
||||
if (creation_delayed()) {
|
||||
creation_delayed_ = false;
|
||||
sort_workspaces();
|
||||
}
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
WorkspaceManager::~WorkspaceManager() {
|
||||
if (!workspace_manager_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_manager_v1_destroy(workspace_manager_);
|
||||
workspace_manager_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
|
||||
auto it = std::find_if(groups_.begin(),
|
||||
groups_.end(),
|
||||
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
|
||||
|
||||
if (it == groups_.end()) {
|
||||
spdlog::warn("Can't find group with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
groups_.erase(it);
|
||||
}
|
||||
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
|
||||
|
||||
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
|
||||
WorkspaceManager &manager,
|
||||
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
|
||||
: bar_(bar),
|
||||
box_(box),
|
||||
config_(config),
|
||||
workspace_manager_(manager),
|
||||
workspace_group_handle_(workspace_group_handle),
|
||||
id_(id) {
|
||||
add_workspace_group_listener(workspace_group_handle, this);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
|
||||
auto WorkspaceGroup::creation_delayed() const -> bool {
|
||||
return workspace_manager_.creation_delayed();
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
|
||||
box_.pack_start(button, false, false);
|
||||
}
|
||||
|
||||
WorkspaceGroup::~WorkspaceGroup() {
|
||||
if (!workspace_group_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
|
||||
auto new_id = ++workspace_global_id;
|
||||
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
|
||||
spdlog::debug("Workspace {} created", new_id);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_remove() -> void {
|
||||
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
|
||||
workspace_group_handle_ = nullptr;
|
||||
workspace_manager_.remove_workspace_group(id_);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
|
||||
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
|
||||
output_ = output;
|
||||
|
||||
if (!is_visible() || workspace_manager_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
add_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::is_visible() const -> bool {
|
||||
return output_ != nullptr &&
|
||||
(workspace_manager_.all_outputs() ||
|
||||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_output_leave() -> void {
|
||||
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
|
||||
output_ = nullptr;
|
||||
|
||||
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
remove_button(workspace->get_button_ref());
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::update() -> void {
|
||||
for (auto &workspace : workspaces_) {
|
||||
if (workspace_manager_.creation_delayed()) {
|
||||
add_button(workspace->get_button_ref());
|
||||
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
|
||||
workspace->show();
|
||||
}
|
||||
}
|
||||
|
||||
workspace->update();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
|
||||
auto it = std::find_if(workspaces_.begin(),
|
||||
workspaces_.end(),
|
||||
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
|
||||
|
||||
if (it == workspaces_.end()) {
|
||||
spdlog::warn("Can't find workspace with id {}", id);
|
||||
return;
|
||||
}
|
||||
|
||||
workspaces_.erase(it);
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::handle_done() -> void {
|
||||
need_to_sort = false;
|
||||
if (!is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &workspace : workspaces_) {
|
||||
workspace->handle_done();
|
||||
}
|
||||
|
||||
if (creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspace_manager_.all_outputs()) {
|
||||
sort_workspaces();
|
||||
} else {
|
||||
workspace_manager_.sort_workspaces();
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
|
||||
|
||||
auto WorkspaceGroup::sort_workspaces() -> void {
|
||||
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
|
||||
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
||||
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
|
||||
}
|
||||
}
|
||||
|
||||
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
|
||||
|
||||
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
|
||||
zext_workspace_handle_v1 *workspace, uint32_t id)
|
||||
: bar_(bar),
|
||||
config_(config),
|
||||
workspace_group_(workspace_group),
|
||||
workspace_handle_(workspace),
|
||||
id_(id) {
|
||||
add_workspace_listener(workspace, this);
|
||||
|
||||
auto config_format = config["format"];
|
||||
|
||||
format_ = config_format.isString() ? config_format.asString() : "{name}";
|
||||
with_icon_ = format_.find("{icon}") != std::string::npos;
|
||||
|
||||
if (with_icon_ && icons_map_.empty()) {
|
||||
auto format_icons = config["format-icons"];
|
||||
for (auto &name : format_icons.getMemberNames()) {
|
||||
icons_map_.emplace(name, format_icons[name].asString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle click events if configured */
|
||||
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
|
||||
config_["on-click-right"].isString()) {
|
||||
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
||||
false);
|
||||
}
|
||||
|
||||
button_.set_relief(Gtk::RELIEF_NONE);
|
||||
content_.set_center_widget(label_);
|
||||
button_.add(content_);
|
||||
|
||||
if (!workspace_group.is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_group.add_button(button_);
|
||||
button_.show_all();
|
||||
}
|
||||
|
||||
Workspace::~Workspace() {
|
||||
workspace_group_.remove_button(button_);
|
||||
if (!workspace_handle_) {
|
||||
return;
|
||||
}
|
||||
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
}
|
||||
|
||||
auto Workspace::update() -> void {
|
||||
label_.set_markup(fmt::format(
|
||||
format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : "")));
|
||||
}
|
||||
|
||||
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
|
||||
state_ = 0;
|
||||
for (auto state_entry : state) {
|
||||
switch (state_entry) {
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
|
||||
state_ |= (uint32_t)State::ACTIVE;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
|
||||
state_ |= (uint32_t)State::URGENT;
|
||||
break;
|
||||
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
|
||||
state_ |= (uint32_t)State::HIDDEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_remove() -> void {
|
||||
zext_workspace_handle_v1_destroy(workspace_handle_);
|
||||
workspace_handle_ = nullptr;
|
||||
workspace_group_.remove_workspace(id_);
|
||||
}
|
||||
|
||||
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
|
||||
const std::string &class_name) {
|
||||
if (condition) {
|
||||
context->add_class(class_name);
|
||||
} else {
|
||||
context->remove_class(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::handle_done() -> void {
|
||||
spdlog::debug("Workspace {} changed to state {}", id_, state_);
|
||||
auto style_context = button_.get_style_context();
|
||||
add_or_remove_class(style_context, is_active(), "active");
|
||||
add_or_remove_class(style_context, is_urgent(), "urgent");
|
||||
add_or_remove_class(style_context, is_hidden(), "hidden");
|
||||
|
||||
if (workspace_group_.creation_delayed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
|
||||
button_.show_all();
|
||||
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
|
||||
button_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspace::get_icon() -> std::string {
|
||||
if (is_active()) {
|
||||
auto active_icon_it = icons_map_.find("active");
|
||||
if (active_icon_it != icons_map_.end()) {
|
||||
return active_icon_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto named_icon_it = icons_map_.find(name_);
|
||||
if (named_icon_it != icons_map_.end()) {
|
||||
return named_icon_it->second;
|
||||
}
|
||||
|
||||
auto default_icon_it = icons_map_.find("default");
|
||||
if (default_icon_it != icons_map_.end()) {
|
||||
return default_icon_it->second;
|
||||
}
|
||||
|
||||
return name_;
|
||||
}
|
||||
|
||||
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
|
||||
std::string action;
|
||||
if (config_["on-click"].isString() && bt->button == 1) {
|
||||
action = config_["on-click"].asString();
|
||||
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
|
||||
action = config_["on-click-middle"].asString();
|
||||
} else if (config_["on-click-right"].isString() && bt->button == 3) {
|
||||
action = config_["on-click-right"].asString();
|
||||
}
|
||||
|
||||
if (action.empty())
|
||||
return true;
|
||||
else if (action == "activate") {
|
||||
zext_workspace_handle_v1_activate(workspace_handle_);
|
||||
} else if (action == "close") {
|
||||
zext_workspace_handle_v1_remove(workspace_handle_);
|
||||
} else {
|
||||
spdlog::warn("Unknown action {}", action);
|
||||
}
|
||||
|
||||
workspace_group_.commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Workspace::show() -> void { button_.show_all(); }
|
||||
auto Workspace::hide() -> void { button_.hide(); }
|
||||
|
||||
auto Workspace::handle_name(const std::string &name) -> void {
|
||||
if (name_ != name) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
|
||||
if (coordinates_ != coordinates) {
|
||||
workspace_group_.set_need_to_sort();
|
||||
}
|
||||
coordinates_ = coordinates;
|
||||
}
|
||||
} // namespace waybar::modules::wlr
|
135
src/modules/wlr/workspace_manager_binding.cpp
Normal file
135
src/modules/wlr/workspace_manager_binding.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
#include "modules/wlr/workspace_manager_binding.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "client.hpp"
|
||||
#include "modules/wlr/workspace_manager.hpp"
|
||||
|
||||
namespace waybar::modules::wlr {
|
||||
|
||||
static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
|
||||
uint32_t version) {
|
||||
if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) {
|
||||
static_cast<WorkspaceManager *>(data)->register_manager(registry, name, version);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) {
|
||||
/* Nothing to do here */
|
||||
}
|
||||
|
||||
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
|
||||
.global_remove = handle_global_remove};
|
||||
|
||||
void add_registry_listener(void *data) {
|
||||
wl_display * display = Client::inst()->wl_display;
|
||||
wl_registry *registry = wl_display_get_registry(display);
|
||||
|
||||
wl_registry_add_listener(registry, ®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
|
24
test/GlibTestsFixture.hpp
Normal file
24
test/GlibTestsFixture.hpp
Normal file
@ -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_;
|
||||
};
|
145
test/SafeSignal.cpp
Normal file
145
test/SafeSignal.cpp
Normal file
@ -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 = [
|
||||
catch2,
|
||||
fmt,
|
||||
gtkmm,
|
||||
jsoncpp,
|
||||
spdlog,
|
||||
]
|
||||
@ -14,8 +15,21 @@ config_test = executable(
|
||||
include_directories: test_inc,
|
||||
)
|
||||
|
||||
safesignal_test = executable(
|
||||
'safesignal_test',
|
||||
'SafeSignal.cpp',
|
||||
dependencies: test_dep,
|
||||
include_directories: test_inc,
|
||||
)
|
||||
|
||||
test(
|
||||
'Configuration test',
|
||||
config_test,
|
||||
workdir: meson.source_root(),
|
||||
)
|
||||
|
||||
test(
|
||||
'SafeSignal test',
|
||||
safesignal_test,
|
||||
workdir: meson.source_root(),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user