mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-11-04 01:32:42 +01:00 
			
		
		
		
	Merge branch 'master' of github.com:Alexays/Waybar
This commit is contained in:
		
							
								
								
									
										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,38 +70,56 @@ 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);
 | 
			
		||||
 | 
			
		||||
  struct waybar_output *output;
 | 
			
		||||
  Json::Value           config;
 | 
			
		||||
  struct wl_surface *   surface;
 | 
			
		||||
  bool                  exclusive = true;
 | 
			
		||||
  struct wl_surface    *surface;
 | 
			
		||||
  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 {
 | 
			
		||||
@@ -54,7 +55,7 @@ class Language : public ALabel, public sigc::trackable {
 | 
			
		||||
 | 
			
		||||
  const static std::string XKB_LAYOUT_NAMES_KEY;
 | 
			
		||||
  const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  Layout                        layout_;
 | 
			
		||||
  std::string tooltip_format_ = "";
 | 
			
		||||
  std::map<std::string, Layout> layouts_map_;
 | 
			
		||||
 
 | 
			
		||||
@@ -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)*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								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(
 | 
			
		||||
@@ -282,7 +295,7 @@ executable(
 | 
			
		||||
        gtk_layer_shell,
 | 
			
		||||
        libsndio,
 | 
			
		||||
        tz_dep,
 | 
			
		||||
				xkbregistry 
 | 
			
		||||
		xkbregistry 
 | 
			
		||||
    ],
 | 
			
		||||
    include_directories: [include_directories('include')],
 | 
			
		||||
    install: true,
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										248
									
								
								src/bar.cpp
									
									
									
									
									
								
							
							
						
						
									
										248
									
								
								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());
 | 
			
		||||
        if (pos == "modules-left") {
 | 
			
		||||
          modules_left_.emplace_back(module);
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
        if (pos == "modules-center") {
 | 
			
		||||
          modules_center_.emplace_back(module);
 | 
			
		||||
        }
 | 
			
		||||
        if (pos == "modules-right") {
 | 
			
		||||
          modules_right_.emplace_back(module);
 | 
			
		||||
 | 
			
		||||
        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_sp);
 | 
			
		||||
          }
 | 
			
		||||
          if (pos == "modules-center") {
 | 
			
		||||
            modules_center_.emplace_back(module_sp);
 | 
			
		||||
          }
 | 
			
		||||
          if (pos == "modules-right") {
 | 
			
		||||
            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);
 | 
			
		||||
  box_.set_center_widget(center_);
 | 
			
		||||
  if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
 | 
			
		||||
    box_.set_center_widget(center_);
 | 
			
		||||
  } else {
 | 
			
		||||
    box_.pack_start(center_, true, false);
 | 
			
		||||
  }
 | 
			
		||||
  box_.pack_end(right_, false, false);
 | 
			
		||||
 | 
			
		||||
  // 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;
 | 
			
		||||
      }
 | 
			
		||||
      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 (fixed_time_zone_) {
 | 
			
		||||
 | 
			
		||||
  // 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)
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
  if (!fixed_time_zone_) {
 | 
			
		||||
    // Time zone can change. Be sure to pick that.
 | 
			
		||||
    time_zone_ = date::current_zone();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto        now = std::chrono::system_clock::now();
 | 
			
		||||
  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);
 | 
			
		||||
      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 {
 | 
			
		||||
      label_.set_tooltip_markup(text);
 | 
			
		||||
      std::string calendar_lines = "";
 | 
			
		||||
      if (is_calendar_in_tooltip_) {
 | 
			
		||||
        calendar_lines = calendar_text(wtime);
 | 
			
		||||
      }
 | 
			
		||||
      auto tooltip_format = config_["tooltip-format"].asString();
 | 
			
		||||
      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;
 | 
			
		||||
    signal_strength_ = std::clamp(strength, 0, 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) {
 | 
			
		||||
                    auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
 | 
			
		||||
					if (!app_info_) {
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
					return app_info_;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    if (app_info && app_info->get_icon())
 | 
			
		||||
        return app_info->get_icon()->to_string();
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 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)
 | 
			
		||||
Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    if (icon_theme->lookup_icon(app_id, 24))
 | 
			
		||||
        return app_id;
 | 
			
		||||
	auto app_info = get_app_info_by_name(app_id);
 | 
			
		||||
	if (app_info) {
 | 
			
		||||
		return app_info;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
 | 
			
		||||
static std::string get_from_desktop_app_info_search(const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    std::string desktop_file = "";
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
    {
 | 
			
		||||
        app_info_ = get_desktop_app_info(app_id);
 | 
			
		||||
		if (app_info_) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto lower_app_id = app_id;
 | 
			
		||||
		std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
 | 
			
		||||
				[](char c){ return std::tolower(c); });
 | 
			
		||||
		app_info_ = get_desktop_app_info(lower_app_id);
 | 
			
		||||
		if (app_info_) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
				
 | 
			
		||||
        size_t start = 0, end = app_id.size();
 | 
			
		||||
        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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
        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); });
 | 
			
		||||
        start = app_id.find("-");
 | 
			
		||||
        app_name = app_id.substr(0, start);
 | 
			
		||||
        app_info_ = get_desktop_app_info(app_name);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        std::string icon_name = get_from_icon_theme(icon_theme, app_id);
 | 
			
		||||
static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
 | 
			
		||||
        const std::string &app_id)
 | 
			
		||||
{
 | 
			
		||||
    if (icon_theme->lookup_icon(app_id, 24))
 | 
			
		||||
        return app_id;
 | 
			
		||||
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            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);
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        if (icon_name.empty())
 | 
			
		||||
            icon_name = "unknown";
 | 
			
		||||
bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
 | 
			
		||||
{
 | 
			
		||||
	std::string ret_icon_name = "unknown";
 | 
			
		||||
	if (app_info) {
 | 
			
		||||
		std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
 | 
			
		||||
		if (!icon_name.empty()) {
 | 
			
		||||
			ret_icon_name = icon_name;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (app_info->get_icon()) {
 | 
			
		||||
				ret_icon_name = app_info->get_icon()->to_string();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
        Glib::RefPtr<Gdk::Pixbuf> pixbuf;
 | 
			
		||||
	Glib::RefPtr<Gdk::Pixbuf> pixbuf;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            pixbuf = icon_theme->load_icon(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);
 | 
			
		||||
            else
 | 
			
		||||
                pixbuf = {};
 | 
			
		||||
        }
 | 
			
		||||
	try {
 | 
			
		||||
		pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
 | 
			
		||||
	} catch(...) {
 | 
			
		||||
		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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	if (pixbuf) {
 | 
			
		||||
		image.set(pixbuf);
 | 
			
		||||
		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::unordered_set<std::string> &Taskbar::ignore_list() const { return ignore_list_; }
 | 
			
		||||
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(),
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user