1 #ifdef HAVE_GTK_LAYER_SHELL
2 #include <gtk-layer-shell.h>
3 #endif
4 
5 #include <spdlog/spdlog.h>
6 
7 #include <type_traits>
8 
9 #include "bar.hpp"
10 #include "client.hpp"
11 #include "factory.hpp"
12 #include "group.hpp"
13 #include "wlr-layer-shell-unstable-v1-client-protocol.h"
14 
15 #ifdef HAVE_SWAY
16 #include "modules/sway/bar.hpp"
17 #endif
18 
19 namespace waybar {
20 static constexpr const char* MIN_HEIGHT_MSG =
21     "Requested height: {} is less than the minimum height: {} required by the modules";
22 
23 static constexpr const char* MIN_WIDTH_MSG =
24     "Requested width: {} is less than the minimum width: {} required by the modules";
25 
26 static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
27 
28 static constexpr const char* SIZE_DEFINED =
29     "{} size is defined in the config file so it will stay like that";
30 
31 const Bar::bar_mode_map Bar::PRESET_MODES = {  //
32     {"default",
33      {// Special mode to hold the global bar configuration
34       .layer = bar_layer::BOTTOM,
35       .exclusive = true,
36       .passthrough = false,
37       .visible = true}},
38     {"dock",
39      {// Modes supported by the sway config; see man sway-bar(5)
40       .layer = bar_layer::BOTTOM,
41       .exclusive = true,
42       .passthrough = false,
43       .visible = true}},
44     {"hide",
45      {//
46       .layer = bar_layer::TOP,
47       .exclusive = false,
48       .passthrough = false,
49       .visible = true}},
50     {"invisible",
51      {//
52       .layer = bar_layer::BOTTOM,
53       .exclusive = false,
54       .passthrough = true,
55       .visible = false}},
56     {"overlay",
57      {//
58       .layer = bar_layer::TOP,
59       .exclusive = false,
60       .passthrough = true,
61       .visible = true}}};
62 
63 const std::string_view Bar::MODE_DEFAULT = "default";
64 const std::string_view Bar::MODE_INVISIBLE = "invisible";
65 const std::string_view DEFAULT_BAR_ID = "bar-0";
66 
67 /* Deserializer for enum bar_layer */
from_json(const Json::Value & j,bar_layer & l)68 void from_json(const Json::Value& j, bar_layer& l) {
69   if (j == "bottom") {
70     l = bar_layer::BOTTOM;
71   } else if (j == "top") {
72     l = bar_layer::TOP;
73   } else if (j == "overlay") {
74     l = bar_layer::OVERLAY;
75   }
76 }
77 
78 /* Deserializer for struct bar_mode */
from_json(const Json::Value & j,bar_mode & m)79 void from_json(const Json::Value& j, bar_mode& m) {
80   if (j.isObject()) {
81     if (auto v = j["layer"]; v.isString()) {
82       from_json(v, m.layer);
83     }
84     if (auto v = j["exclusive"]; v.isBool()) {
85       m.exclusive = v.asBool();
86     }
87     if (auto v = j["passthrough"]; v.isBool()) {
88       m.passthrough = v.asBool();
89     }
90     if (auto v = j["visible"]; v.isBool()) {
91       m.visible = v.asBool();
92     }
93   }
94 }
95 
96 /* Deserializer for JSON Object -> map<string compatible type, Value>
97  * Assumes that all the values in the object are deserializable to the same type.
98  */
99 template <typename Key, typename Value,
100           typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
from_json(const Json::Value & j,std::map<Key,Value> & m)101 void from_json(const Json::Value& j, std::map<Key, Value>& m) {
102   if (j.isObject()) {
103     for (auto it = j.begin(); it != j.end(); ++it) {
104       from_json(*it, m[it.key().asString()]);
105     }
106   }
107 }
108 
109 #ifdef HAVE_GTK_LAYER_SHELL
110 struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImplwaybar::GLSSurfaceImpl111   GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
112     output_name_ = output.name;
113     // this has to be executed before GtkWindow.realize
114     gtk_layer_init_for_window(window_.gobj());
115     gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE);
116     gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
117     gtk_layer_set_namespace(window_.gobj(), "waybar");
118 
119     window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
120     window.signal_configure_event().connect_notify(
121         sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
122   }
123 
setExclusiveZonewaybar::GLSSurfaceImpl124   void setExclusiveZone(bool enable) override {
125     if (enable) {
126       gtk_layer_auto_exclusive_zone_enable(window_.gobj());
127     } else {
128       gtk_layer_set_exclusive_zone(window_.gobj(), 0);
129     }
130   }
131 
setMarginswaybar::GLSSurfaceImpl132   void setMargins(const struct bar_margins& margins) override {
133     gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left);
134     gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right);
135     gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top);
136     gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom);
137   }
138 
setLayerwaybar::GLSSurfaceImpl139   void setLayer(bar_layer value) override {
140     auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
141     if (value == bar_layer::TOP) {
142       layer = GTK_LAYER_SHELL_LAYER_TOP;
143     } else if (value == bar_layer::OVERLAY) {
144       layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
145     }
146     gtk_layer_set_layer(window_.gobj(), layer);
147   }
148 
setPassThroughwaybar::GLSSurfaceImpl149   void setPassThrough(bool enable) override {
150     passthrough_ = enable;
151     auto gdk_window = window_.get_window();
152     if (gdk_window) {
153       Cairo::RefPtr<Cairo::Region> region;
154       if (enable) {
155         region = Cairo::Region::create();
156       }
157       gdk_window->input_shape_combine_region(region, 0, 0);
158     }
159   }
160 
setPositionwaybar::GLSSurfaceImpl161   void setPosition(const std::string_view& position) override {
162     auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
163     vertical_ = false;
164     if (position == "bottom") {
165       unanchored = GTK_LAYER_SHELL_EDGE_TOP;
166     } else if (position == "left") {
167       unanchored = GTK_LAYER_SHELL_EDGE_RIGHT;
168       vertical_ = true;
169     } else if (position == "right") {
170       vertical_ = true;
171       unanchored = GTK_LAYER_SHELL_EDGE_LEFT;
172     }
173     for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT,
174                       GTK_LAYER_SHELL_EDGE_RIGHT,
175                       GTK_LAYER_SHELL_EDGE_TOP,
176                       GTK_LAYER_SHELL_EDGE_BOTTOM}) {
177       gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge);
178     }
179   }
180 
setSizewaybar::GLSSurfaceImpl181   void setSize(uint32_t width, uint32_t height) override {
182     width_ = width;
183     height_ = height;
184     window_.set_size_request(width_, height_);
185   };
186 
187  private:
188   Gtk::Window& window_;
189   std::string  output_name_;
190   uint32_t     width_;
191   uint32_t     height_;
192   bool         passthrough_ = false;
193   bool         vertical_ = false;
194 
onMapwaybar::GLSSurfaceImpl195   void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
196 
onConfigurewaybar::GLSSurfaceImpl197   void onConfigure(GdkEventConfigure* ev) {
198     /*
199      * GTK wants new size for the window.
200      * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell
201      * code. This event handler only updates stored size of the window and prints some warnings.
202      *
203      * Note: forced resizing to a window smaller than required by GTK would not work with
204      * gtk-layer-shell.
205      */
206     if (vertical_) {
207       if (width_ > 1 && ev->width > static_cast<int>(width_)) {
208         spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
209       }
210     } else {
211       if (height_ > 1 && ev->height > static_cast<int>(height_)) {
212         spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
213       }
214     }
215     width_ = ev->width;
216     height_ = ev->height;
217     spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_);
218   }
219 };
220 #endif
221 
222 struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
RawSurfaceImplwaybar::RawSurfaceImpl223   RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
224     output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
225     output_name_ = output.name;
226 
227     window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize));
228     window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap));
229     window.signal_configure_event().connect_notify(
230         sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure));
231 
232     if (window.get_realized()) {
233       onRealize();
234     }
235   }
236 
setExclusiveZonewaybar::RawSurfaceImpl237   void setExclusiveZone(bool enable) override {
238     exclusive_zone_ = enable;
239     if (layer_surface_) {
240       auto zone = 0;
241       if (enable) {
242         // exclusive zone already includes margin for anchored edge,
243         // only opposite margin should be added
244         if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
245           zone += width_;
246           zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
247         } else {
248           zone += height_;
249           zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
250         }
251       }
252       spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_);
253       zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone);
254     }
255   }
256 
setLayerwaybar::RawSurfaceImpl257   void setLayer(bar_layer layer) override {
258     layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
259     if (layer == bar_layer::TOP) {
260       layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
261     } else if (layer == bar_layer::OVERLAY) {
262       layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
263     }
264     // updating already mapped window
265     if (layer_surface_) {
266       if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >=
267           ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) {
268         zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_);
269       } else {
270         spdlog::warn("Unable to change layer: layer-shell implementation is too old");
271       }
272     }
273   }
274 
setMarginswaybar::RawSurfaceImpl275   void setMargins(const struct bar_margins& margins) override {
276     margins_ = margins;
277     // updating already mapped window
278     if (layer_surface_) {
279       zwlr_layer_surface_v1_set_margin(
280           layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
281     }
282   }
283 
setPassThroughwaybar::RawSurfaceImpl284   void setPassThrough(bool enable) override {
285     passthrough_ = enable;
286     /* GTK overwrites any region changes applied directly to the wl_surface,
287      * thus the same GTK region API as in the GLS impl has to be used. */
288     auto gdk_window = window_.get_window();
289     if (gdk_window) {
290       Cairo::RefPtr<Cairo::Region> region;
291       if (enable) {
292         region = Cairo::Region::create();
293       }
294       gdk_window->input_shape_combine_region(region, 0, 0);
295     }
296   }
297 
setPositionwaybar::RawSurfaceImpl298   void setPosition(const std::string_view& position) override {
299     anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
300     if (position == "bottom") {
301       anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
302     } else if (position == "left") {
303       anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
304     } else if (position == "right") {
305       anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
306     }
307 
308     // updating already mapped window
309     if (layer_surface_) {
310       zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
311     }
312   }
313 
setSizewaybar::RawSurfaceImpl314   void setSize(uint32_t width, uint32_t height) override {
315     configured_width_ = width_ = width;
316     configured_height_ = height_ = height;
317     // layer_shell.configure handler should update exclusive zone if size changes
318     window_.set_size_request(width, height);
319   };
320 
commitwaybar::RawSurfaceImpl321   void commit() override {
322     if (surface_) {
323       wl_surface_commit(surface_);
324     }
325   }
326 
327  private:
328   constexpr static uint8_t VERTICAL_ANCHOR =
329       ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
330   constexpr static uint8_t HORIZONTAL_ANCHOR =
331       ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
332 
333   template <auto fn>
334   using deleter_fn = std::integral_constant<decltype(fn), fn>;
335   using layer_surface_ptr =
336       std::unique_ptr<zwlr_layer_surface_v1, deleter_fn<zwlr_layer_surface_v1_destroy>>;
337 
338   Gtk::Window&       window_;
339   std::string        output_name_;
340   uint32_t           configured_width_ = 0;
341   uint32_t           configured_height_ = 0;
342   uint32_t           width_ = 0;
343   uint32_t           height_ = 0;
344   uint8_t            anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
345   bool               exclusive_zone_ = true;
346   bool               passthrough_ = false;
347   struct bar_margins margins_;
348 
349   zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
350   struct wl_output*         output_ = nullptr;   // owned by GTK
351   struct wl_surface*        surface_ = nullptr;  // owned by GTK
352   layer_surface_ptr         layer_surface_;
353 
onRealizewaybar::RawSurfaceImpl354   void onRealize() {
355     auto gdk_window = window_.get_window()->gobj();
356     gdk_wayland_window_set_use_custom_surface(gdk_window);
357   }
358 
onMapwaybar::RawSurfaceImpl359   void onMap(GdkEventAny* ev) {
360     static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
361         .configure = onSurfaceConfigure,
362         .closed = onSurfaceClosed,
363     };
364     auto client = Client::inst();
365     auto gdk_window = window_.get_window()->gobj();
366     surface_ = gdk_wayland_window_get_wl_surface(gdk_window);
367 
368     layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(
369         client->layer_shell, surface_, output_, layer_, "waybar"));
370 
371     zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this);
372     zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false);
373     zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
374     zwlr_layer_surface_v1_set_margin(
375         layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
376 
377     setSurfaceSize(width_, height_);
378     setExclusiveZone(exclusive_zone_);
379     setPassThrough(passthrough_);
380 
381     commit();
382     wl_display_roundtrip(client->wl_display);
383   }
384 
onConfigurewaybar::RawSurfaceImpl385   void onConfigure(GdkEventConfigure* ev) {
386     /*
387      * GTK wants new size for the window.
388      *
389      * Prefer configured size if it's non-default.
390      * If the size is not set and the window is smaller than requested by GTK, request resize from
391      * layer surface.
392      */
393     auto tmp_height = height_;
394     auto tmp_width = width_;
395     if (ev->height > static_cast<int>(height_)) {
396       // Default minimal value
397       if (height_ > 1) {
398         spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
399       }
400       if (configured_height_ > 1) {
401         spdlog::info(SIZE_DEFINED, "Height");
402       } else {
403         tmp_height = ev->height;
404       }
405     }
406     if (ev->width > static_cast<int>(width_)) {
407       // Default minimal value
408       if (width_ > 1) {
409         spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
410       }
411       if (configured_width_ > 1) {
412         spdlog::info(SIZE_DEFINED, "Width");
413       } else {
414         tmp_width = ev->width;
415       }
416     }
417     if (tmp_width != width_ || tmp_height != height_) {
418       setSurfaceSize(tmp_width, tmp_height);
419       commit();
420     }
421   }
422 
setSurfaceSizewaybar::RawSurfaceImpl423   void setSurfaceSize(uint32_t width, uint32_t height) {
424     /* If the client is anchored to two opposite edges, layer_surface.configure will return
425      * size without margins for the axis.
426      * layer_surface.set_size, however, expects size with margins for the anchored axis.
427      * This is not specified by wlr-layer-shell and based on actual behavior of sway.
428      *
429      * If the size for unanchored axis is not set (0), change request to 1 to avoid automatic
430      * assignment by the compositor.
431      */
432     if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
433       width = width > 0 ? width : 1;
434       if (height > 1) {
435         height += margins_.top + margins_.bottom;
436       }
437     } else {
438       height = height > 0 ? height : 1;
439       if (width > 1) {
440         width += margins_.right + margins_.left;
441       }
442     }
443     spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_);
444     zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height);
445   }
446 
onSurfaceConfigurewaybar::RawSurfaceImpl447   static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial,
448                                  uint32_t width, uint32_t height) {
449     auto o = static_cast<RawSurfaceImpl*>(data);
450     if (width != o->width_ || height != o->height_) {
451       o->width_ = width;
452       o->height_ = height;
453       o->window_.set_size_request(o->width_, o->height_);
454       o->window_.resize(o->width_, o->height_);
455       o->setExclusiveZone(o->exclusive_zone_);
456       spdlog::info(BAR_SIZE_MSG,
457                    o->width_ == 1 ? "auto" : std::to_string(o->width_),
458                    o->height_ == 1 ? "auto" : std::to_string(o->height_),
459                    o->output_name_);
460       o->commit();
461     }
462     zwlr_layer_surface_v1_ack_configure(surface, serial);
463   }
464 
onSurfaceClosedwaybar::RawSurfaceImpl465   static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) {
466     auto o = static_cast<RawSurfaceImpl*>(data);
467     o->layer_surface_.reset();
468   }
469 };
470 
471 };  // namespace waybar
472 
Bar(struct waybar_output * w_output,const Json::Value & w_config)473 waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
474     : output(w_output),
475       config(w_config),
476       window{Gtk::WindowType::WINDOW_TOPLEVEL},
477       left_(Gtk::ORIENTATION_HORIZONTAL, 0),
478       center_(Gtk::ORIENTATION_HORIZONTAL, 0),
479       right_(Gtk::ORIENTATION_HORIZONTAL, 0),
480       box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
481   window.set_title("waybar");
482   window.set_name("waybar");
483   window.set_decorated(false);
484   window.get_style_context()->add_class(output->name);
485   window.get_style_context()->add_class(config["name"].asString());
486   window.get_style_context()->add_class(config["position"].asString());
487 
488   auto position = config["position"].asString();
489 
490   if (position == "right" || position == "left") {
491     left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
492     center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
493     right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
494     box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
495     vertical = true;
496   }
497 
498   left_.get_style_context()->add_class("modules-left");
499   center_.get_style_context()->add_class("modules-center");
500   right_.get_style_context()->add_class("modules-right");
501 
502   if (config["spacing"].isInt()) {
503     int spacing = config["spacing"].asInt();
504     left_.set_spacing(spacing);
505     center_.set_spacing(spacing);
506     right_.set_spacing(spacing);
507   }
508 
509   uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
510   uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
511 
512   struct bar_margins margins_;
513 
514   if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
515       config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
516     margins_ = {
517         config["margin-top"].isInt() ? config["margin-top"].asInt() : 0,
518         config["margin-right"].isInt() ? config["margin-right"].asInt() : 0,
519         config["margin-bottom"].isInt() ? config["margin-bottom"].asInt() : 0,
520         config["margin-left"].isInt() ? config["margin-left"].asInt() : 0,
521     };
522   } else if (config["margin"].isString()) {
523     std::istringstream       iss(config["margin"].asString());
524     std::vector<std::string> margins{std::istream_iterator<std::string>(iss), {}};
525     try {
526       if (margins.size() == 1) {
527         auto gaps = std::stoi(margins[0], nullptr, 10);
528         margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};
529       }
530       if (margins.size() == 2) {
531         auto vertical_margins = std::stoi(margins[0], nullptr, 10);
532         auto horizontal_margins = std::stoi(margins[1], nullptr, 10);
533         margins_ = {.top = vertical_margins,
534                     .right = horizontal_margins,
535                     .bottom = vertical_margins,
536                     .left = horizontal_margins};
537       }
538       if (margins.size() == 3) {
539         auto horizontal_margins = std::stoi(margins[1], nullptr, 10);
540         margins_ = {.top = std::stoi(margins[0], nullptr, 10),
541                     .right = horizontal_margins,
542                     .bottom = std::stoi(margins[2], nullptr, 10),
543                     .left = horizontal_margins};
544       }
545       if (margins.size() == 4) {
546         margins_ = {.top = std::stoi(margins[0], nullptr, 10),
547                     .right = std::stoi(margins[1], nullptr, 10),
548                     .bottom = std::stoi(margins[2], nullptr, 10),
549                     .left = std::stoi(margins[3], nullptr, 10)};
550       }
551     } catch (...) {
552       spdlog::warn("Invalid margins: {}", config["margin"].asString());
553     }
554   } else if (config["margin"].isInt()) {
555     auto gaps = config["margin"].asInt();
556     margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps};
557   }
558 
559 #ifdef HAVE_GTK_LAYER_SHELL
560   bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
561   if (use_gls) {
562     surface_impl_ = std::make_unique<GLSSurfaceImpl>(window, *output);
563   } else
564 #endif
565   {
566     surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
567   }
568 
569   surface_impl_->setMargins(margins_);
570   surface_impl_->setPosition(position);
571   surface_impl_->setSize(width, height);
572 
573   /* Read custom modes if available */
574   if (auto modes = config.get("modes", {}); modes.isObject()) {
575     from_json(modes, configured_modes);
576   }
577 
578   /* Update "default" mode with the global bar options */
579   from_json(config, configured_modes[MODE_DEFAULT]);
580 
581   if (auto mode = config.get("mode", {}); mode.isString()) {
582     setMode(config["mode"].asString());
583   } else {
584     setMode(MODE_DEFAULT);
585   }
586 
587   window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
588 
589 #if HAVE_SWAY
590   if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
591     bar_id = Client::inst()->bar_id;
592     if (auto id = config["id"]; id.isString()) {
593       bar_id = id.asString();
594     }
595     if (bar_id.empty()) {
596       bar_id = DEFAULT_BAR_ID;
597     }
598     try {
599       _ipc_client = std::make_unique<BarIpcClient>(*this);
600     } catch (const std::exception& exc) {
601       spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
602     }
603   }
604 #endif
605 
606   setupWidgets();
607   window.show_all();
608 
609   if (spdlog::should_log(spdlog::level::debug)) {
610     // Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
611     char* gtk_tree = gtk_style_context_to_string(
612         window.get_style_context()->gobj(),
613         (GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |
614                                     GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));
615     spdlog::debug("GTK widget tree:\n{}", gtk_tree);
616     g_free(gtk_tree);
617   }
618 }
619 
620 /* Need to define it here because of forward declared members */
621 waybar::Bar::~Bar() = default;
622 
setMode(const std::string_view & mode)623 void waybar::Bar::setMode(const std::string_view& mode) {
624   using namespace std::literals::string_literals;
625 
626   auto style = window.get_style_context();
627   /* remove styles added by previous setMode calls */
628   style->remove_class("mode-"s + last_mode_);
629 
630   auto it = configured_modes.find(mode);
631   if (it != configured_modes.end()) {
632     last_mode_ = mode;
633     style->add_class("mode-"s + last_mode_);
634     setMode(it->second);
635   } else {
636     spdlog::warn("Unknown mode \"{}\" requested", mode);
637     last_mode_ = MODE_DEFAULT;
638     style->add_class("mode-"s + last_mode_);
639     setMode(configured_modes.at(MODE_DEFAULT));
640   }
641 }
642 
setMode(const struct bar_mode & mode)643 void waybar::Bar::setMode(const struct bar_mode& mode) {
644   surface_impl_->setLayer(mode.layer);
645   surface_impl_->setExclusiveZone(mode.exclusive);
646   surface_impl_->setPassThrough(mode.passthrough);
647 
648   if (mode.visible) {
649     window.get_style_context()->remove_class("hidden");
650     window.set_opacity(1);
651   } else {
652     window.get_style_context()->add_class("hidden");
653     window.set_opacity(0);
654   }
655   surface_impl_->commit();
656 }
657 
onMap(GdkEventAny *)658 void waybar::Bar::onMap(GdkEventAny*) {
659   /*
660    * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
661    */
662   auto gdk_window = window.get_window()->gobj();
663   surface = gdk_wayland_window_get_wl_surface(gdk_window);
664 }
665 
setVisible(bool value)666 void waybar::Bar::setVisible(bool value) {
667   visible = value;
668   setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
669 }
670 
toggle()671 void waybar::Bar::toggle() { setVisible(!visible); }
672 
673 // Converting string to button code rn as to avoid doing it later
setupAltFormatKeyForModule(const std::string & module_name)674 void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
675   if (config.isMember(module_name)) {
676     Json::Value& module = config[module_name];
677     if (module.isMember("format-alt")) {
678       if (module.isMember("format-alt-click")) {
679         Json::Value& click = module["format-alt-click"];
680         if (click.isString()) {
681           if (click == "click-right") {
682             module["format-alt-click"] = 3U;
683           } else if (click == "click-middle") {
684             module["format-alt-click"] = 2U;
685           } else if (click == "click-backward") {
686             module["format-alt-click"] = 8U;
687           } else if (click == "click-forward") {
688             module["format-alt-click"] = 9U;
689           } else {
690             module["format-alt-click"] = 1U;  // default click-left
691           }
692         } else {
693           module["format-alt-click"] = 1U;
694         }
695       } else {
696         module["format-alt-click"] = 1U;
697       }
698     }
699   }
700 }
701 
setupAltFormatKeyForModuleList(const char * module_list_name)702 void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
703   if (config.isMember(module_list_name)) {
704     Json::Value& modules = config[module_list_name];
705     for (const Json::Value& module_name : modules) {
706       if (module_name.isString()) {
707         setupAltFormatKeyForModule(module_name.asString());
708       }
709     }
710   }
711 }
712 
handleSignal(int signal)713 void waybar::Bar::handleSignal(int signal) {
714   for (auto& module : modules_all_) {
715     auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
716     if (custom != nullptr) {
717       custom->refresh(signal);
718     }
719   }
720 }
721 
getModules(const Factory & factory,const std::string & pos,Gtk::Box * group=nullptr)722 void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
723   auto module_list = group ? config[pos]["modules"] : config[pos];
724   if (module_list.isArray()) {
725     for (const auto& name : module_list) {
726       try {
727         auto ref = name.asString();
728         AModule* module;
729 
730         if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
731           auto group_module = new waybar::Group(ref, *this, config[ref]);
732           getModules(factory, ref, &group_module->box);
733           module = group_module;
734         } else {
735           module = factory.makeModule(ref);
736         }
737 
738         std::shared_ptr<AModule> module_sp(module);
739         modules_all_.emplace_back(module_sp);
740         if (group) {
741           group->pack_start(*module, false, false);
742         } else {
743           if (pos == "modules-left") {
744             modules_left_.emplace_back(module_sp);
745           }
746           if (pos == "modules-center") {
747             modules_center_.emplace_back(module_sp);
748           }
749           if (pos == "modules-right") {
750             modules_right_.emplace_back(module_sp);
751           }
752         }
753         module->dp.connect([module, name] {
754           try {
755             module->update();
756           } catch (const std::exception& e) {
757             spdlog::error("{}: {}", name.asString(), e.what());
758           }
759         });
760       } catch (const std::exception& e) {
761         spdlog::warn("module {}: {}", name.asString(), e.what());
762       }
763     }
764   }
765 }
766 
setupWidgets()767 auto waybar::Bar::setupWidgets() -> void {
768   window.add(box_);
769   box_.pack_start(left_, false, false);
770   if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
771     box_.set_center_widget(center_);
772   } else {
773     box_.pack_start(center_, true, false);
774   }
775   box_.pack_end(right_, false, false);
776 
777   // Convert to button code for every module that is used.
778   setupAltFormatKeyForModuleList("modules-left");
779   setupAltFormatKeyForModuleList("modules-right");
780   setupAltFormatKeyForModuleList("modules-center");
781 
782   Factory factory(*this, config);
783   getModules(factory, "modules-left");
784   getModules(factory, "modules-center");
785   getModules(factory, "modules-right");
786   for (auto const& module : modules_left_) {
787     left_.pack_start(*module, false, false);
788   }
789   for (auto const& module : modules_center_) {
790     center_.pack_start(*module, false, false);
791   }
792   std::reverse(modules_right_.begin(), modules_right_.end());
793   for (auto const& module : modules_right_) {
794     right_.pack_end(*module, false, false);
795   }
796 }
797