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