diff --git a/include/bar.hpp b/include/bar.hpp index 7566b08..fb0cd59 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -15,10 +16,11 @@ namespace waybar { class Factory; struct waybar_output { - struct wl_output * output = nullptr; - std::string name; - uint32_t wl_name; - struct zxdg_output_v1 *xdg_output = nullptr; + Glib::RefPtr monitor; + std::string name; + + std::unique_ptr xdg_output = { + nullptr, &zxdg_output_v1_destroy}; }; class Bar { @@ -30,13 +32,12 @@ class Bar { auto toggle() -> void; void handleSignal(int); - struct waybar_output * output; - Json::Value config; - Gtk::Window window; - struct wl_surface * surface; - struct zwlr_layer_surface_v1 *layer_surface; - bool visible = true; - bool vertical = false; + struct waybar_output *output; + Json::Value config; + Gtk::Window window; + struct wl_surface * surface; + bool visible = true; + bool vertical = false; private: static constexpr const char *MIN_HEIGHT_MSG = @@ -51,7 +52,9 @@ class Bar { uint32_t, uint32_t); static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *); - void destroyOutput(); +#ifdef HAVE_GTK_LAYER_SHELL + void initGtkLayerShell(); +#endif void onConfigure(GdkEventConfigure *ev); void onRealize(); void onMap(GdkEventAny *ev); @@ -68,6 +71,9 @@ class Bar { int bottom = 0; int left = 0; } margins_; + struct zwlr_layer_surface_v1 *layer_surface_; + // use gtk-layer-shell instead of handling layer surfaces directly + bool use_gls_ = false; uint32_t width_ = 0; uint32_t height_ = 1; uint8_t anchor_; diff --git a/include/client.hpp b/include/client.hpp index 715ae58..65a814f 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -34,17 +34,15 @@ class Client { bool isValidOutput(const Json::Value &config, std::unique_ptr &output); auto setupConfig(const std::string &config_file) -> void; auto setupCss(const std::string &css_file) -> void; - std::unique_ptr &getOutput(uint32_t wl_name); + std::unique_ptr &getOutput(void *); std::vector getOutputConfigs(std::unique_ptr &output); static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); - static void handleLogicalPosition(void *, struct zxdg_output_v1 *, int32_t, int32_t); - static void handleLogicalSize(void *, struct zxdg_output_v1 *, int32_t, int32_t); - static void handleDone(void *, struct zxdg_output_v1 *); - static void handleName(void *, struct zxdg_output_v1 *, const char *); - static void handleDescription(void *, struct zxdg_output_v1 *, const char *); + static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); + void handleMonitorAdded(Glib::RefPtr monitor); + void handleMonitorRemoved(Glib::RefPtr monitor); Json::Value config_; Glib::RefPtr style_context_; diff --git a/meson.build b/meson.build index a9d44ee..a3260d3 100644 --- a/meson.build +++ b/meson.build @@ -52,7 +52,7 @@ spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdl wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') -gtkmm = dependency('gtkmm-3.0') +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')) jsoncpp = dependency('jsoncpp') @@ -62,6 +62,9 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) +gtk_layer_shell = dependency('gtk-layer-shell-0', + required: get_option('gtk-layer-shell'), + fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) prefix = get_option('prefix') @@ -136,6 +139,10 @@ if libmpdclient.found() src_files += 'src/modules/mpd.cpp' endif +if gtk_layer_shell.found() + add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') +endif + subdir('protocol') executable( @@ -158,7 +165,8 @@ executable( libnlgen, libpulse, libudev, - libmpdclient + libmpdclient, + gtk_layer_shell ], include_directories: [include_directories('include')], install: true, diff --git a/meson_options.txt b/meson_options.txt index 1b86b57..f70e0ff 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,3 +7,4 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') option('out', type: 'string', value : '/', description: 'output prefix directory') +option('gtk-layer-shell', type: 'feature', value: 'disabled', description: 'Use gtk-layer-shell library for popups support') diff --git a/src/bar.cpp b/src/bar.cpp index fc88511..7b9e930 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -1,3 +1,7 @@ +#ifdef HAVE_GTK_LAYER_SHELL +#include +#endif + #include "bar.hpp" #include "client.hpp" #include "factory.hpp" @@ -8,7 +12,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, surface(nullptr), - layer_surface(nullptr), + layer_surface_(nullptr), anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP), left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -28,11 +32,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) height_ = config["height"].isUInt() ? config["height"].asUInt() : height_; width_ = config["width"].isUInt() ? config["width"].asUInt() : width_; - window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); - window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); - window.set_size_request(width_, height_); - if (config["position"] == "bottom") { anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else if (config["position"] == "left") { @@ -98,6 +97,17 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) margins_ = {.top = gaps, .right = gaps, .bottom = gaps, .left = gaps}; } +#ifdef HAVE_GTK_LAYER_SHELL + use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; + if (use_gls_) { + initGtkLayerShell(); + } +#endif + + window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); + window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); + window.set_size_request(width_, height_); setupWidgets(); if (window.get_realized()) { @@ -131,11 +141,43 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) { tmp_width = ev->width; } } - if (tmp_width != width_ || tmp_height != height_) { + if (use_gls_) { + width_ = tmp_width; + height_ = tmp_height; + spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name); + setExclusiveZone(tmp_width, tmp_height); + } else if (tmp_width != width_ || tmp_height != height_) { setSurfaceSize(tmp_width, tmp_height); } } +#ifdef HAVE_GTK_LAYER_SHELL +void waybar::Bar::initGtkLayerShell() { + auto gtk_window = window.gobj(); + // this has to be executed before GtkWindow.realize + gtk_layer_init_for_window(gtk_window); + gtk_layer_set_keyboard_interactivity(gtk_window, FALSE); + auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM; + gtk_layer_set_layer(gtk_window, layer); + gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); + gtk_layer_set_namespace(gtk_window, "waybar"); + + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP); + gtk_layer_set_anchor( + gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); + + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top); + gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom); +} +#endif + void waybar::Bar::onRealize() { auto gdk_window = window.get_window()->gobj(); gdk_wayland_window_set_use_custom_surface(gdk_window); @@ -145,16 +187,22 @@ void waybar::Bar::onMap(GdkEventAny* ev) { auto gdk_window = window.get_window()->gobj(); surface = gdk_wayland_window_get_wl_surface(gdk_window); + if (use_gls_) { + return; + } + auto client = waybar::Client::inst(); + // owned by output->monitor; no need to destroy + auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj()); auto layer = config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - layer_surface = zwlr_layer_shell_v1_get_layer_surface( - client->layer_shell, surface, output->output, layer, "waybar"); + layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( + client->layer_shell, surface, wl_output, layer, "waybar"); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, false); - zwlr_layer_surface_v1_set_anchor(layer_surface, anchor_); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); + zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); zwlr_layer_surface_v1_set_margin( - layer_surface, margins_.top, margins_.right, margins_.bottom, margins_.left); + layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); setSurfaceSize(width_, height_); setExclusiveZone(width_, height_); @@ -162,7 +210,7 @@ void waybar::Bar::onMap(GdkEventAny* ev) { .configure = layerSurfaceHandleConfigure, .closed = layerSurfaceHandleClosed, }; - zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); + zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); wl_surface_commit(surface); wl_display_roundtrip(client->wl_display); @@ -182,7 +230,15 @@ void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) { } } spdlog::debug("Set exclusive zone {} for output {}", zone, output->name); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, zone); + +#ifdef HAVE_GTK_LAYER_SHELL + if (use_gls_) { + gtk_layer_set_exclusive_zone(window.gobj(), zone); + } else +#endif + { + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); + } } void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) { @@ -198,7 +254,7 @@ void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) { width += margins_.right + margins_.left; } spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name); - zwlr_layer_surface_v1_set_size(layer_surface, width, height); + zwlr_layer_surface_v1_set_size(layer_surface_, width, height); } // Converting string to button code rn as to avoid doing it later @@ -282,9 +338,9 @@ void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surf void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) { auto o = static_cast(data); - if (o->layer_surface) { - zwlr_layer_surface_v1_destroy(o->layer_surface); - o->layer_surface = nullptr; + if (o->layer_surface_) { + zwlr_layer_surface_v1_destroy(o->layer_surface_); + o->layer_surface_ = nullptr; } o->modules_left_.clear(); o->modules_center_.clear(); diff --git a/src/client.cpp b/src/client.cpp index 34efdf1..dd4cd50 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,4 +1,5 @@ #include "client.hpp" +#include #include #include #include @@ -33,11 +34,6 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { client->layer_shell = static_cast( wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - auto wl_output = static_cast( - wl_registry_bind(registry, name, &wl_output_interface, version)); - client->outputs_.emplace_back(new struct waybar_output({wl_output, "", name, nullptr})); - client->handleOutput(client->outputs_.back()); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { client->xdg_output_manager = static_cast(wl_registry_bind( @@ -50,58 +46,21 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint void waybar::Client::handleGlobalRemove(void * data, struct wl_registry * /*registry*/, uint32_t name) { - auto client = static_cast(data); - for (auto it = client->bars.begin(); it != client->bars.end();) { - if ((*it)->output->wl_name == name) { - auto output_name = (*it)->output->name; - (*it)->window.close(); - it = client->bars.erase(it); - spdlog::info("Bar removed from output: {}", output_name); - } else { - ++it; - } - } - auto it = std::find_if(client->outputs_.begin(), - client->outputs_.end(), - [&name](const auto &output) { return output->wl_name == name; }); - if (it != client->outputs_.end()) { - if ((*it)->xdg_output != nullptr) { - zxdg_output_v1_destroy((*it)->xdg_output); - (*it)->xdg_output = nullptr; - } - if ((*it)->output != nullptr) { - wl_output_destroy((*it)->output); - (*it)->output = nullptr; - } - client->outputs_.erase(it); - } + // Nothing here } void waybar::Client::handleOutput(std::unique_ptr &output) { static const struct zxdg_output_v1_listener xdgOutputListener = { - .logical_position = handleLogicalPosition, - .logical_size = handleLogicalSize, - .done = handleDone, - .name = handleName, - .description = handleDescription, + .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, + .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, + .done = [](void *, struct zxdg_output_v1 *) {}, + .name = &handleOutputName, + .description = [](void *, struct zxdg_output_v1 *, const char *) {}, }; - output->xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, output->output); - zxdg_output_v1_add_listener(output->xdg_output, &xdgOutputListener, &output->wl_name); -} - -void waybar::Client::handleLogicalPosition(void * /*data*/, - struct zxdg_output_v1 * /*zxdg_output_v1*/, - int32_t /*x*/, int32_t /*y*/) { - // Nothing here -} - -void waybar::Client::handleLogicalSize(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/, - int32_t /*width*/, int32_t /*height*/) { - // Nothing here -} - -void waybar::Client::handleDone(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/) { - // Nothing here + // owned by output->monitor; no need to destroy + auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj()); + output->xdg_output.reset(zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, wl_output)); + zxdg_output_v1_add_listener(output->xdg_output.get(), &xdgOutputListener, output.get()); } bool waybar::Client::isValidOutput(const Json::Value & config, @@ -123,9 +82,9 @@ bool waybar::Client::isValidOutput(const Json::Value & config return found; } -std::unique_ptr &waybar::Client::getOutput(uint32_t wl_name) { - auto it = std::find_if(outputs_.begin(), outputs_.end(), [&wl_name](const auto &output) { - return output->wl_name == wl_name; +std::unique_ptr &waybar::Client::getOutput(void *addr) { + auto it = std::find_if(outputs_.begin(), outputs_.end(), [&addr](const auto &output) { + return output.get() == addr; }); if (it == outputs_.end()) { throw std::runtime_error("Unable to find valid output"); @@ -148,23 +107,19 @@ std::vector waybar::Client::getOutputConfigs( return configs; } -void waybar::Client::handleName(void * data, struct zxdg_output_v1 * /*xdg_output*/, - const char *name) { - auto wl_name = *static_cast(data); +void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/, + const char *name) { auto client = waybar::Client::inst(); try { - auto &output = client->getOutput(wl_name); + auto &output = client->getOutput(data); output->name = name; + spdlog::debug("Output detected: {} ({} {})", + name, + output->monitor->get_manufacturer(), + output->monitor->get_model()); auto configs = client->getOutputConfigs(output); if (configs.empty()) { - if (output->output != nullptr) { - wl_output_destroy(output->output); - output->output = nullptr; - } - if (output->xdg_output != nullptr) { - zxdg_output_v1_destroy(output->xdg_output); - output->xdg_output = nullptr; - } + output->xdg_output.reset(); } else { wl_display_roundtrip(client->wl_display); for (const auto &config : configs) { @@ -179,9 +134,26 @@ void waybar::Client::handleName(void * data, struct zxdg_output_v1 * /*xdg_ } } -void waybar::Client::handleDescription(void * /*data*/, struct zxdg_output_v1 * /*zxdg_output_v1*/, - const char * /*description*/) { - // Nothing here +void waybar::Client::handleMonitorAdded(Glib::RefPtr monitor) { + auto &output = outputs_.emplace_back(new struct waybar_output({monitor})); + handleOutput(output); +} + +void waybar::Client::handleMonitorRemoved(Glib::RefPtr monitor) { + spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model()); + for (auto it = bars.begin(); it != bars.end();) { + if ((*it)->output->monitor == monitor) { + auto output_name = (*it)->output->name; + (*it)->window.close(); + it = bars.erase(it); + spdlog::info("Bar removed from output: {}", output_name); + } else { + ++it; + } + } + std::remove_if(outputs_.begin(), outputs_.end(), [&monitor](const auto &output) { + return output->monitor == monitor; + }); } std::tuple waybar::Client::getConfigs( @@ -240,6 +212,14 @@ void waybar::Client::bindInterfaces() { if (layer_shell == nullptr || xdg_output_manager == nullptr) { throw std::runtime_error("Failed to acquire required resources."); } + // add existing outputs and subscribe to updates + for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { + auto monitor = gdk_display->get_monitor(i); + handleMonitorAdded(monitor); + } + gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); + gdk_display->signal_monitor_removed().connect( + sigc::mem_fun(*this, &Client::handleMonitorRemoved)); } int waybar::Client::main(int argc, char *argv[]) { diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap new file mode 100644 index 0000000..b826ab9 --- /dev/null +++ b/subprojects/gtk-layer-shell.wrap @@ -0,0 +1,5 @@ +[wrap-file] +directory = gtk-layer-shell-0.1.0 +source_filename = gtk-layer-shell-0.1.0.tar.gz +source_hash = f7569e27ae30b1a94c3ad6c955cf56240d6bc272b760d9d266ce2ccdb94a5cf0 +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.1.0/gtk-layer-shell-0.1.0.tar.gz