diff --git a/metadata/gtk-shell.xml b/metadata/gtk-shell.xml
index ab445d707..4ae809c0c 100644
--- a/metadata/gtk-shell.xml
+++ b/metadata/gtk-shell.xml
@@ -4,5 +4,10 @@
<_short>GTK Shell Protocol
<_long>An implementation of the gtk-shell protocol.
Utility
+
diff --git a/metadata/kde-appmenu.xml b/metadata/kde-appmenu.xml
new file mode 100644
index 000000000..2f5497b33
--- /dev/null
+++ b/metadata/kde-appmenu.xml
@@ -0,0 +1,8 @@
+
+
+
+ <_short>KDE Appmenu support
+ <_long>An implementation of the KDE Appmenu protocol for global menus.
+ Utility
+
+
diff --git a/metadata/meson.build b/metadata/meson.build
index 3cb2112a2..73b50c9ed 100644
--- a/metadata/meson.build
+++ b/metadata/meson.build
@@ -19,6 +19,7 @@ install_data('input-method-v1.xml', install_dir: conf_data.get('PLUGIN_XML_DIR')
install_data('invert.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
install_data('ipc.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
install_data('ipc-rules.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
+install_data('kde-appmenu.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
install_data('move.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
install_data('oswitch.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
install_data('output.xml', install_dir: conf_data.get('PLUGIN_XML_DIR'))
diff --git a/plugins/protocols/gtk-shell.cpp b/plugins/protocols/gtk-shell.cpp
index 1bf4adfb7..883420937 100644
--- a/plugins/protocols/gtk-shell.cpp
+++ b/plugins/protocols/gtk-shell.cpp
@@ -45,6 +45,54 @@ static void handle_gtk_surface_set_dbus_properties(wl_client *client,
{
wf::get_core().get_data_safe()->surface_app_id[surface->wl_surface] = application_id;
}
+
+ wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface);
+ if (!view)
+ {
+ LOGE("Could not get view");
+ return;
+ } else
+ {
+ if (app_menu_path && *app_menu_path)
+ {
+ view->set_property("gtk-shell-app-menu-path", app_menu_path);
+ } else
+ {
+ view->erase_property("gtk-shell-app-menu-path");
+ }
+
+ if (application_object_path && *application_object_path)
+ {
+ view->set_property("gtk-shell-application-object-path", application_object_path);
+ } else
+ {
+ view->erase_property("gtk-shell-application-object-path");
+ }
+
+ if (menubar_path && *menubar_path)
+ {
+ view->set_property("gtk-shell-menubar-path", menubar_path);
+ } else
+ {
+ view->erase_property("gtk-shell-menubar-path");
+ }
+
+ if (unique_bus_name && *unique_bus_name)
+ {
+ view->set_property("gtk-shell-unique-bus-name", unique_bus_name);
+ } else
+ {
+ view->erase_property("gtk-shell-unique-bus-name");
+ }
+
+ if (window_object_path && *window_object_path)
+ {
+ view->set_property("gtk-shell-window-object-path", window_object_path);
+ } else
+ {
+ view->erase_property("gtk-shell-window-object-path");
+ }
+ }
}
/**
@@ -331,6 +379,9 @@ void bind_gtk_shell1(wl_client *client, void *data, uint32_t version, uint32_t i
{
auto resource = wl_resource_create(client, >k_shell1_interface, GTK_SHELL_VERSION, id);
wl_resource_set_implementation(resource, >k_shell1_impl, data, handle_gtk_shell1_destroy);
+ wf::option_wrapper_t global_menu_bar{"gtk-shell/global_menu_bar"};
+ // Note: we also have the "global_app_menu" capability, but it does not seem to have any effect
+ gtk_shell1_send_capabilities(resource, global_menu_bar ? GTK_SHELL1_CAPABILITY_GLOBAL_MENU_BAR : 0);
}
class wayfire_gtk_shell_impl : public wf::plugin_interface_t
diff --git a/plugins/protocols/kde-appmenu.cpp b/plugins/protocols/kde-appmenu.cpp
new file mode 100644
index 000000000..e0d629f35
--- /dev/null
+++ b/plugins/protocols/kde-appmenu.cpp
@@ -0,0 +1,114 @@
+#include "kde-appmenu-protocol.h"
+#include "wayfire/core.hpp"
+#include "wayfire/object.hpp"
+#include
+#include
+#include
+
+#define KDE_APPMENU_VERSION 2
+
+struct wf_kde_appmenu_surface
+{
+ wl_resource *resource;
+ wl_resource *wl_surface;
+};
+
+static void handle_kde_appmenu_set_address(wl_client *client,
+ wl_resource *resource, const char *service_name,
+ const char *object_path)
+{
+ auto surface = static_cast(wl_resource_get_user_data(resource));
+ wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface);
+ if (!view)
+ {
+ LOGE("Could not get view");
+ return;
+ } else
+ {
+ if (service_name && *service_name)
+ {
+ view->set_property("kde-appmenu-service-name", service_name);
+ } else
+ {
+ view->erase_property("kde-appmenu-service-name");
+ }
+
+ if (object_path && *object_path)
+ {
+ view->set_property("kde-appmenu-object-path", object_path);
+ } else
+ {
+ view->erase_property("kde-appmenu-object-path");
+ }
+ }
+}
+
+static void handle_kde_appmenu_release(wl_client*, wl_resource*)
+{
+ /* no-op */
+}
+
+static void handle_kde_appmenu_destroy(wl_resource *resource)
+{
+ auto surface = static_cast(wl_resource_get_user_data(resource));
+ delete surface;
+}
+
+const struct org_kde_kwin_appmenu_interface kde_appmenu_impl = {
+ .set_address = handle_kde_appmenu_set_address,
+ .release = handle_kde_appmenu_release
+};
+
+
+static void handle_kde_appmenu_manager_create(wl_client *client,
+ wl_resource *resource, uint32_t id, wl_resource *surface)
+{
+ wf_kde_appmenu_surface *kde_appmenu_surface = new wf_kde_appmenu_surface;
+ kde_appmenu_surface->resource = wl_resource_create(client,
+ &org_kde_kwin_appmenu_interface, wl_resource_get_version(resource), id);
+ kde_appmenu_surface->wl_surface = surface;
+ wl_resource_set_implementation(kde_appmenu_surface->resource, &kde_appmenu_impl,
+ kde_appmenu_surface, handle_kde_appmenu_destroy);
+}
+
+static void handle_kde_appmenu_manager_release(wl_client*, wl_resource*)
+{
+ /* no-op */
+}
+
+static void handle_kde_appmenu_manager_destroy(wl_resource*)
+{
+ /* no-op */
+}
+
+static const struct org_kde_kwin_appmenu_manager_interface kde_appmenu_manager_impl = {
+ .create = handle_kde_appmenu_manager_create,
+ .release = handle_kde_appmenu_manager_release
+};
+
+
+static void bind_kde_appmenu(wl_client *client, void *data, uint32_t version, uint32_t id)
+{
+ auto resource = wl_resource_create(client, &org_kde_kwin_appmenu_manager_interface,
+ KDE_APPMENU_VERSION, id);
+ wl_resource_set_implementation(resource, &kde_appmenu_manager_impl,
+ data, handle_kde_appmenu_manager_destroy);
+}
+
+class wayfire_kde_appmenu_impl : public wf::plugin_interface_t
+{
+ public:
+ void init() override
+ {
+ auto display = wf::get_core().display;
+ wl_global_create(display, &org_kde_kwin_appmenu_manager_interface,
+ KDE_APPMENU_VERSION, NULL, bind_kde_appmenu);
+ }
+
+ bool is_unloadable() override
+ {
+ return false;
+ }
+};
+
+DECLARE_WAYFIRE_PLUGIN(wayfire_kde_appmenu_impl);
diff --git a/plugins/protocols/meson.build b/plugins/protocols/meson.build
index be87ef092..c2c4299b8 100644
--- a/plugins/protocols/meson.build
+++ b/plugins/protocols/meson.build
@@ -1,6 +1,6 @@
protocol_plugins = [
'ext-toplevel', 'foreign-toplevel', 'gtk-shell', 'wayfire-shell', 'xdg-activation', 'shortcuts-inhibit',
- 'input-method-v1', 'session-lock', 'security-context-v1',
+ 'input-method-v1', 'session-lock', 'security-context-v1', 'kde-appmenu',
]
all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs]
diff --git a/proto/kde-appmenu.xml b/proto/kde-appmenu.xml
new file mode 100644
index 000000000..7b2d7fb0b
--- /dev/null
+++ b/proto/kde-appmenu.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+ This interface allows a client to link a window (or wl_surface) to an com.canonical.dbusmenu
+ interface registered on DBus.
+
+
+
+
+
+
+
+
+
+
+
+
+ The DBus service name and object path where the appmenu interface is present
+ The object should be registered on the session bus before sending this request.
+ If not applicable, clients should remove this object.
+
+
+
+ Set or update the service name and object path.
+ Strings should be formatted in Latin-1 matching the relevant DBus specifications.
+
+
+
+
+
+
+
+
+
diff --git a/proto/meson.build b/proto/meson.build
index 338e4daaa..be28122e9 100644
--- a/proto/meson.build
+++ b/proto/meson.build
@@ -33,7 +33,8 @@ server_protocols = [
'wayfire-shell-unstable-v2.xml',
'gtk-shell.xml',
'wlr-layer-shell-unstable-v1.xml',
- 'wlr-output-power-management-unstable-v1.xml'
+ 'wlr-output-power-management-unstable-v1.xml',
+ 'kde-appmenu.xml'
]
wl_protos_src = []
diff --git a/src/api/wayfire/object.hpp b/src/api/wayfire/object.hpp
index 59705c188..f7956bb75 100644
--- a/src/api/wayfire/object.hpp
+++ b/src/api/wayfire/object.hpp
@@ -162,6 +162,11 @@ class object_base_t
return true;
}
+ void erase_property(std::string name)
+ {
+ erase_data(name);
+ }
+
object_base_t(const object_base_t &) = delete;
object_base_t(object_base_t &&) = delete;
object_base_t& operator =(const object_base_t&) = delete;
diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp
index bcbe9f51f..6ea456fd2 100644
--- a/src/api/wayfire/plugin.hpp
+++ b/src/api/wayfire/plugin.hpp
@@ -107,7 +107,7 @@ using wayfire_plugin_load_func = wf::plugin_interface_t * (*)();
/**
* The version is defined as macro as well, to allow conditional compilation.
*/
-#define WAYFIRE_API_ABI_VERSION_MACRO 2025'12'26
+#define WAYFIRE_API_ABI_VERSION_MACRO 2026'01'22
/**
* The version of Wayfire's API/ABI