From 93d7211aa96b25fd836cd9ef4b906dd19d706d63 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Sun, 10 May 2026 11:51:42 +0300 Subject: [PATCH 01/16] Increase notification center size --- src/panel/widgets/notifications/notification-center.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panel/widgets/notifications/notification-center.hpp b/src/panel/widgets/notifications/notification-center.hpp index d8106539..67fbc336 100644 --- a/src/panel/widgets/notifications/notification-center.hpp +++ b/src/panel/widgets/notifications/notification-center.hpp @@ -11,7 +11,7 @@ class WayfireNotificationCenter : public WayfireWidget { private: - static const int WIDTH = 300, HEIGHT = 400; + static const int WIDTH = 500, HEIGHT = 600; const std::shared_ptr daemon = Daemon::Launch(); sigc::connection notification_new_conn; From 67d45843f89c2aac4acb9474677213033763b8f7 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Fri, 15 May 2026 11:47:43 +0300 Subject: [PATCH 02/16] Backport configurable hide/unhide delays --- metadata/dock.xml | 10 ++++++++++ metadata/panel.xml | 10 ++++++++++ src/panel/widgets/menu.cpp | 2 +- src/util/wf-autohide-window.cpp | 15 +++++++-------- src/util/wf-autohide-window.hpp | 6 ++++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/metadata/dock.xml b/metadata/dock.xml index 1c74e047..040ecd89 100644 --- a/metadata/dock.xml +++ b/metadata/dock.xml @@ -15,6 +15,16 @@ <_short>Autohide duration 300 + + + + + <_short>Network From 107ed323251bf6efbdffb695334f7b7778b78970 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Sun, 17 May 2026 15:25:29 +0300 Subject: [PATCH 06/16] Backport wf-ipc classes --- meson.build | 1 + src/util/meson.build | 23 ++- src/util/wf-ipc.cpp | 398 +++++++++++++++++++++++++++++++++++++++++++ src/util/wf-ipc.hpp | 82 +++++++++ 4 files changed, 502 insertions(+), 2 deletions(-) create mode 100644 src/util/wf-ipc.cpp create mode 100644 src/util/wf-ipc.hpp diff --git a/meson.build b/meson.build index 147c4e35..f926f6bd 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,7 @@ project( ) wayfire = dependency('wayfire') +json = subproject('wf-json').get_variable('wfjson') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-3.0', version: '>=3.24') diff --git a/src/util/meson.build b/src/util/meson.build index 056f79db..aa945bcb 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,5 +1,24 @@ -util = static_library('util', ['gtk-utils.cpp', 'wf-shell-app.cpp', 'wf-autohide-window.cpp', 'wf-popover.cpp'], - dependencies: [wf_protos, wayland_client, gtkmm, wfconfig, libinotify, gtklayershell]) +util = static_library( + 'util', + [ + 'gtk-utils.cpp', + 'wf-shell-app.cpp', + 'wf-ipc.cpp', + 'wf-autohide-window.cpp', + 'wf-popover.cpp' + ], + + dependencies: + [ + wf_protos, + wayland_client, + gtkmm, + wfconfig, + libinotify, + json, + gtklayershell + ] +) util_includes = include_directories('.') libutil = declare_dependency( diff --git a/src/util/wf-ipc.cpp b/src/util/wf-ipc.cpp new file mode 100644 index 00000000..bae91f77 --- /dev/null +++ b/src/util/wf-ipc.cpp @@ -0,0 +1,398 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wf-ipc.hpp" + +WayfireIPC::WayfireIPC() +{ + + std::cerr << "WAYFIRE_IPC CREATING" << std::endl; + if (connect()) + { + sig_connection = Glib::signal_io().connect( + sigc::mem_fun(*this, &WayfireIPC::receive), + connection->get_socket()->get_fd(), + Glib::IOCondition::IO_IN); + std::cerr << "WAYFIRE_IPC CONNECTED" << std::endl; + connected = true; + } else + { + std::cerr << "Failed to connect to WAYFIRE_SOCKET. Is wayfire ipc plugin enabled?" << std::endl; + } +} + +WayfireIPC::~WayfireIPC() +{ + if (connected) + { + disconnect(); + } +} + +bool WayfireIPC::connect() +{ + const char *socket_path = getenv("WAYFIRE_SOCKET"); + if (!socket_path || std::string(socket_path).empty()) + { + std::cerr << "Wayfire socket not found" << std::endl; + return false; + } + + try { + auto client = Gio::SocketClient::create(); + auto address = Gio::UnixSocketAddress::create(socket_path); + connection = client->connect(address); + connection->get_socket()->set_blocking(false); + output = connection->get_output_stream(); + input = connection->get_input_stream(); + cancel = Gio::Cancellable::create(); + + return true; + } catch (const Glib::Error& ex) + { + std::cerr << "Error connecting to WAYFIRE_SOCKET at path \"" << socket_path << "\": " << ex.what(); + return false; + } + + return false; +} + +void WayfireIPC::disconnect() +{ + cancel->cancel(); + sig_connection.disconnect(); + connection->close(); +} + +void WayfireIPC::send(const std::string& message) +{ + send_message(message); + response_handlers.push(0); +} + +void WayfireIPC::send(const std::string& message, int response_handler) +{ + if (!connected) + { + return; + } + + send_message(message); + response_handlers.push(response_handler); +} + +void WayfireIPC::send_message(const std::string& message) +{ + if (output->has_pending() || writing) + { + write_queue.push(message); + write_next(); + return; + } + + // Shortcut: stream is not busy, no queue needed + write_stream(message); +} + +void WayfireIPC::write_next() +{ + if (writing || cancel->is_cancelled()) + { + return; + } + + writing = true; + sig_connection = Glib::signal_io().connect( + sigc::mem_fun(*this, &WayfireIPC::send_queue), + connection->get_socket()->get_fd(), + Glib::IOCondition::IO_OUT); +} + +void WayfireIPC::write_stream(const std::string& message) +{ + try { + writing = true; + uint32_t length = message.size(); + // Pointer to data must be valid until completely wrote and + // slot is called, as documented for write_all_async. + // So we pin it with a shared pointer, destroyed *after* slot is called. + auto all_data = std::make_shared((char*)&length, 4); + *all_data += message; + output->write_all_async(all_data->data(), all_data->size(), + [this, all_data] (Glib::RefPtr& result) + { + try { + gsize written; + auto success = output->write_all_finish(result, written); + if (!success) + { + LOGE("IPC error: write failed. Bytes written: ", written); + } + + this->writing = false; + if (!cancel->is_cancelled()) + { + write_next(); + } + } catch (const Glib::Error& e) + { + this->writing = false; + if (e.code() == G_IO_ERROR_CANCELLED) + { + // Intended behavior + return; + } else + { + LOGE("IPC error: write failed: ", e.what()); + } + } + }, cancel); + } catch (const Gio::Error& e) + { + LOGE("IPC error: ", e.what()); + } +} + +bool WayfireIPC::send_queue(Glib::IOCondition cond) +{ + if (write_queue.empty()) + { + writing = false; + return false; + } + + auto message = write_queue.front(); + write_queue.pop(); + + write_stream(message); + return false; +} + +bool WayfireIPC::receive(Glib::IOCondition cond) +{ + try { + ssize_t received = 0; + uint32_t length; + + // TODO: Input buffer can(?) contain incomplete message + while (connection->get_socket()->get_available_bytes() > 0) + { + received = input->read(&length, sizeof(length)); + if (received == -1) + { + LOGE("IPC error: Receive message length failed"); + return false; + } + + if (received == 0) + { + LOGE("IPC error: Disconnected"); + return false; + } + + if (received != sizeof(length)) + { + LOGE("IPC error: failed to read message. Expected (bytes): ", + sizeof(length), + ", was read (bytes)", + received); + return false; + } + + std::string buf(length, 0); + received = input->read(&buf[0], length); + if (received == -1) + { + LOGE("IPC error: receive message body failed"); + return false; + } + + if (received == 0) + { + LOGE("IPC error: Disconnected"); + return false; + } + + if (received != length) + { + LOGE("IPC error: failed to read message. Expected (bytes): ", + length, + ", was read (bytes)", + received); + return false; + } + + wf::json_t message; + auto err = wf::json_t::parse_string(buf, message); + if (err.has_value()) + { + LOGE("IPC error: JSON parse: ", err.value(), " message: ", buf, " length: ", buf.length()); + return false; + } + + if (message.has_member("event")) + { + for (auto subscriber : subscribers) + { + subscriber->on_event(message); + } + + if (subscriptions.find(message["event"]) != subscriptions.end()) + { + for (auto sub : subscriptions[message["event"]]) + { + sub->on_event(message); + } + } + } else + { + auto handler = response_handlers.front(); + response_handlers.pop(); + auto client = clients.find(handler); + if (client != clients.end()) + { + client->second->handle_response(message); + } + } + } + } catch (const Gio::Error& e) + { + LOGE("IPC error: ", e.what()); + return false; + } + + return true; +} + +void WayfireIPC::subscribe_all(IIPCSubscriber *subscriber) +{ + subscribers.insert(subscriber); + + wf::json_t new_subs; + new_subs["method"] = "window-rules/events/watch"; + send(new_subs.serialize()); +} + +void WayfireIPC::subscribe(IIPCSubscriber *subscriber, const std::vector& events) +{ + wf::json_t new_subs; + new_subs["method"] = "window-rules/events/watch"; + new_subs["events"] = wf::json_t::array(); + + for (auto event : events) + { + if (subscriptions.find(event) == subscriptions.end()) + { + new_subs["events"].append(event); + subscriptions[event] = std::set(); + } + + subscriptions[event].insert(subscriber); + } + + if (new_subs["events"].size() > 0) + { + send(new_subs.serialize()); + } +} + +void WayfireIPC::unsubscribe(IIPCSubscriber *subscriber) +{ + subscribers.erase(subscriber); + + for (auto& [_, subs] : subscriptions) + { + subs.erase(subscriber); + } +} + +std::shared_ptr WayfireIPC::create_client() +{ + + std::cerr << "FAILED TO CREATE IPC CLIENT" << std::endl; + if (!connected) + { + return std::shared_ptr(new IPCClient(0, shared_from_this())); + } + + auto client = new IPCClient(next_client_id, shared_from_this()); + clients[next_client_id++] = client; + + // Zero is reserved for NO CLIENT id, so just in case :) + if (next_client_id == 0) + { + next_client_id++; + } + + return std::shared_ptr(client); +} + +void WayfireIPC::client_destroyed(int id) +{ + clients.erase(id); +} + +std::shared_ptr WayfireIPC::get_instance() +{ + static std::weak_ptr ipc; +std::cerr << "CALLED GET INSTANCE \n"; + auto instance = ipc.lock(); + if (!instance) + { + instance = std::shared_ptr(new WayfireIPC()); + ipc = instance; + } + + return instance; +} + +// IPCClient +IPCClient::~IPCClient() +{ + ipc->client_destroyed(id); +} + +void IPCClient::send(const std::string& message) +{ + ipc->send(message); +} + +void IPCClient::send(const std::string& message, response_handler cb) +{ + response_handlers.push(cb); + ipc->send(message, id); +} + +void IPCClient::handle_response(wf::json_t response) +{ + auto handler = response_handlers.front(); + response_handlers.pop(); + handler(response); +} + +void IPCClient::subscribe(IIPCSubscriber *subscriber, const std::vector& events) +{ + ipc->subscribe(subscriber, events); +} + +void IPCClient::subscribe_all(IIPCSubscriber *subscriber) +{ + ipc->subscribe_all(subscriber); +} + +void IPCClient::unsubscribe(IIPCSubscriber *subscriber) +{ + ipc->unsubscribe(subscriber); +} diff --git a/src/util/wf-ipc.hpp b/src/util/wf-ipc.hpp new file mode 100644 index 00000000..d9496a14 --- /dev/null +++ b/src/util/wf-ipc.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class IIPCSubscriber +{ + public: + virtual void on_event(wf::json_t) = 0; +}; + +using response_handler = std::function; + +class WayfireIPC; +class IPCClient +{ + private: + int id; + std::shared_ptr ipc; + std::queue response_handlers; + + public: + IPCClient(int id, std::shared_ptr ipc) : id(id), ipc(ipc) + {} + ~IPCClient(); + void handle_response(wf::json_t response); + void send(const std::string& message); + void send(const std::string& message, response_handler cb); + void subscribe(IIPCSubscriber *subscriber, const std::vector& events); + void subscribe_all(IIPCSubscriber *subscriber); + void unsubscribe(IIPCSubscriber *subscriber); +}; + +class WayfireIPC : public std::enable_shared_from_this +{ + private: + std::queue response_handlers; + std::set subscribers; + std::unordered_map> subscriptions; + int next_client_id{1}; + std::unordered_map clients; + sigc::connection sig_connection; + Glib::RefPtr connection; + Glib::RefPtr input; + Glib::RefPtr output; + Glib::RefPtr cancel; + std::queue write_queue; + bool writing = false; + + bool connect(); + void disconnect(); + void send_message(const std::string& message); + bool send_queue(Glib::IOCondition cond); + bool receive(Glib::IOCondition cond); + void write_stream(const std::string& message); + void write_next(); + + public: + void send(const std::string& message); + void send(const std::string& message, int response_handler); + void subscribe(IIPCSubscriber *subscriber, const std::vector& events); + void subscribe_all(IIPCSubscriber *subscriber); + void unsubscribe(IIPCSubscriber *subscriber); + std::shared_ptr create_client(); + void client_destroyed(int id); + + static std::shared_ptr get_instance(); + bool connected = false; + WayfireIPC(); + ~WayfireIPC(); +}; From dfddb39c515d5cfdead051d537675ba01205f7ba Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Sun, 17 May 2026 19:52:12 +0300 Subject: [PATCH 07/16] IPC connection on panel activate --- src/panel/panel.cpp | 32 +++++++++++++++++++++++++++++++- src/panel/panel.hpp | 7 +++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 566dd517..0cd981e7 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -78,7 +78,7 @@ class WayfirePanel::impl window->override_background_color(rgba); }; - + WfOption panel_layer{"panel/layer"}; std::function set_panel_layer = [=] () { @@ -345,6 +345,12 @@ class WayfirePanel::impl w->handle_config_reload(); } } + + WayfirePanelApp *panel_app; + void set_panel_app(WayfirePanelApp *panel_app) + { + this->panel_app = panel_app; + } }; WayfirePanel::WayfirePanel(WayfireOutput *output) : pimpl(new impl(output)) @@ -364,12 +370,36 @@ void WayfirePanel::handle_config_reload() return pimpl->handle_config_reload(); } +void WayfirePanel::set_panel_app(WayfirePanelApp *panel_app) +{ + pimpl->set_panel_app(panel_app); +} + + class WayfirePanelApp::impl { public: std::map> panels; }; +void WayfirePanelApp::on_activate() +{ + WayfireShellApp::on_activate(); + + if (!ipc_server) + { + ipc_server = WayfireIPC::get_instance(); + } + + for (auto& p : priv->panels) + { + p.second->handle_config_reload(); + p.second->set_panel_app(this); + /* p.second->init_widgets();*/ + } +} + + void WayfirePanelApp::on_config_reload() { for (auto& p : priv->panels) diff --git a/src/panel/panel.hpp b/src/panel/panel.hpp index c20f94b9..00d9017e 100644 --- a/src/panel/panel.hpp +++ b/src/panel/panel.hpp @@ -7,7 +7,9 @@ #include #include "wf-shell-app.hpp" +#include "wf-ipc.hpp" +class WayfirePanelApp; class WayfirePanel { public: @@ -16,6 +18,8 @@ class WayfirePanel wl_surface *get_wl_surface(); Gtk::Window& get_window(); void handle_config_reload(); + void set_panel_app(WayfirePanelApp *panel_app); + std::shared_ptr get_ipc_server_instance(); private: class impl; @@ -33,10 +37,13 @@ class WayfirePanelApp : public WayfireShellApp static void create(int argc, char **argv); ~WayfirePanelApp(); + void on_activate() override; void handle_new_output(WayfireOutput *output) override; void handle_output_removed(WayfireOutput *output) override; void on_config_reload() override; void on_css_reload() override; + std::shared_ptr get_ipc_server_instance(); + std::shared_ptr ipc_server = nullptr; private: WayfirePanelApp(int argc, char **argv); From 76813e9d598f5fb30b82ecae71d60d0a05d89bd0 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Sun, 17 May 2026 21:25:08 +0300 Subject: [PATCH 08/16] Backport language widget --- meson.build | 1 + src/panel/meson.build | 3 +- src/panel/panel.cpp | 129 +++++++++++++++++---------- src/panel/panel.hpp | 2 + src/panel/widgets/language.cpp | 154 +++++++++++++++++++++++++++++++++ src/panel/widgets/language.hpp | 40 +++++++++ 6 files changed, 283 insertions(+), 46 deletions(-) create mode 100644 src/panel/widgets/language.cpp create mode 100644 src/panel/widgets/language.hpp diff --git a/meson.build b/meson.build index f926f6bd..83492f5e 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,7 @@ gtklayershell = dependency('gtk-layer-shell-0', version: '>= 0.6', fallback: [' libpulse = dependency('libpulse', required : get_option('pulse')) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4') libgvc = subproject('gvc', default_options: ['static=true'], required : get_option('pulse')) +xkbregistry = dependency('xkbregistry') if get_option('wayland-logout') == true wayland_logout = subproject('wayland-logout') diff --git a/src/panel/meson.build b/src/panel/meson.build index 0a511c76..a9386a2c 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -6,6 +6,7 @@ widget_sources = ['widgets/battery.cpp', 'widgets/network.cpp', 'widgets/spacing.cpp', 'widgets/separator.cpp', + 'widgets/language.cpp', 'widgets/window-list/window-list.cpp', 'widgets/window-list/toplevel.cpp', 'widgets/notifications/daemon.cpp', @@ -17,7 +18,7 @@ widget_sources = ['widgets/battery.cpp', 'widgets/tray/item.cpp', 'widgets/tray/host.cpp'] -deps = [gtkmm, wayland_client, libutil, wf_protos, wfconfig, gtklayershell, dbusmenu_gtk] +deps = [gtkmm, wayland_client, libutil, wf_protos, wfconfig, gtklayershell, xkbregistry, json, dbusmenu_gtk] if libpulse.found() widget_sources += 'widgets/volume.cpp' diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 0cd981e7..057a7127 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -18,6 +18,7 @@ #include "widgets/battery.hpp" #include "widgets/command-output.hpp" +#include "widgets/language.hpp" #include "widgets/menu.hpp" #include "widgets/clock.hpp" #include "widgets/launchers.hpp" @@ -118,11 +119,9 @@ class WayfirePanel::impl bg_color.set_callback(on_window_color_updated); on_window_color_updated(); // set initial color + window->present(); - window->show_all(); - init_widgets(); - init_layout(); - + window->signal_delete_event().connect( sigc::mem_fun(this, &WayfirePanel::impl::on_delete)); } @@ -134,22 +133,7 @@ class WayfirePanel::impl return true; } - void init_layout() - { - left_box.get_style_context()->add_class("left"); - center_box.get_style_context()->add_class("center"); - right_box.get_style_context()->add_class("right"); - content_box.pack_start(left_box, false, false); - content_box.pack_end(right_box, false, false); - if (!center_box.get_children().empty()) - { - content_box.set_center_widget(center_box); - } - center_box.show_all(); - window->add(content_box); - window->show_all(); - } std::optional widget_with_value(std::string value, std::string prefix) { @@ -225,6 +209,19 @@ class WayfirePanel::impl { return Widget(new WfCommandOutputButtons()); } + + if (name == "language") + { + if (get_ipc_server_instance()->connected) + { + return Widget(new WayfireLanguage()); + } else + { + std::cerr << "Wayfire IPC not connected, which is required to load language widget." << + std::endl; + return nullptr; + } + } if (auto pixel = widget_with_value(name, "spacing")) { @@ -285,34 +282,10 @@ class WayfirePanel::impl WfOption left_widgets_opt{"panel/widgets_left"}; WfOption right_widgets_opt{"panel/widgets_right"}; WfOption center_widgets_opt{"panel/widgets_center"}; - void init_widgets() - { - left_widgets_opt.set_callback([=] () - { - reload_widgets((std::string)left_widgets_opt, left_widgets, left_box); - }); - right_widgets_opt.set_callback([=] () - { - reload_widgets((std::string)right_widgets_opt, right_widgets, right_box); - }); - center_widgets_opt.set_callback([=] () - { - reload_widgets((std::string)center_widgets_opt, center_widgets, center_box); - if (center_box.get_children().empty()) - { - content_box.unset_center_widget(); - } else - { - content_box.set_center_widget(center_box); - } - }); - reload_widgets((std::string)left_widgets_opt, left_widgets, left_box); - reload_widgets((std::string)right_widgets_opt, right_widgets, right_box); - reload_widgets((std::string)center_widgets_opt, center_widgets, center_box); - } public: + impl(WayfireOutput *output) : output(output) { create_window(); @@ -351,6 +324,56 @@ class WayfirePanel::impl { this->panel_app = panel_app; } + + void init_widgets() + { + + left_widgets_opt.set_callback([=] () + { + reload_widgets((std::string)left_widgets_opt, left_widgets, left_box); + }); + right_widgets_opt.set_callback([=] () + { + reload_widgets((std::string)right_widgets_opt, right_widgets, right_box); + }); + center_widgets_opt.set_callback([=] () + { + reload_widgets((std::string)center_widgets_opt, center_widgets, center_box); + if (center_box.get_children().empty()) + { + content_box.unset_center_widget(); + } else + { + content_box.set_center_widget(center_box); + } + }); + + reload_widgets((std::string)left_widgets_opt, left_widgets, left_box); + reload_widgets((std::string)right_widgets_opt, right_widgets, right_box); + reload_widgets((std::string)center_widgets_opt, center_widgets, center_box); + } + + void init_layout() + { + left_box.get_style_context()->add_class("left"); + center_box.get_style_context()->add_class("center"); + right_box.get_style_context()->add_class("right"); + content_box.pack_start(left_box, false, false); + content_box.pack_end(right_box, false, false); + if (!center_box.get_children().empty()) + { + content_box.set_center_widget(center_box); + } + + center_box.show_all(); + window->add(content_box); + window->show_all(); + } + + std::shared_ptr get_ipc_server_instance() + { + return panel_app->get_ipc_server_instance(); + } }; WayfirePanel::WayfirePanel(WayfireOutput *output) : pimpl(new impl(output)) @@ -370,6 +393,16 @@ void WayfirePanel::handle_config_reload() return pimpl->handle_config_reload(); } +void WayfirePanel::init_widgets() +{ + pimpl->init_widgets(); +} + +void WayfirePanel::init_layout() +{ + pimpl->init_layout(); +} + void WayfirePanel::set_panel_app(WayfirePanelApp *panel_app) { pimpl->set_panel_app(panel_app); @@ -395,7 +428,8 @@ void WayfirePanelApp::on_activate() { p.second->handle_config_reload(); p.second->set_panel_app(this); - /* p.second->init_widgets();*/ + p.second->init_widgets(); + p.second->init_layout(); } } @@ -498,6 +532,11 @@ WayfirePanelApp& WayfirePanelApp::get() return dynamic_cast(*instance.get()); } +std::shared_ptr WayfirePanelApp::get_ipc_server_instance() +{ + return ipc_server; +} + void WayfirePanelApp::create(int argc, char **argv) { if (instance) diff --git a/src/panel/panel.hpp b/src/panel/panel.hpp index 00d9017e..525c2be1 100644 --- a/src/panel/panel.hpp +++ b/src/panel/panel.hpp @@ -18,6 +18,8 @@ class WayfirePanel wl_surface *get_wl_surface(); Gtk::Window& get_window(); void handle_config_reload(); + void init_widgets(); + void init_layout(); void set_panel_app(WayfirePanelApp *panel_app); std::shared_ptr get_ipc_server_instance(); diff --git a/src/panel/widgets/language.cpp b/src/panel/widgets/language.cpp new file mode 100644 index 00000000..55acdee5 --- /dev/null +++ b/src/panel/widgets/language.cpp @@ -0,0 +1,154 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "../widget.hpp" +#include "wf-popover.hpp" + +#include "language.hpp" +#include "wf-ipc.hpp" +#include "panel.hpp" + +void WayfireLanguage::init(Gtk::HBox *container) +{ + + ipc_client = WayfirePanelApp::get().get_ipc_server_instance()->create_client(); + + if (!ipc_client) + { + std::cout << "Failed to connect to ipc. (are ipc and ipc-rules plugins loaded?)"; + + } + + auto style = button.get_style_context(); + style->add_class("flat"); + style->add_class("language"); + style->remove_class("activated"); + btn_sig = button.signal_clicked().connect(sigc::mem_fun(*this, &WayfireLanguage::next_layout)); + + container->pack_start(button, Gtk::PACK_SHRINK); + + button.show_all(); + ipc_client->subscribe(this, {"keyboard-modifier-state-changed"}); + ipc_client->send("{\"method\":\"wayfire/get-keyboard-state\"}", [=] (wf::json_t data) + { + if (data.serialize().find( + "error") != std::string::npos) + { + std::cerr << "Error getting keyboard state for language widget. Is wayfire ipc-rules plugin enabled?" << std::endl; + return; + } + + set_available(data["possible-layouts"]); + set_current(data["layout-index"]); + }); +} + +void WayfireLanguage::on_event(wf::json_t data) +{ + std::cout <<"on event/n"; + if (data["event"].as_string() == "keyboard-modifier-state-changed") + { + if (available_layouts.size() == 0) + { + set_available(data["state"]["possible-layouts"]); + } + + auto state_layout = data["state"]["layout-index"].as_uint(); + if (state_layout != current_layout) + { + current_layout = state_layout; + set_current(state_layout); + } + } +} + +bool WayfireLanguage::update_label() +{ + std::cout << "update label /n"; + if (current_layout >= available_layouts.size()) + { + return false; + } + + button.set_label(available_layouts[current_layout].ID); + return true; +} + +void WayfireLanguage::set_current(uint32_t index) +{ + std::cout << "set_current /n"; + current_layout = index; + update_label(); +} + +void WayfireLanguage::set_available(wf::json_t layouts) +{ + std::cout << "set_available/n" ; + std::vector layouts_available; + std::map names; + + for (size_t i = 0; i < layouts.size(); i++) + { + auto elem = layouts[i]; + names[elem] = i; + layouts_available.push_back(Layout{ + .Name = (std::string)elem, + .ID = "", + }); + } + + auto context = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS); + rxkb_context_parse_default_ruleset(context); + auto rlayout = rxkb_layout_first(context); + for (; rlayout != NULL; rlayout = rxkb_layout_next(rlayout)) + { + auto descr = rxkb_layout_get_description(rlayout); + auto name = names.find(descr); + if (name != names.end()) + { + layouts_available[name->second].ID = rxkb_layout_get_brief(rlayout); + } + } + + available_layouts = layouts_available; + update_label(); +} + +void WayfireLanguage::next_layout() +{ + std::cout << "next layout /n"; + uint32_t next = current_layout + 1; + if (next >= available_layouts.size()) + { + next = 0; + } + + wf::json_t message; + message["method"] = "wayfire/set-keyboard-state"; + message["data"] = wf::json_t(); + message["data"]["layout-index"] = next; + ipc_client->send(message.serialize()); +} + +WayfireLanguage::WayfireLanguage() +{} + +WayfireLanguage::~WayfireLanguage() +{ + if (ipc_client) + { + ipc_client->unsubscribe(this); + } + + btn_sig.disconnect(); +} diff --git a/src/panel/widgets/language.hpp b/src/panel/widgets/language.hpp new file mode 100644 index 00000000..e8542705 --- /dev/null +++ b/src/panel/widgets/language.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../widget.hpp" +#include "wf-ipc.hpp" + +struct Layout +{ + std::string Name; + std::string ID; +}; + +class WayfireLanguage : public WayfireWidget , public IIPCSubscriber +{ + Gtk::Label label; + Gtk::Button button; + sigc::connection btn_sig; + + std::shared_ptr ipc_client; + uint32_t current_layout; + std::vector available_layouts; + + public: + void init(Gtk::HBox *container) override; + void on_event(wf::json_t data) override; + bool update_label(); + void set_current(uint32_t index); + void set_available(wf::json_t layouts); + void next_layout(); + WayfireLanguage(); + ~WayfireLanguage(); +}; From 9eaec5bd8aa66ad6214b069d2f88af8660713305 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Mon, 18 May 2026 06:08:16 +0300 Subject: [PATCH 09/16] rebase launchers option in xml --- metadata/panel.xml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index 91d2ff64..b25145e0 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -94,6 +94,21 @@ + @@ -138,21 +153,6 @@ <_short>Battery Font default - <_short>Network From 12a22e5db4c11705ba1f35b511563ee3a0b4039e Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Mon, 18 May 2026 06:10:56 +0300 Subject: [PATCH 10/16] Autohide panel on mute and input language change --- src/panel/panel.cpp | 22 ++++++++++++++++++++-- src/panel/panel.hpp | 2 ++ src/panel/widgets/language.cpp | 2 ++ src/panel/widgets/volume.cpp | 6 ++++-- src/util/wf-autohide-window.hpp | 3 +-- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 057a7127..a969155f 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -353,7 +353,7 @@ class WayfirePanel::impl reload_widgets((std::string)center_widgets_opt, center_widgets, center_box); } - void init_layout() + void init_layout() { left_box.get_style_context()->add_class("left"); center_box.get_style_context()->add_class("center"); @@ -369,7 +369,12 @@ class WayfirePanel::impl window->add(content_box); window->show_all(); } - + void unhide_now() + { + + window->m_show_uncertain(); + + } std::shared_ptr get_ipc_server_instance() { return panel_app->get_ipc_server_instance(); @@ -403,6 +408,11 @@ void WayfirePanel::init_layout() pimpl->init_layout(); } +void WayfirePanel::unhide_now() +{ + pimpl->unhide_now(); +} + void WayfirePanel::set_panel_app(WayfirePanelApp *panel_app) { pimpl->set_panel_app(panel_app); @@ -548,6 +558,14 @@ void WayfirePanelApp::create(int argc, char **argv) instance->run(); } +void WayfirePanelApp::unhide_now() +{ + for (auto& p : priv->panels) + { + p.second->unhide_now(); + } +} + WayfirePanelApp::~WayfirePanelApp() = default; WayfirePanelApp::WayfirePanelApp(int argc, char **argv) : WayfireShellApp(argc, argv), priv(new impl()) diff --git a/src/panel/panel.hpp b/src/panel/panel.hpp index 525c2be1..4c63028b 100644 --- a/src/panel/panel.hpp +++ b/src/panel/panel.hpp @@ -20,6 +20,7 @@ class WayfirePanel void handle_config_reload(); void init_widgets(); void init_layout(); + void unhide_now(); void set_panel_app(WayfirePanelApp *panel_app); std::shared_ptr get_ipc_server_instance(); @@ -44,6 +45,7 @@ class WayfirePanelApp : public WayfireShellApp void handle_output_removed(WayfireOutput *output) override; void on_config_reload() override; void on_css_reload() override; + void unhide_now(); std::shared_ptr get_ipc_server_instance(); std::shared_ptr ipc_server = nullptr; diff --git a/src/panel/widgets/language.cpp b/src/panel/widgets/language.cpp index 55acdee5..66ceff66 100644 --- a/src/panel/widgets/language.cpp +++ b/src/panel/widgets/language.cpp @@ -67,6 +67,8 @@ void WayfireLanguage::on_event(wf::json_t data) if (state_layout != current_layout) { current_layout = state_layout; + std::cerr << "Unhiding panel" << std::endl; + WayfirePanelApp::get().unhide_now(); set_current(state_layout); } } diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index acf576d0..5e983410 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -3,6 +3,7 @@ #include "volume.hpp" #include "launchers.hpp" #include "gtk-utils.hpp" +#include "panel.hpp" WayfireVolumeScale::WayfireVolumeScale() { @@ -81,6 +82,7 @@ void WayfireVolume::update_icon() if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream)) { set_image_icon(main_image, "audio-volume-muted", icon_size); + WayfirePanelApp::get().unhide_now(); return; } @@ -93,6 +95,7 @@ void WayfireVolume::update_icon() }; set_image_icon(main_image, icon_name_from_state.at(current), icon_size); + WayfirePanelApp::get().unhide_now(); } bool WayfireVolume::on_popover_timeout(int timer) @@ -192,7 +195,7 @@ static void notify_is_muted(GvcMixerControl *gvc_control, guint id, gpointer user_data) { WayfireVolume *wf_volume = (WayfireVolume*)user_data; - wf_volume->update_icon(); + wf_volume->update_icon(); } void WayfireVolume::disconnect_gvc_stream_signals() @@ -249,7 +252,6 @@ static void default_sink_changed(GvcMixerControl *gvc_control, void WayfireVolume::on_volume_value_changed() { /* User manually changed volume */ - button->grab_focus(); set_volume(volume_scale.get_target_value()); } diff --git a/src/util/wf-autohide-window.hpp b/src/util/wf-autohide-window.hpp index 2c679765..fe357360 100644 --- a/src/util/wf-autohide-window.hpp +++ b/src/util/wf-autohide-window.hpp @@ -54,6 +54,7 @@ class WayfireAutohidingWindow : public Gtk::Window * in the meantime */ void schedule_hide(int delay); void schedule_show(int delay); + void m_show_uncertain(); /** When auto exclusive zone is set, the window will adjust its exclusive * zone based on the window size. @@ -109,8 +110,6 @@ class WayfireAutohidingWindow : public Gtk::Window bool m_do_hide(); int autohide_counter = static_cast(autohide_opt); - /** Show the window but hide if no pointer input */ - void m_show_uncertain(); int32_t last_hotspot_height = -1; bool input_inside_panel = false; From b305e9b910656be1cc8580e0dbe16bcbf1ed6206 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Tue, 19 May 2026 11:35:30 +0300 Subject: [PATCH 11/16] Separate animated scale and icon select from volume.cpp to utils --- src/panel/widgets/volume.cpp | 41 ++---------------------------- src/panel/widgets/volume.hpp | 23 ++--------------- src/util/animated-scale.cpp | 48 ++++++++++++++++++++++++++++++++++++ src/util/animated-scale.hpp | 31 +++++++++++++++++++++++ src/util/icon-select.cpp | 30 ++++++++++++++++++++++ src/util/icon-select.hpp | 29 ++++++++++++++++++++++ src/util/meson.build | 6 +++-- 7 files changed, 146 insertions(+), 62 deletions(-) create mode 100644 src/util/animated-scale.cpp create mode 100644 src/util/animated-scale.hpp create mode 100644 src/util/icon-select.cpp create mode 100644 src/util/icon-select.hpp diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index 5e983410..3a342bea 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -4,46 +4,9 @@ #include "launchers.hpp" #include "gtk-utils.hpp" #include "panel.hpp" +#include "icon-select.hpp" -WayfireVolumeScale::WayfireVolumeScale() -{ - this->signal_draw().connect_notify( - [=] (const Cairo::RefPtr& ctx) - { - if (this->current_volume.running()) - { - value_changed.block(); - this->set_value(this->current_volume); - value_changed.unblock(); - } - }, true); - - value_changed = this->signal_value_changed().connect_notify([=] () - { - this->current_volume.animate(this->get_value(), this->get_value()); - if (this->user_changed_callback) - { - this->user_changed_callback(); - } - }); -} - -void WayfireVolumeScale::set_target_value(double value) -{ - this->current_volume.animate(value); - this->queue_draw(); -} - -double WayfireVolumeScale::get_target_value() const -{ - return this->current_volume.end; -} - -void WayfireVolumeScale::set_user_changed_callback( - std::function callback) -{ - this->user_changed_callback = callback; -} +#define ICON(volume) icon_from_range(volume_icons, volume) enum VolumeLevel { diff --git a/src/panel/widgets/volume.hpp b/src/panel/widgets/volume.hpp index 69f1d9c1..1d90b3f9 100644 --- a/src/panel/widgets/volume.hpp +++ b/src/panel/widgets/volume.hpp @@ -8,32 +8,13 @@ #include #include "gvc-mixer-control.h" #include +#include -/** - * A custom scale which animates transitions when its value is - * changed programatically. - */ -class WayfireVolumeScale : public Gtk::Scale -{ - wf::animation::simple_animation_t current_volume{wf::create_option(200)}; - sigc::connection value_changed; - std::function user_changed_callback; - - public: - WayfireVolumeScale(); - - /* Gets the current target value */ - double get_target_value() const; - /* Set a target value to animate towards */ - void set_target_value(double value); - /** Set the callback when the user changes the scale value */ - void set_user_changed_callback(std::function callback); -}; class WayfireVolume : public WayfireWidget { Gtk::Image main_image; - WayfireVolumeScale volume_scale; + WayfireAnimatedScale volume_scale; std::unique_ptr button; WfOption icon_size{"panel/volume_icon_size"}; diff --git a/src/util/animated-scale.cpp b/src/util/animated-scale.cpp new file mode 100644 index 00000000..7abeea81 --- /dev/null +++ b/src/util/animated-scale.cpp @@ -0,0 +1,48 @@ +#include +#include "animated-scale.hpp" + +WayfireAnimatedScale::WayfireAnimatedScale() +{ + this->signal_draw().connect_notify( + [=] (const Cairo::RefPtr& ctx) + { + if (this->current_volume.running()) + { + value_changed.block(); + this->set_value(this->current_volume); + value_changed.unblock(); + } + }, true); + + value_changed = this->signal_value_changed().connect_notify([=] () + { + this->current_volume.animate(this->get_value(), this->get_value()); + if (this->user_changed_callback) + { + this->user_changed_callback(); + } + }); +} + +WayfireAnimatedScale::~WayfireAnimatedScale() +{ + value_changed.disconnect(); +} + +void WayfireAnimatedScale::set_target_value(double value) +{ + this->current_volume.animate(value); + this->queue_draw(); +} + + +double WayfireAnimatedScale::get_target_value() const +{ + return this->current_volume.end; +} + +void WayfireAnimatedScale::set_user_changed_callback( + std::function callback) +{ + this->user_changed_callback = callback; +} diff --git a/src/util/animated-scale.hpp b/src/util/animated-scale.hpp new file mode 100644 index 00000000..1e81dea8 --- /dev/null +++ b/src/util/animated-scale.hpp @@ -0,0 +1,31 @@ +#ifndef ANIMATED_SCALE_HPP +#define ANIMATED_SCALE_HPP + +#include "wf-popover.hpp" +#include +#include +#include + +/** + * A custom scale which animates transitions when its value is changed programatically. + */ +class WayfireAnimatedScale : public Gtk::Scale +{ + wf::animation::simple_animation_t current_volume{wf::create_option(200)}; + sigc::connection value_changed; + std::function user_changed_callback; + + public: + WayfireAnimatedScale(); + ~WayfireAnimatedScale(); + + /* Gets the current target value */ + double get_target_value() const; + /* Set a target value to animate towards */ + void set_target_value(double value); + /** Set the callback when the user changes the scale value */ + void set_user_changed_callback(std::function callback); + +}; + +#endif // ANIMATED_SCALE_HPP diff --git a/src/util/icon-select.cpp b/src/util/icon-select.cpp new file mode 100644 index 00000000..d58ea56c --- /dev/null +++ b/src/util/icon-select.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "icon-select.hpp" + +std::string icon_from_range(std::map> icons, double value) +{ + for (auto pair : icons) + { + if (value <= pair.first) + { + auto theme = Gtk::IconTheme::get_for_screen(Gdk::Display::get_default()->get_default_screen()); + if (!theme) + { + // let’s stick to standard icons in this case + return *pair.second.end(); + } + + for (auto name : pair.second) + { + if (theme->has_icon(name)) + { + return name; + } + } + } + } + + return ""; +} diff --git a/src/util/icon-select.hpp b/src/util/icon-select.hpp new file mode 100644 index 00000000..107c40d2 --- /dev/null +++ b/src/util/icon-select.hpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +std::string icon_from_range(std::map> icons, double value); + +// the number in the first term is the maximal value at which this icon will be shown +// selection values of the tables are expected to be ordered from least to greatest +// the vector of icons is the different possibilites of icons to show, +// with the prefered one as the first. This is to be able to use non-standard icons +// if they are present in the current theme, and fall back to a standard one if not. + +const std::map> volume_icons = { + {std::numeric_limits::min(), {"emblem-unreadable"}}, + {0.0, {"audio-volume-muted"}}, + {0.33, {"audio-volume-low"}}, + {0.66, {"audio-volume-medium"}}, + {1.0, {"audio-volume-high"}}, + {std::numeric_limits::max(), {"audio-volume-high-danger", "dialog-warning"}} +}; + +const std::map> brightness_display_icons = { + {std::numeric_limits::min(), {"display-brightness-invalid", "emblem-unreadable"}}, + {0, {"display-brightness-low-symbolic", "display-brightness-low"}}, + {0.5, {"display-brightness-medium-symbolic", "display-medium-low"}}, + {1, {"display-brightness-high-symbolic", "display-brightness-high"}}, + {std::numeric_limits::max(), {"display-brightness-invalid", "emblem-unreadable"}}, +}; diff --git a/src/util/meson.build b/src/util/meson.build index aa945bcb..1b1a7933 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -2,10 +2,12 @@ util = static_library( 'util', [ 'gtk-utils.cpp', - 'wf-shell-app.cpp', + 'wf-shell-app.cpp', + 'animated-scale.cpp', 'wf-ipc.cpp', 'wf-autohide-window.cpp', - 'wf-popover.cpp' + 'wf-popover.cpp', + 'icon-select.cpp' ], dependencies: From adaf8044d65364651bae802915f6fd4f811078b8 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Tue, 19 May 2026 15:28:40 +0300 Subject: [PATCH 12/16] Further volume cleanup --- src/panel/widgets/volume.cpp | 43 ++---------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index 3a342bea..321366b2 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -8,56 +8,17 @@ #define ICON(volume) icon_from_range(volume_icons, volume) -enum VolumeLevel -{ - VOLUME_LEVEL_MUTE = 0, - VOLUME_LEVEL_LOW, - VOLUME_LEVEL_MED, - VOLUME_LEVEL_HIGH, - VOLUME_LEVEL_OOR, /* Out of range */ -}; - -static VolumeLevel get_volume_level(pa_volume_t volume, pa_volume_t max) -{ - auto third = max / 3; - if (volume == 0) - { - return VOLUME_LEVEL_MUTE; - } else if ((volume > 0) && (volume <= third)) - { - return VOLUME_LEVEL_LOW; - } else if ((volume > third) && (volume <= (third * 2))) - { - return VOLUME_LEVEL_MED; - } else if ((volume > (third * 2)) && (volume <= max)) - { - return VOLUME_LEVEL_HIGH; - } - - return VOLUME_LEVEL_OOR; -} - void WayfireVolume::update_icon() { - VolumeLevel current = - get_volume_level(volume_scale.get_target_value(), max_norm); if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream)) { - set_image_icon(main_image, "audio-volume-muted", icon_size); + set_image_icon(main_image, ICON(0), icon_size); WayfirePanelApp::get().unhide_now(); return; } - std::map icon_name_from_state = { - {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, - {VOLUME_LEVEL_LOW, "audio-volume-low"}, - {VOLUME_LEVEL_MED, "audio-volume-medium"}, - {VOLUME_LEVEL_HIGH, "audio-volume-high"}, - {VOLUME_LEVEL_OOR, "audio-volume-muted"}, - }; - - set_image_icon(main_image, icon_name_from_state.at(current), icon_size); + set_image_icon(main_image, ICON(volume_scale.get_target_value()), icon_size); WayfirePanelApp::get().unhide_now(); } From f949783e3e81dd05dc2cdf5d62036f4710ce4704 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Tue, 19 May 2026 23:39:59 +0300 Subject: [PATCH 13/16] Fix multimonitor hotplug --- src/panel/panel.cpp | 30 ++++++++++-------------------- src/panel/panel.hpp | 3 +-- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index a969155f..75914786 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -119,7 +119,6 @@ class WayfirePanel::impl bg_color.set_callback(on_window_color_updated); on_window_color_updated(); // set initial color - window->present(); window->signal_delete_event().connect( @@ -425,25 +424,6 @@ class WayfirePanelApp::impl std::map> panels; }; -void WayfirePanelApp::on_activate() -{ - WayfireShellApp::on_activate(); - - if (!ipc_server) - { - ipc_server = WayfireIPC::get_instance(); - } - - for (auto& p : priv->panels) - { - p.second->handle_config_reload(); - p.second->set_panel_app(this); - p.second->init_widgets(); - p.second->init_layout(); - } -} - - void WayfirePanelApp::on_config_reload() { for (auto& p : priv->panels) @@ -510,8 +490,18 @@ void WayfirePanelApp::add_css_file(std::string file, int priority) void WayfirePanelApp::handle_new_output(WayfireOutput *output) { + if (!ipc_server) + { + ipc_server = WayfireIPC::get_instance(); + } + priv->panels[output] = std::unique_ptr( new WayfirePanel(output)); + priv->panels[output]->handle_config_reload(); + priv->panels[output]->set_panel_app(this); + priv->panels[output]->init_widgets(); + priv->panels[output]->init_layout(); + } WayfirePanel*WayfirePanelApp::panel_for_wl_output(wl_output *output) diff --git a/src/panel/panel.hpp b/src/panel/panel.hpp index 4c63028b..fc5bfdc7 100644 --- a/src/panel/panel.hpp +++ b/src/panel/panel.hpp @@ -39,8 +39,7 @@ class WayfirePanelApp : public WayfireShellApp * call to create() */ static void create(int argc, char **argv); ~WayfirePanelApp(); - - void on_activate() override; + void handle_new_output(WayfireOutput *output) override; void handle_output_removed(WayfireOutput *output) override; void on_config_reload() override; From be834167db18df92ac2c1cfe75f4824ab4aad00d Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Wed, 20 May 2026 10:17:22 +0300 Subject: [PATCH 14/16] Fix volume icon determination --- src/panel/widgets/volume.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index 321366b2..179a0c9c 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -10,7 +10,6 @@ void WayfireVolume::update_icon() { - if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream)) { set_image_icon(main_image, ICON(0), icon_size); @@ -18,7 +17,7 @@ void WayfireVolume::update_icon() return; } - set_image_icon(main_image, ICON(volume_scale.get_target_value()), icon_size); + set_image_icon(main_image, ICON(volume_scale.get_target_value()/(double)max_norm), icon_size); WayfirePanelApp::get().unhide_now(); } From a1d4ad83d9f8735466fee4417fd92a4f8c2e4fad Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Thu, 21 May 2026 09:42:37 +0300 Subject: [PATCH 15/16] Make notification popover size configurable --- metadata/panel.xml | 14 +++++++++++++- .../widgets/notifications/notification-center.cpp | 10 +++++----- .../widgets/notifications/notification-center.hpp | 3 ++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index b25145e0..02b23003 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -279,6 +279,18 @@ <_short>Notifications Display Timeout <_long>Set to negative value to disable. 2.5 + + + + <_short>Tray diff --git a/src/panel/widgets/notifications/notification-center.cpp b/src/panel/widgets/notifications/notification-center.cpp index d91bdd1f..1613f233 100644 --- a/src/panel/widgets/notifications/notification-center.cpp +++ b/src/panel/widgets/notifications/notification-center.cpp @@ -17,7 +17,7 @@ void WayfireNotificationCenter::init(Gtk::HBox *container) button->show_all(); auto *popover = button->get_popover(); - popover->set_size_request(WIDTH, HEIGHT); + popover->set_size_request(notification_width, notification_height); popover->get_style_context()->add_class("notification-popover"); vbox.set_valign(Gtk::ALIGN_START); @@ -27,7 +27,7 @@ void WayfireNotificationCenter::init(Gtk::HBox *container) button->set_tooltip_text("Middle click to toggle DND mode."); button->signal_button_press_event().connect_notify([=] (GdkEventButton *ev) - { + { if (ev->button == 2) { dnd_enabled = !dnd_enabled; @@ -62,9 +62,10 @@ void WayfireNotificationCenter::newNotification(Notification::id_type id, bool s widget->set_reveal_child(); if (show_popup && !dnd_enabled || (show_critical_in_dnd && (notification.hints.urgency == 2))) { - auto *popover = button->get_popover(); + auto *popover = button->get_popover(); + popover->set_size_request(notification_width, notification_height); if ((timeout > 0) && (!popover_timeout.empty() || !popover->is_visible())) - { + { popover_timeout.disconnect(); popover_timeout = Glib::signal_timeout().connect( [=] @@ -76,7 +77,6 @@ void WayfireNotificationCenter::newNotification(Notification::id_type id, bool s }, timeout * 1000); } - button->set_keyboard_interactive(false); popover->popup(); } diff --git a/src/panel/widgets/notifications/notification-center.hpp b/src/panel/widgets/notifications/notification-center.hpp index 67fbc336..aa003129 100644 --- a/src/panel/widgets/notifications/notification-center.hpp +++ b/src/panel/widgets/notifications/notification-center.hpp @@ -11,7 +11,6 @@ class WayfireNotificationCenter : public WayfireWidget { private: - static const int WIDTH = 500, HEIGHT = 600; const std::shared_ptr daemon = Daemon::Launch(); sigc::connection notification_new_conn; @@ -34,6 +33,8 @@ class WayfireNotificationCenter : public WayfireWidget WfOption timeout{"panel/notifications_autohide_timeout"}; WfOption show_critical_in_dnd{"panel/notifications_critical_in_dnd"}; WfOption icon_size{"panel/notifications_icon_size"}; + WfOption notification_width{"panel/notification_width"}; + WfOption notification_height{"panel/notification_height"}; bool dnd_enabled = false; public: From bfda628248403385d49250017e60cc1dd66b6972 Mon Sep 17 00:00:00 2001 From: "mark.herbert" Date: Thu, 21 May 2026 19:22:55 +0300 Subject: [PATCH 16/16] Add wf-json commit 70039e13cdeaebd8ec498ed30bf5ab91c2e313ec --- .gitmodules | 3 +++ subprojects/wf-json | 1 + 2 files changed, 4 insertions(+) create mode 160000 subprojects/wf-json diff --git a/.gitmodules b/.gitmodules index 03ef7c62..cf4f912a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "subprojects/wayland-logout"] path = subprojects/wayland-logout url = https://github.com/soreau/wayland-logout +[submodule "subprojects/wf-json"] + path = subprojects/wf-json + url = https://github.com/WayfireWM/wf-json diff --git a/subprojects/wf-json b/subprojects/wf-json new file mode 160000 index 00000000..70039e13 --- /dev/null +++ b/subprojects/wf-json @@ -0,0 +1 @@ +Subproject commit 70039e13cdeaebd8ec498ed30bf5ab91c2e313ec