aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-01-28 16:50:47 +0100
committerJavier <dev.git@javispedro.com>2014-01-28 16:50:47 +0100
commitc2d6d46b9ca89cb1c0729ee599c3566d3d1e7cf7 (patch)
tree7e1e2285437816c06ac884d68284d854413cf41e
downloadtopmenu-gtk-c2d6d46b9ca89cb1c0729ee599c3566d3d1e7cf7.tar.gz
topmenu-gtk-c2d6d46b9ca89cb1c0729ee599c3566d3d1e7cf7.zip
initial import
-rw-r--r--.gitignore35
-rw-r--r--Makefile.am4
-rw-r--r--config.h.in74
-rw-r--r--configure.ac89
-rw-r--r--global.h18
-rw-r--r--libtopmenu-client/Makefile.am8
-rw-r--r--libtopmenu-client/topmenu-appmenubar.c86
-rw-r--r--libtopmenu-client/topmenu-appmenubar.h38
-rw-r--r--libtopmenu-client/topmenu-client.c113
-rw-r--r--libtopmenu-client/topmenu-client.h13
-rw-r--r--libtopmenu-client/topmenu-menubar-proxy.h44
-rw-r--r--libtopmenu-client/topmenu-monitor.c163
-rw-r--r--libtopmenu-client/topmenu-monitor.h39
-rw-r--r--libtopmenu-server/Makefile.am7
-rw-r--r--libtopmenu-server/topmenu-server.c83
-rw-r--r--libtopmenu-server/topmenu-server.h9
-rw-r--r--libtopmenu-server/topmenu-widget.c390
-rw-r--r--libtopmenu-server/topmenu-widget.h39
-rw-r--r--mate-applet/Makefile.am29
-rw-r--r--mate-applet/com.javispedro.topmenu.MatePanelApplet.mate-panel-applet.in10
-rw-r--r--mate-applet/main.c21
-rw-r--r--mate-applet/org.mate.panel.applet.TopMenuMatePanelAppletFactory.service.in3
-rw-r--r--mate-applet/topmenu-mate-panel-applet.c82
-rw-r--r--mate-applet/topmenu-mate-panel-applet.h38
-rw-r--r--module/Makefile.am9
-rw-r--r--module/appmenu.c222
-rw-r--r--module/appmenu.h21
-rw-r--r--module/data.c156
-rw-r--r--module/data.h48
-rw-r--r--module/main.c724
-rw-r--r--module/menuitem-proxy.c421
-rw-r--r--module/menuitem-proxy.h8
-rw-r--r--test/Makefile.am12
-rw-r--r--test/client.c79
-rw-r--r--test/server.c40
35 files changed, 3175 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c41ed4b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+*.creator.user
+*.am.user
+config.h
+.deps
+.libs
+*~
+*.o
+*.la
+*.lo
+mate-applet/com.javispedro.topmenu.MatePanelApplet.mate-panel-applet
+mate-applet/org.mate.panel.applet.TopMenuMatePanelAppletFactory.service
+mate-applet/topmenu-mate-panel-applet
+test/client
+test/server
+missing
+compile
+m4/libtool.m4
+m4/lt*.m4
+ltmain.sh
+libtool
+texinfo.tex
+Makefile
+Makefile.in
+Makefile.msc
+aclocal.m4
+autom4te.cache
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+INSTALL
+install-sh
+stamp-h1
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..b1458e4
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,4 @@
+AUTOMAKE_OPTIONS = foreign
+SUBDIRS = libtopmenu-server libtopmenu-client module test mate-applet
+
+noinst_HEADERS = global.h
diff --git a/config.h.in b/config.h.in
new file mode 100644
index 0000000..a566d64
--- /dev/null
+++ b/config.h.in
@@ -0,0 +1,74 @@
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define if you have libmatepanelapplet */
+#undef HAVE_MATEPANELAPPLET
+
+/* Define if you have libmatewnck */
+#undef HAVE_MATEWNCK
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define if you have libwnck-1.0 */
+#undef HAVE_WNCK1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#undef LT_OBJDIR
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+#undef NO_MINUS_C_MINUS_O
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..56af2b4
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,89 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.69])
+AC_INIT(libtopmenu-gtk, 1.0, javier@javispedro.com)
+AC_CONFIG_SRCDIR([libtopmenu-server/topmenu-server.h])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([foreign])
+
+AC_CONFIG_MACRO_DIR([m4])
+
+AC_ARG_WITH([gtk],
+ [AS_HELP_STRING([--with-gtk=2|3], [GTK+ version [default=2]])],
+ [],
+ [with_gtk=2])
+AC_ARG_WITH([gtk-libdir],
+ [AS_HELP_STRING([--with-gtk-libdir=DIR], [GTK+ library directory [default=`pkg-config --variable=libdir gtk+-3.0`]])],
+ [],
+ [with_gtk_libdir=`pkg-config --variable=libdir gtk+-\$with_gtk.0`])
+AC_ARG_WITH([gtk-module-dir],
+ [AS_HELP_STRING([--with-gtk-module-dir=DIR], [GTK+ module directory [default=`pkg-config --variable=libdir gtk+-3.0`/gtk-3.0/modules]])],
+ [],
+ [with_gtk_module_dir=$with_gtk_libdir/gtk-$with_gtk.0/modules])
+AC_ARG_WITH([wnck],
+ [AS_HELP_STRING([--with-wnck], [support window management using [wnck1|matewnck] @<:@default=check@:>@])],
+ [],
+ [with_wnck=check])
+
+AC_ARG_ENABLE([mate-applet],
+ [AS_HELP_STRING([--enable-mate-applet], [build the Mate panel applet @<:@default=check@:>@])],
+ [],
+ [enable_mate_applet=check])
+
+AC_SUBST([GTK_VERSION], [$with_gtk])
+AC_SUBST([GTK_MODULE_DIR], [$with_gtk_module_dir])
+
+# Checks for programs.
+AC_PROG_CC
+AM_PROG_CC_C_O
+AC_PROG_LIBTOOL
+
+# Checks for libraries.
+PKG_CHECK_MODULES([GTK], [gtk+-x11-$with_gtk.0])
+
+AS_IF([test "x$with_wnck" = xwnck1 -o "x$with_wnck" = xcheck],
+ [PKG_CHECK_MODULES([WNCK], [libwnck-1.0],
+ [
+ AC_DEFINE([HAVE_WNCK1], [1], [Define if you have libwnck-1.0])
+ with_wnck=libwnck1
+ ],
+ [if test "x$with_wnck" = xwnck1; then
+ AC_MSG_FAILURE([--with-wnck=wnck1 was given, but test for libwnck-1.0 failed])
+ fi]
+ )])
+AS_IF([test "x$with_wnck" = xmatewnck -o "x$with_wnck" = xcheck],
+ [PKG_CHECK_MODULES([MATEWNCK], [libmatewnck],
+ [
+ AC_DEFINE([HAVE_MATEWNCK], [1], [Define if you have libmatewnck])
+ with_wnck=libmatewnck
+ ],
+ [if test "x$with_wnck" = xmatewnck; then
+ AC_MSG_FAILURE([--with-wnck=matewnck was given, but test for libmatewnck failed])
+ fi]
+ )])
+
+AS_IF([test "x$enable_mate_applet" != xno],
+ [PKG_CHECK_MODULES([MATEPANELAPPLET], [libmatepanelapplet-4.0],
+ [
+ AC_DEFINE([HAVE_MATEPANELAPPLET], [1], [Define if you have libmatepanelapplet])
+ enable_mate_applet=yes
+ ],
+ [if test "x$enable_mate_applet" = xyes; then
+ AC_MSG_FAILURE([--enable-mate-applet was given, but test for libmatepanelapplet failed])
+ fi]
+ )])
+
+AM_CONDITIONAL([WANT_MATE_APPLET], [test x$enable_mate_applet = xyes])
+
+# Output files
+AC_CONFIG_FILES([
+ Makefile
+ libtopmenu-client/Makefile
+ libtopmenu-server/Makefile
+ module/Makefile
+ mate-applet/Makefile
+ test/Makefile
+])
+
+AC_OUTPUT
diff --git a/global.h b/global.h
new file mode 100644
index 0000000..f10a738
--- /dev/null
+++ b/global.h
@@ -0,0 +1,18 @@
+#ifndef _TOPMENU_GLOBAL_H_
+#define _TOPMENU_GLOBAL_H_
+
+/* Private definitions that are common to entire project. */
+
+#include "config.h"
+
+/** The window ID of this top level's window attached menu window. */
+#define ATOM_TOPMENU_WINDOW "TOPMENU_WINDOW"
+
+/** The X11 selection that is used to indicate the current server widget. */
+#define ATOM_TOPMENU_SERVER_SELECTION "TOPMENU_SERVER"
+
+/* Gobject data keys */
+#define OBJECT_DATA_KEY_PLUG "topmenu-plug"
+#define OBJECT_DATA_KEY_SERVER_STUB "topmenu-server-stub"
+
+#endif
diff --git a/libtopmenu-client/Makefile.am b/libtopmenu-client/Makefile.am
new file mode 100644
index 0000000..b57343b
--- /dev/null
+++ b/libtopmenu-client/Makefile.am
@@ -0,0 +1,8 @@
+lib_LTLIBRARIES = libtopmenu-client.la
+libtopmenu_client_la_SOURCES = topmenu-client.c topmenu-client.h \
+ topmenu-monitor.c topmenu-monitor.h \
+ topmenu-appmenubar.c topmenu-appmenubar.h
+libtopmenu_client_la_CPPFLAGS = $(GTK_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-client\"
+libtopmenu_client_la_LIBADD = $(GTK_LIBS)
+
+include_HEADERS = topmenu-client.h topmenu-monitor.h topmenu-appmenubar.h
diff --git a/libtopmenu-client/topmenu-appmenubar.c b/libtopmenu-client/topmenu-appmenubar.c
new file mode 100644
index 0000000..7d94cf5
--- /dev/null
+++ b/libtopmenu-client/topmenu-appmenubar.c
@@ -0,0 +1,86 @@
+#include "topmenu-appmenubar.h"
+
+G_DEFINE_TYPE(TopMenuAppMenuBar, topmenu_app_menu_bar, GTK_TYPE_MENU_BAR)
+
+enum {
+ PROP_0,
+ PROP_APP_MENU,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL };
+
+static void topmenu_app_menu_bar_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ TopMenuAppMenuBar *self = TOPMENU_APP_MENU_BAR(obj);
+ switch (property_id) {
+ case PROP_APP_MENU:
+ g_value_set_object(value, topmenu_app_menu_bar_get_app_menu(self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
+ }
+}
+
+static void topmenu_app_menu_bar_set_property(GObject *obj, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ TopMenuAppMenuBar *self = TOPMENU_APP_MENU_BAR(obj);
+ switch (property_id) {
+ case PROP_APP_MENU:
+ topmenu_app_menu_bar_set_app_menu(self, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
+ }
+}
+
+static void topmenu_app_menu_bar_class_init(TopMenuAppMenuBarClass *klass)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ obj_class->get_property = topmenu_app_menu_bar_get_property;
+ obj_class->set_property = topmenu_app_menu_bar_set_property;
+
+ properties[PROP_APP_MENU] = g_param_spec_object("app-menu",
+ "Application menu",
+ "The application menu (shown under the application name)",
+ GTK_TYPE_MENU,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ gtk_rc_parse_string (
+ "style \"app-menubar-style\"\n"
+ "{\n"
+ " GtkMenuBar::shadow-type = none\n"
+ " GtkMenuBar::internal-padding = 0\n"
+ "}\n"
+ "class \"TopMenuAppMenuBar\" style \"app-menubar-style\"");
+}
+
+static void topmenu_app_menu_bar_init(TopMenuAppMenuBar *self)
+{
+ self->app_menu_item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(g_get_application_name()));
+ GtkLabel *app_label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(self->app_menu_item)));
+ PangoAttrList *app_label_attr = pango_attr_list_new();
+ pango_attr_list_insert(app_label_attr, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
+ gtk_label_set_attributes(app_label, app_label_attr);
+ pango_attr_list_unref(app_label_attr);
+ gtk_widget_show(GTK_WIDGET(self->app_menu_item));
+
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(self), GTK_WIDGET(self->app_menu_item));
+}
+
+TopMenuAppMenuBar *topmenu_app_menu_bar_new(void)
+{
+ return TOPMENU_APP_MENU_BAR(g_object_new(TOPMENU_TYPE_APP_MENU_BAR, NULL));
+}
+
+void topmenu_app_menu_bar_set_app_menu(TopMenuAppMenuBar *self, GtkWidget *menu)
+{
+ gtk_menu_item_set_submenu(self->app_menu_item, GTK_WIDGET(menu));
+}
+
+GtkWidget *topmenu_app_menu_bar_get_app_menu(TopMenuAppMenuBar *self)
+{
+ return gtk_menu_item_get_submenu(self->app_menu_item);
+}
diff --git a/libtopmenu-client/topmenu-appmenubar.h b/libtopmenu-client/topmenu-appmenubar.h
new file mode 100644
index 0000000..e2e1344
--- /dev/null
+++ b/libtopmenu-client/topmenu-appmenubar.h
@@ -0,0 +1,38 @@
+#ifndef _TOPMENU_APPMENUBAR_H_
+#define _TOPMENU_APPMENUBAR_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TOPMENU_TYPE_APP_MENU_BAR topmenu_app_menu_bar_get_type()
+#define TOPMENU_APP_MENU_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_APP_MENU_BAR, TopMenuAppMenuBar))
+#define TOPMENU_IS_APP_MENU_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_APP_MENU_BAR))
+#define TOPMENU_APP_MENU_BAR_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_APP_MENU_BAR, TopMenuAppMenuBarClass))
+#define TOPMENU_IS_APP_MENU_BAR_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_APP_MENU_BAR))
+#define TOPMENU_APP_MENU_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_APP_MENU_BAR, TopMenuAppMenuBarClass))
+
+typedef struct _TopMenuAppMenuBar TopMenuAppMenuBar;
+typedef struct _TopMenuAppMenuBarClass TopMenuAppMenuBarClass;
+
+struct _TopMenuAppMenuBar
+{
+ GtkMenuBar parent_instance;
+ GtkMenuItem *app_menu_item;
+};
+
+struct _TopMenuAppMenuBarClass
+{
+ GtkMenuBarClass parent_class;
+};
+
+GType topmenu_app_menu_bar_get_type(void);
+
+TopMenuAppMenuBar *topmenu_app_menu_bar_new(void);
+
+void topmenu_app_menu_bar_set_app_menu(TopMenuAppMenuBar *self, GtkWidget *menu);
+GtkWidget *topmenu_app_menu_bar_get_app_menu(TopMenuAppMenuBar *self);
+
+G_END_DECLS
+
+#endif /* _TOPMENU_APP_MENU_BAR_H_ */
diff --git a/libtopmenu-client/topmenu-client.c b/libtopmenu-client/topmenu-client.c
new file mode 100644
index 0000000..63e27bc
--- /dev/null
+++ b/libtopmenu-client/topmenu-client.c
@@ -0,0 +1,113 @@
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+
+#include "../global.h"
+
+#include "topmenu-client.h"
+
+#define OBJECT_DATA_KEY_PLUG "topmenu-plug"
+
+static gboolean handle_plug_delete(GtkPlug *plug, GdkEvent *event, GdkWindow *window)
+{
+ return TRUE; // Prevent deletion of plug window
+}
+
+static gboolean handle_widget_button_event(GtkWidget *widget, GdkEvent *event, GtkPlug *plug)
+{
+ if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) {
+ return FALSE;
+ }
+ if (event->button.button == 1) {
+ return FALSE;
+ }
+
+ GdkWindow *socket = gtk_plug_get_socket_window(plug);
+ if (socket) {
+ GdkScreen *screen = gdk_window_get_screen(socket);
+ GdkWindow *root = gdk_screen_get_root_window(screen);
+ Display *dpy = GDK_WINDOW_XDISPLAY(socket);
+ Window xwin = GDK_WINDOW_XWINDOW(socket);
+
+ if (event->type == GDK_BUTTON_PRESS) {
+ gdk_display_pointer_ungrab(gtk_widget_get_display(widget),
+ GDK_CURRENT_TIME);
+ }
+
+ XEvent e;
+ long mask = event->type == GDK_BUTTON_PRESS ? ButtonPressMask : ButtonReleaseMask;
+ e.type = event->type == GDK_BUTTON_PRESS ? ButtonPress : ButtonRelease;
+ e.xbutton.window = xwin;
+ e.xbutton.display = dpy;
+ e.xbutton.root = GDK_WINDOW_XWINDOW(root);
+ e.xbutton.time = event->button.time;
+ e.xbutton.button = event->button.button;
+ e.xbutton.state = event->button.state;
+ e.xbutton.x = event->button.x;
+ e.xbutton.y = event->button.y;
+ e.xbutton.x_root = event->button.x_root;
+ e.xbutton.y_root = event->button.y_root;
+ e.xbutton.same_screen = True;
+
+ gdk_error_trap_push();
+ XSendEvent(dpy, xwin, True, mask, &e);
+ g_debug("Forwarding button %d %s event to 0x%lx",
+ e.xbutton.button,
+ event->type == GDK_BUTTON_PRESS ? "press" : "release",
+ xwin);
+ gdk_flush();
+ gdk_error_trap_pop();
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void topmenu_client_connect_window_widget(GdkWindow *window, GtkWidget *widget)
+{
+ Display *display = GDK_WINDOW_XDISPLAY(window);
+
+ if (g_object_get_data(G_OBJECT(window), OBJECT_DATA_KEY_PLUG)) {
+ topmenu_client_disconnect_window(window);
+ }
+
+ Window xwin = GDK_WINDOW_XID(window);
+ GtkPlug *plug = GTK_PLUG(gtk_plug_new(0));
+ gtk_container_add(GTK_CONTAINER(plug), widget);
+ g_signal_connect_object(plug, "delete-event",
+ G_CALLBACK(handle_plug_delete), window, 0);
+ g_signal_connect_object(widget, "button-press-event",
+ G_CALLBACK(handle_widget_button_event), plug, 0);
+ g_signal_connect_object(widget, "button-release-event",
+ G_CALLBACK(handle_widget_button_event), plug, 0);
+ gtk_widget_show(GTK_WIDGET(plug));
+
+ Window plug_xwin = gtk_plug_get_id(plug);
+
+ Atom atom = XInternAtom(display, ATOM_TOPMENU_WINDOW, False);
+
+ XChangeProperty(display, xwin, atom,
+ XA_WINDOW, 32, PropModeReplace,
+ (unsigned char*)&plug_xwin, 1);
+
+ g_object_set_data_full(G_OBJECT(window), OBJECT_DATA_KEY_PLUG, plug, (GDestroyNotify)&gtk_widget_destroy);
+}
+
+void topmenu_client_disconnect_window(GdkWindow *window)
+{
+ Display *display = GDK_WINDOW_XDISPLAY(window);
+
+ gpointer window_data = g_object_steal_data(G_OBJECT(window), OBJECT_DATA_KEY_PLUG);
+ g_return_if_fail(window_data);
+
+ Window xwin = GDK_WINDOW_XID(window);
+
+ GtkPlug *plug = GTK_PLUG(window_data);
+ g_return_if_fail(plug);
+
+ Atom atom = XInternAtom(display, ATOM_TOPMENU_WINDOW, False);
+
+ XDeleteProperty(display, xwin, atom);
+
+ g_object_unref(plug);
+}
diff --git a/libtopmenu-client/topmenu-client.h b/libtopmenu-client/topmenu-client.h
new file mode 100644
index 0000000..f65eb69
--- /dev/null
+++ b/libtopmenu-client/topmenu-client.h
@@ -0,0 +1,13 @@
+#ifndef _TOPMENU_CLIENT_H_
+#define _TOPMENU_CLIENT_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void topmenu_client_connect_window_widget(GdkWindow *window, GtkWidget *widget);
+void topmenu_client_disconnect_window(GdkWindow *window);
+
+G_END_DECLS
+
+#endif
diff --git a/libtopmenu-client/topmenu-menubar-proxy.h b/libtopmenu-client/topmenu-menubar-proxy.h
new file mode 100644
index 0000000..e601e04
--- /dev/null
+++ b/libtopmenu-client/topmenu-menubar-proxy.h
@@ -0,0 +1,44 @@
+#ifndef _TOPMENU_MENUBAR_PROXY_H_
+#define _TOPMENU_MENUBAR_PROXY_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TOPMENU_TYPE_MENUBAR_PROXY topmenu_menubar_proxy_get_type()
+#define TOPMENU_MENUBAR_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_MENUBAR_PROXY, TopMenuMenuBarProxy))
+#define TOPMENU_IS_MENUBAR_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_MENUBAR_PROXY))
+#define TOPMENU_MENUBAR_PROXY_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_MENUBAR_PROXY, TopMenuMenuBarProxyClass))
+#define TOPMENU_IS_MENUBAR_PROXY_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_MENUBAR_PROXY))
+#define TOPMENU_MENUBAR_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_MENUBAR_PROXY, TopMenuMenuBarProxyClass))
+
+typedef struct _TopMenuMenuBarProxy TopMenuMenuBarProxy;
+typedef struct _TopMenuMenuBarProxyClass TopMenuMenuBarProxyClass;
+typedef struct _TopMenuMenuBarProxyPrivate TopMenuMenuBarProxyPrivate;
+
+struct _TopMenuMenuBarProxy
+{
+ GtkMenuBar parent_instance;
+ TopMenuMenuBarProxyPrivate *priv;
+ GtkMenuItem *app_menu_item;
+ GtkMenu *app_menu;
+};
+
+struct _TopMenuMenuBarProxyClass
+{
+ GtkMenuBarClass parent_class;
+};
+
+GType topmenu_menubar_proxy_get_type(void);
+
+TopMenuMenuBarProxy *topmenu_menubar_proxy_new(void);
+
+void topmenu_menubar_proxy_add_menu(TopMenuMenuBarProxy *self, GtkMenuShell *shell);
+void topmenu_menubar_proxy_remove_menu(TopMenuMenuBarProxy *self, GtkMenuShell *shell);
+
+typedef enum _MenuItemRole MenuItemRole;
+void topmenu_menubar_proxy_add_app_menu_item(TopMenuMenuBarProxy *self, GtkMenuItem *item, MenuItemRole role);
+
+G_END_DECLS
+
+#endif /* _TOPMENU_MENUBAR_PROXY_H_ */
diff --git a/libtopmenu-client/topmenu-monitor.c b/libtopmenu-client/topmenu-monitor.c
new file mode 100644
index 0000000..abd7522
--- /dev/null
+++ b/libtopmenu-client/topmenu-monitor.c
@@ -0,0 +1,163 @@
+#include <X11/Xatom.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+#include "../global.h"
+
+#include "topmenu-monitor.h"
+
+struct _TopMenuMonitorPrivate
+{
+ GdkAtom atom_selection;
+ GtkClipboard *selection;
+ GdkWindow *cur_server;
+};
+
+enum {
+ PROP_0,
+ PROP_AVAILABLE,
+ N_PROPERTIES
+};
+
+G_DEFINE_TYPE(TopMenuMonitor, topmenu_monitor, G_TYPE_OBJECT)
+
+#define TOPMENU_MONITOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TOPMENU_TYPE_MONITOR, TopMenuMonitorPrivate))
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL };
+
+static void topmenu_monitor_update(TopMenuMonitor *self);
+
+static void handle_clipboard_owner_change(GtkClipboard *clipboard, GdkEvent *event, TopMenuMonitor *self)
+{
+ topmenu_monitor_update(self);
+}
+
+static GdkFilterReturn handle_cur_server_event(GdkXEvent *xevent, GdkEvent *event, gpointer data)
+{
+ XEvent *e = (XEvent*)xevent;
+ if (e->type == DestroyNotify) {
+ g_debug("Current server has been destroyed");
+ TopMenuMonitor *self = TOPMENU_MONITOR(data);
+ if (self->priv->cur_server &&
+ GDK_WINDOW_XWINDOW(self->priv->cur_server) == e->xdestroywindow.window) {
+ topmenu_monitor_update(self);
+ }
+ }
+ return GDK_FILTER_CONTINUE;
+}
+
+static void topmenu_monitor_set_cur_server(TopMenuMonitor *self, GdkWindow *window)
+{
+ if (self->priv->cur_server == window) {
+ // Nothing to do
+ return;
+ }
+ g_debug("Setting current server to 0x%lx", GDK_WINDOW_XWINDOW(window));
+ if (self->priv->cur_server) {
+ gdk_window_remove_filter(window, handle_cur_server_event, self);
+ gdk_window_unref(self->priv->cur_server);
+ self->priv->cur_server = 0;
+ }
+ if (window) {
+ gdk_window_set_events(window, gdk_window_get_events(window) | GDK_STRUCTURE_MASK);
+ gdk_window_add_filter(window, handle_cur_server_event, self);
+ self->priv->cur_server = window;
+ }
+ if (self->priv->cur_server && !self->available) {
+ // Signal availability
+ self->available = TRUE;
+ g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_AVAILABLE]);
+ } else if (!self->priv->cur_server && self->available) {
+ // Signal no availability
+ self->available = FALSE;
+ g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_AVAILABLE]);
+ }
+}
+
+static void topmenu_monitor_update(TopMenuMonitor *self)
+{
+ GdkScreen *screen = gdk_screen_get_default();
+ GdkDisplay *display = gdk_screen_get_display(screen);
+
+ Display *xdpy = GDK_DISPLAY_XDISPLAY(display);
+ Atom atom = gdk_x11_atom_to_xatom_for_display(display, self->priv->atom_selection);
+
+ Window xwin = XGetSelectionOwner(xdpy, atom);
+
+ if (xwin) {
+ GdkWindow *window = gdk_x11_window_foreign_new_for_display(display, xwin);
+ topmenu_monitor_set_cur_server(self, window);
+ } else {
+ topmenu_monitor_set_cur_server(self, NULL);
+ }
+}
+
+static void topmenu_monitor_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ TopMenuMonitor *self = TOPMENU_MONITOR(obj);
+ switch (property_id) {
+ case PROP_AVAILABLE:
+ g_value_set_boolean(value, self->available);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
+ }
+}
+
+static void topmenu_monitor_dispose(GObject *obj)
+{
+ TopMenuMonitor *self = TOPMENU_MONITOR(obj);
+ if (self->priv->cur_server) {
+ gdk_window_remove_filter(self->priv->cur_server,
+ handle_cur_server_event, self);
+ gdk_window_unref(self->priv->cur_server);
+ self->priv->cur_server = 0;
+ }
+ self->priv->selection = NULL;
+ G_OBJECT_CLASS(topmenu_monitor_parent_class)->dispose(obj);
+}
+
+static void topmenu_monitor_class_init(TopMenuMonitorClass *klass)
+{
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ obj_class->get_property = topmenu_monitor_get_property;
+ obj_class->dispose = topmenu_monitor_dispose;
+
+ properties[PROP_AVAILABLE] = g_param_spec_boolean("available",
+ "TopMenu's availability",
+ "Set to TRUE whether a TopMenu server is currently available",
+ FALSE,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ g_type_class_add_private(klass, sizeof(TopMenuMonitorPrivate));
+}
+
+static void topmenu_monitor_init(TopMenuMonitor *self)
+{
+ self->priv = TOPMENU_MONITOR_GET_PRIVATE(self);
+ self->available = FALSE;
+
+ self->priv->atom_selection = gdk_atom_intern_static_string(ATOM_TOPMENU_SERVER_SELECTION);
+ self->priv->selection = gtk_clipboard_get(self->priv->atom_selection);
+ self->priv->cur_server = NULL;
+
+ g_signal_connect_object(self->priv->selection, "owner-change",
+ G_CALLBACK(handle_clipboard_owner_change), self, 0);
+
+ topmenu_monitor_update(self);
+}
+
+TopMenuMonitor * topmenu_monitor_get_instance()
+{
+ static TopMenuMonitor *instance = NULL;
+ if (!instance) {
+ instance = TOPMENU_MONITOR(g_object_new(TOPMENU_TYPE_MONITOR, NULL));
+ }
+ return instance;
+}
+
+gboolean topmenu_monitor_is_topmenu_available(TopMenuMonitor * self)
+{
+ return self->available;
+}
diff --git a/libtopmenu-client/topmenu-monitor.h b/libtopmenu-client/topmenu-monitor.h
new file mode 100644
index 0000000..afd066c
--- /dev/null
+++ b/libtopmenu-client/topmenu-monitor.h
@@ -0,0 +1,39 @@
+#ifndef _TOPMENU_MONITOR_H_
+#define _TOPMENU_MONITOR_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define TOPMENU_TYPE_MONITOR topmenu_monitor_get_type()
+#define TOPMENU_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_MONITOR, TopMenuMonitor))
+#define TOPMENU_IS_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_MONITOR))
+#define TOPMENU_MONITOR_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_MONITOR, TopMenuMonitorClass))
+#define TOPMENU_IS_MONITOR_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_MONITOR))
+#define TOPMENU_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_MONITOR, TopMenuMonitorClass))
+
+typedef struct _TopMenuMonitor TopMenuMonitor;
+typedef struct _TopMenuMonitorClass TopMenuMonitorClass;
+typedef struct _TopMenuMonitorPrivate TopMenuMonitorPrivate;
+
+struct _TopMenuMonitor
+{
+ GObject parent_instance;
+ TopMenuMonitorPrivate *priv;
+ gboolean available;
+};
+
+struct _TopMenuMonitorClass
+{
+ GObjectClass parent_class;
+};
+
+GType topmenu_monitor_get_type(void);
+
+TopMenuMonitor * topmenu_monitor_get_instance(void);
+
+gboolean topmenu_monitor_is_topmenu_available(TopMenuMonitor * self);
+
+G_END_DECLS
+
+#endif /* _TOPMENU_MONITOR_H_ */
diff --git a/libtopmenu-server/Makefile.am b/libtopmenu-server/Makefile.am
new file mode 100644
index 0000000..8a17f5f
--- /dev/null
+++ b/libtopmenu-server/Makefile.am
@@ -0,0 +1,7 @@
+lib_LTLIBRARIES = libtopmenu-server.la
+libtopmenu_server_la_SOURCES = topmenu-server.c topmenu-server.h topmenu-widget.c topmenu-widget.h
+libtopmenu_server_la_CPPFLAGS = $(GTK_CFLAGS) $(WNCK_CFLAGS) $(MATEWNCK_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-server\"
+libtopmenu_server_la_LIBADD = $(GTK_LIBS) $(WNCK_LIBS) $(MATEWNCK_LIBS)
+
+include_HEADERS = topmenu-server.h topmenu-widget.h
+
diff --git a/libtopmenu-server/topmenu-server.c b/libtopmenu-server/topmenu-server.c
new file mode 100644
index 0000000..a907d47
--- /dev/null
+++ b/libtopmenu-server/topmenu-server.c
@@ -0,0 +1,83 @@
+#include <gtk/gtk.h>
+
+#include "topmenu-server.h"
+
+#include "../global.h"
+
+static GdkAtom selection_atom = GDK_NONE;
+static GtkClipboard *selection_clipboard = NULL;
+static GList *server_widgets = NULL;
+
+static void handle_selection_owner_change(GtkClipboard *clipboard, GdkEvent *event, gpointer user_data);
+
+static void init_selection_monitor()
+{
+ if (!selection_clipboard || selection_atom == GDK_NONE) {
+ selection_atom = gdk_atom_intern_static_string(ATOM_TOPMENU_SERVER_SELECTION);
+ selection_clipboard = gtk_clipboard_get(selection_atom);
+ // Used to monitor the current owner of the server selection
+ g_signal_connect(selection_clipboard, "owner-change",
+ G_CALLBACK(handle_selection_owner_change), NULL);
+ }
+}
+
+/** Returns the current stub this process wants to set as owner of the server_selection. */
+static GdkWindow *get_front_server_stub()
+{
+ if (server_widgets) {
+ GtkWidget *widget = server_widgets->data;
+ gpointer data = g_object_get_data(G_OBJECT(widget), OBJECT_DATA_KEY_SERVER_STUB);
+ g_return_val_if_fail(data, NULL);
+ return GDK_WINDOW(data);
+ } else {
+ return NULL;
+ }
+}
+
+static void update_selection_owner(guint32 time)
+{
+ GdkWindow *our_owner = get_front_server_stub();
+ if (our_owner == NULL) return; // Nothing to do
+
+ GdkWindow *cur_owner = gdk_selection_owner_get(selection_atom);
+ if (cur_owner != our_owner) {
+ g_debug("Setting this process as owner of the selection");
+ int res = gdk_selection_owner_set(our_owner, selection_atom, time, TRUE);
+ g_debug("Result = %d", res);
+ }
+}
+
+static void handle_selection_owner_change(GtkClipboard *clipboard, GdkEvent *event, gpointer user_data)
+{
+ update_selection_owner(event->selection.time);
+}
+
+void topmenu_server_register_server_widget(GtkWidget *widget)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ g_return_if_fail(window);
+
+ init_selection_monitor();
+
+ g_return_if_fail(g_object_get_data(G_OBJECT(widget), OBJECT_DATA_KEY_SERVER_STUB) == NULL);
+
+ GdkWindowAttr stub_attr = { 0 };
+ stub_attr.wclass = GDK_INPUT_ONLY;
+ stub_attr.override_redirect = TRUE;
+ GdkWindow *stub = gdk_window_new(window, &stub_attr, GDK_WA_NOREDIR);
+
+ g_object_set_data_full(G_OBJECT(widget), OBJECT_DATA_KEY_SERVER_STUB,
+ stub, (GDestroyNotify)gdk_window_destroy);
+
+ server_widgets = g_list_prepend(server_widgets, widget);
+ update_selection_owner(GDK_CURRENT_TIME);
+}
+
+void topmenu_server_unregister_server_widget(GtkWidget *widget)
+{
+ g_return_if_fail(g_object_get_data(G_OBJECT(widget), OBJECT_DATA_KEY_SERVER_STUB) != NULL);
+ server_widgets = g_list_remove_all(server_widgets, widget);
+ gpointer data = g_object_steal_data(G_OBJECT(widget), OBJECT_DATA_KEY_SERVER_STUB);
+ GdkWindow *stub = GDK_WINDOW(data);
+ gdk_window_destroy(stub);
+}
diff --git a/libtopmenu-server/topmenu-server.h b/libtopmenu-server/topmenu-server.h
new file mode 100644
index 0000000..4ca9084
--- /dev/null
+++ b/libtopmenu-server/topmenu-server.h
@@ -0,0 +1,9 @@
+#ifndef _TOPMENU_SERVER_H_
+#define _TOPMENU_SERVER_H_
+
+#include <gtk/gtk.h>
+
+void topmenu_server_register_server_widget(GtkWidget *widget);
+void topmenu_server_unregister_server_widget(GtkWidget *widget);
+
+#endif
diff --git a/libtopmenu-server/topmenu-widget.c b/libtopmenu-server/topmenu-widget.c
new file mode 100644
index 0000000..0d21be6
--- /dev/null
+++ b/libtopmenu-server/topmenu-widget.c
@@ -0,0 +1,390 @@
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+#include "../global.h"
+
+#include "topmenu-widget.h"
+#include "topmenu-server.h"
+
+#ifdef HAVE_MATEWNCK
+#include <libmatewnck/libmatewnck.h>
+#endif
+
+struct _TopMenuWidgetPrivate
+{
+ Atom atom_window;
+ Atom atom_transient_for;
+ GQueue followed_windows;
+#ifdef HAVE_MATEWNCK
+ MatewnckScreen *screen;
+#endif
+};
+
+G_DEFINE_TYPE(TopMenuWidget, topmenu_widget, GTK_TYPE_BIN)
+
+#define TOPMENU_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), TOPMENU_TYPE_WIDGET, TopMenuWidgetPrivate))
+
+static Window read_window_property(Display *dpy, Window window, Atom property)
+{
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop_return;
+
+ if (XGetWindowProperty(dpy, window, property,
+ 0, sizeof(Window), False,
+ XA_WINDOW, &actual_type, &actual_format, &nitems,
+ &bytes_after, &prop_return) == Success) {
+ if (prop_return && actual_type == XA_WINDOW) {
+ return *(Window*)prop_return;
+ }
+ }
+
+ return None;
+}
+
+static Display * topmenu_widget_get_display(TopMenuWidget *self)
+{
+ GdkWindow *gdk_win = gtk_widget_get_window(GTK_WIDGET(self));
+ if (gdk_win) {
+ return GDK_WINDOW_XDISPLAY(gdk_win);
+ }
+ return NULL;
+}
+
+static Window topmenu_widget_get_toplevel_xwindow(TopMenuWidget *self)
+{
+ GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(self));
+ GdkWindow *window = gtk_widget_get_window(toplevel);
+ if (window) {
+ return GDK_WINDOW_XID(window);
+ } else {
+ return None;
+ }
+}
+
+static Window topmenu_widget_get_current_active_window(TopMenuWidget *self)
+{
+#ifdef HAVE_MATEWNCK
+ MatewnckWindow *window = matewnck_screen_get_active_window(self->priv->screen);
+ return matewnck_window_get_xid(window);
+#else
+ return None;
+#endif
+}
+
+static Window topmenu_widget_get_session_leader(TopMenuWidget *self, Window window)
+{
+#ifdef HAVE_MATEWNCK
+ MatewnckWindow *w = matewnck_window_get(window);
+ if (w) {
+ return matewnck_window_get_group_leader(w);
+ } else {
+ return None;
+ }
+#else
+ return None;
+#endif
+}
+
+static Window topmenu_widget_get_any_app_window_with_menu(TopMenuWidget *self, Window window)
+{
+#ifdef HAVE_MATEWNCK
+ Display *dpy = topmenu_widget_get_display(self);
+
+ MatewnckWindow *w = matewnck_window_get(window);
+ if (!w) return None;
+
+ MatewnckApplication *app = matewnck_window_get_application(w);
+ if (!app) return None;
+
+ GList *i, *windows = matewnck_screen_get_windows_stacked(self->priv->screen);
+ if (!windows) return None;
+
+ for (i = g_list_last(windows); i; i = g_list_previous(i)) {
+ if (i->data != w && matewnck_window_get_application(i->data) == app) {
+ Window candidate = matewnck_window_get_xid(i->data);
+ Window menu_window = read_window_property(dpy, candidate, self->priv->atom_window);
+ if (menu_window) {
+ return candidate;
+ }
+ }
+ }
+ return None;
+#else
+ return None;
+#endif
+}
+
+static void topmenu_widget_embed_topmenu_window(TopMenuWidget *self, Window window)
+{
+ g_return_if_fail(self->socket);
+ GdkWindow *cur = gtk_socket_get_plug_window(self->socket);
+
+ if (cur) {
+ if (GDK_WINDOW_XWINDOW(cur) == window) {
+ // Trying to embed the same client again
+ return; // Nothing to do
+ }
+
+ // Otherwise, disembed the current client
+ g_debug("Pulling the plug");
+ gdk_window_hide(cur);
+
+ // Reparent back to root window to end embedding
+ GdkScreen *screen = gdk_window_get_screen(cur);
+ gdk_window_reparent(cur, gdk_screen_get_root_window(screen), 0, 0);
+ }
+
+ if (window) {
+ g_debug("Embedding window 0x%lx", window);
+ gtk_socket_add_id(self->socket, window);
+ }
+}
+
+static gboolean topmenu_widget_try_window(TopMenuWidget *self, Window window)
+{
+ Display *dpy = topmenu_widget_get_display(self);
+ g_return_val_if_fail(dpy, FALSE);
+ g_return_val_if_fail(window, FALSE);
+
+ Window menu_window = read_window_property(dpy, window, self->priv->atom_window);
+ if (menu_window) {
+ topmenu_widget_embed_topmenu_window(self, menu_window);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean topmenu_widget_follow_window(TopMenuWidget *self, Window window)
+{
+ Display *dpy = topmenu_widget_get_display(self);
+ g_return_val_if_fail(dpy, FALSE);
+ g_return_val_if_fail(window, FALSE);
+
+ if (window == topmenu_widget_get_toplevel_xwindow(self)) {
+ return FALSE; // Ignore the window this widget is on as a candidate
+ }
+
+ // Add this window to the list of windows we are following
+ g_queue_push_head(&self->priv->followed_windows, GSIZE_TO_POINTER(window));
+
+ XWindowAttributes win_attrs;
+ if (XGetWindowAttributes(dpy, window, &win_attrs)) {
+ XSelectInput(dpy, window, win_attrs.your_event_mask | PropertyChangeMask);
+ }
+
+ if (topmenu_widget_try_window(self, window)) {
+ // Found a menu bar on this window
+ return TRUE;
+ } else {
+ // This window had no menu bar, so let's check its transient_for windows.
+ Window transient_for;
+ if (XGetTransientForHint(dpy, window, &transient_for)) {
+ if (topmenu_widget_follow_window(self, transient_for)) {
+ return TRUE;
+ }
+ }
+
+ // Also see if its client leader has a global menu bar....
+ Window leader = topmenu_widget_get_session_leader(self, window);
+ if (leader && leader != window) {
+ if (topmenu_widget_follow_window(self, leader)) {
+ return TRUE;
+ }
+ }
+
+ // Otherwise, if this program has more than one window, then let's search
+ // for any other window with a menu bar
+ Window other = topmenu_widget_get_any_app_window_with_menu(self, window);
+ if (other && other != window) {
+ if (topmenu_widget_follow_window(self, other)) {
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void topmenu_widget_set_followed_window(TopMenuWidget *self, Window window)
+{
+ Display *dpy = topmenu_widget_get_display(self);
+ g_return_if_fail(dpy);
+
+ g_debug("Setting active window to 0x%lx", window);
+
+ // Clear the list of currently followed windows.
+ g_queue_clear(&self->priv->followed_windows);
+
+ if (window) {
+ // Initialize atoms now
+ if (self->priv->atom_window == None) {
+ self->priv->atom_window = XInternAtom(dpy, ATOM_TOPMENU_WINDOW, False);
+ }
+ if (self->priv->atom_transient_for) {
+ self->priv->atom_transient_for = XInternAtom(dpy, "WM_TRANSIENT_FOR", False);
+ }
+
+ // Start by checking the active window
+ // This will recursively check its transient_for windows.
+ if (topmenu_widget_follow_window(self, window)) {
+ g_debug("Also following %d windows",
+ g_queue_get_length(&self->priv->followed_windows));
+ return;
+ }
+
+ // Otherwise fallback to "no menu bar".
+ g_debug("Active window has no menu bar; following %d windows",
+ g_queue_get_length(&self->priv->followed_windows));
+ }
+
+ topmenu_widget_embed_topmenu_window(self, None);
+}
+
+static void handle_socket_realize(GtkSocket *socket, TopMenuWidget *self)
+{
+ // Workaround a "bug workaround" where GtkSocket will not select ButtonPress
+ // events
+ g_warn_if_fail(gtk_widget_get_realized(GTK_WIDGET(socket)));
+ gtk_widget_add_events(GTK_WIDGET(socket),
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+}
+
+static gboolean handle_socket_plug_removed(GtkSocket *socket, TopMenuWidget *self)
+{
+ g_debug("Plug has been removed");
+ // No need to do anything
+ return TRUE; // Do not destroy the socket
+}
+
+#ifdef HAVE_MATEWNCK
+static void handle_active_window_changed(MatewnckScreen *screen, MatewnckWindow *prev_window, TopMenuWidget *self)
+{
+ if (!gtk_widget_get_visible(GTK_WIDGET(self))) {
+ return;
+ }
+ MatewnckWindow *window = matewnck_screen_get_active_window(screen);
+ if (window) {
+ topmenu_widget_set_followed_window(self, matewnck_window_get_xid(window));
+ } else {
+ // No active window?
+ }
+}
+#endif
+
+static GdkFilterReturn handle_gdk_event(GdkXEvent *xevent, GdkEvent *event, gpointer data)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(data);
+ XEvent *e = (XEvent*) xevent;
+
+ if (e->type == PropertyNotify &&
+ (e->xproperty.atom == self->priv->atom_transient_for ||
+ e->xproperty.atom == self->priv->atom_window)) {
+ // One of the properties we are interested in changed.
+ // See if it's one of the windows we're following.
+ if (g_queue_find(&self->priv->followed_windows,
+ GSIZE_TO_POINTER(e->xproperty.window))) {
+ // If so, try refollowing the currently followed window in order
+ // to see if any window has suddenly grown a menu bar.
+ g_debug("One of our followed windows changed");
+ Window window = GPOINTER_TO_SIZE(g_queue_peek_tail(&self->priv->followed_windows));
+ topmenu_widget_set_followed_window(self, window);
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void topmenu_widget_map(GtkWidget *widget)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(widget);
+ topmenu_server_register_server_widget(widget);
+ topmenu_widget_set_followed_window(self,
+ topmenu_widget_get_current_active_window(self));
+ GTK_WIDGET_CLASS(topmenu_widget_parent_class)->map(widget);
+}
+
+static void topmenu_widget_unmap(GtkWidget *widget)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(widget);
+ topmenu_widget_set_followed_window(self, None);
+ topmenu_server_unregister_server_widget(widget);
+ GTK_WIDGET_CLASS(topmenu_widget_parent_class)->unmap(widget);
+}
+
+static void topmenu_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(widget);
+ if (self->socket) {
+ gtk_widget_size_allocate(GTK_WIDGET(self->socket), allocation);
+ }
+ GTK_WIDGET_CLASS(topmenu_widget_parent_class)->size_allocate(widget, allocation);
+}
+
+static void topmenu_widget_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(widget);
+ if (self->socket) {
+ gtk_widget_size_request(GTK_WIDGET(self->socket), requisition);
+ }
+}
+
+static void topmenu_widget_dispose(GObject *obj)
+{
+ TopMenuWidget *self = TOPMENU_WIDGET(obj);
+ gdk_window_remove_filter(NULL, handle_gdk_event, self);
+ if (self->socket) {
+ g_signal_handlers_disconnect_by_data(self->socket, self);
+ self->socket = NULL;
+ }
+ g_queue_clear(&self->priv->followed_windows);
+#ifdef HAVE_MATEWNCK
+ if (self->priv->screen) {
+ g_signal_handlers_disconnect_by_data(self->priv->screen, self);
+ self->priv->screen = NULL;
+ }
+#endif
+ G_OBJECT_CLASS(topmenu_widget_parent_class)->dispose(obj);
+}
+
+static void topmenu_widget_class_init(TopMenuWidgetClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->map = topmenu_widget_map;
+ widget_class->unmap = topmenu_widget_unmap;
+ widget_class->size_allocate = topmenu_widget_size_allocate;
+ widget_class->size_request = topmenu_widget_size_request;
+
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ obj_class->dispose = topmenu_widget_dispose;
+
+ g_type_class_add_private(klass, sizeof(TopMenuWidgetPrivate));
+}
+
+static void topmenu_widget_init(TopMenuWidget *self)
+{
+ self->priv = TOPMENU_WIDGET_GET_PRIVATE(self);
+ self->socket = GTK_SOCKET(gtk_socket_new());
+ g_signal_connect_after(self->socket, "realize",
+ G_CALLBACK(handle_socket_realize), self);
+ g_signal_connect(self->socket, "plug-removed",
+ G_CALLBACK(handle_socket_plug_removed), self);
+ self->priv->atom_window = None;
+ self->priv->atom_transient_for = None;
+ g_queue_init(&self->priv->followed_windows);
+#ifdef HAVE_MATEWNCK
+ self->priv->screen = matewnck_screen_get_default();
+ g_signal_connect(self->priv->screen, "active-window-changed",
+ G_CALLBACK(handle_active_window_changed), self);
+#endif
+ gdk_window_add_filter(NULL, handle_gdk_event, self);
+ gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->socket));
+}
+
+GtkWidget *topmenu_widget_new(void)
+{
+ return GTK_WIDGET(g_object_new(TOPMENU_TYPE_WIDGET, NULL));
+}
diff --git a/libtopmenu-server/topmenu-widget.h b/libtopmenu-server/topmenu-widget.h
new file mode 100644
index 0000000..b3ea8f8
--- /dev/null
+++ b/libtopmenu-server/topmenu-widget.h
@@ -0,0 +1,39 @@
+#ifndef _TOPMENU_WIDGET_H_
+#define _TOPMENU_WIDGET_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TOPMENU_TYPE_WIDGET topmenu_widget_get_type()
+#define TOPMENU_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_WIDGET, TopMenuWidget))
+#define TOPMENU_IS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_WIDGET))
+#define TOPMENU_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_WIDGET, TopMenuWidgetClass))
+#define TOPMENU_IS_WIDGET_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_WIDGET))
+#define TOPMENU_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_WIDGET, TopMenuWidgetClass))
+
+typedef struct _TopMenuWidget TopMenuWidget;
+typedef struct _TopMenuWidgetClass TopMenuWidgetClass;
+typedef struct _TopMenuWidgetPrivate TopMenuWidgetPrivate;
+
+struct _TopMenuWidget
+{
+ GtkBin parent_instance;
+
+ TopMenuWidgetPrivate *priv;
+
+ GtkSocket *socket;
+};
+
+struct _TopMenuWidgetClass
+{
+ GtkBinClass parent_class;
+};
+
+GType topmenu_widget_get_type(void);
+
+GtkWidget *topmenu_widget_new(void);
+
+G_END_DECLS
+
+#endif /* _TOPMENU_WIDGET_H_ */
diff --git a/mate-applet/Makefile.am b/mate-applet/Makefile.am
new file mode 100644
index 0000000..dcebc77
--- /dev/null
+++ b/mate-applet/Makefile.am
@@ -0,0 +1,29 @@
+if WANT_MATE_APPLET
+
+libexec_PROGRAMS = topmenu-mate-panel-applet
+
+topmenu_mate_panel_applet_SOURCES = main.c topmenu-mate-panel-applet.c topmenu-mate-panel-applet.h
+topmenu_mate_panel_applet_CPPFLAGS = $(GTK_CFLAGS) $(MATEPANELAPPLET_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-mate-panel-applet\"
+topmenu_mate_panel_applet_LDADD = $(GTK_LIBS) $(MATEPANELAPPLET_LIBS) ../libtopmenu-server/libtopmenu-server.la
+
+appletdir = $(datadir)/mate-panel/applets
+applet_DATA = com.javispedro.topmenu.MatePanelApplet.mate-panel-applet
+
+$(applet_DATA): %: %.in Makefile
+ $(AM_V_GEN)sed \
+ -e "s|\@LIBEXECDIR\@|$(libexecdir)|" \
+ $< > $@
+
+servicedir = $(datadir)/dbus-1/services
+service_DATA = org.mate.panel.applet.TopMenuMatePanelAppletFactory.service
+
+$(service_DATA): %: %.in Makefile
+ $(AM_V_GEN)sed \
+ -e "s|\@LIBEXECDIR\@|$(libexecdir)|" \
+ $< > $@
+
+EXTRA_DIST = com.javispedro.topmenu.MatePanelApplet.mate-panel-applet.in \
+ org.mate.panel.applet.TopMenuMatePanelAppletFactory.service.in
+CLEANFILES = $(applet_DATA) $(service_DATA)
+
+endif
diff --git a/mate-applet/com.javispedro.topmenu.MatePanelApplet.mate-panel-applet.in b/mate-applet/com.javispedro.topmenu.MatePanelApplet.mate-panel-applet.in
new file mode 100644
index 0000000..12b7618
--- /dev/null
+++ b/mate-applet/com.javispedro.topmenu.MatePanelApplet.mate-panel-applet.in
@@ -0,0 +1,10 @@
+[Applet Factory]
+Id=TopMenuMatePanelAppletFactory
+Location=@LIBEXECDIR@/topmenu-mate-panel-applet
+Name=TopMenu Mate Panel Applet Factory
+Description=Trash Applet Factory
+
+[TopMenuMatePanelApplet]
+Name=TopMenu Panel Applet
+Description=Shows the TopMenu menu bar
+Icon=user-trash-full
diff --git a/mate-applet/main.c b/mate-applet/main.c
new file mode 100644
index 0000000..2ce2e92
--- /dev/null
+++ b/mate-applet/main.c
@@ -0,0 +1,21 @@
+#include <string.h>
+
+#include "topmenu-mate-panel-applet.h"
+
+static gboolean topmenu_mate_panel_applet_factory(MatePanelApplet *applet,
+ const gchar *iid,
+ gpointer data)
+{
+ if (strcmp(iid, "TopMenuMatePanelApplet") == 0) {
+ gtk_widget_show_all(GTK_WIDGET(applet));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MATE_PANEL_APPLET_OUT_PROCESS_FACTORY("TopMenuMatePanelAppletFactory",
+ TOPMENU_TYPE_MATE_PANEL_APPLET,
+ "TopMenuMatePanelApplet",
+ topmenu_mate_panel_applet_factory,
+ NULL)
diff --git a/mate-applet/org.mate.panel.applet.TopMenuMatePanelAppletFactory.service.in b/mate-applet/org.mate.panel.applet.TopMenuMatePanelAppletFactory.service.in
new file mode 100644
index 0000000..05388eb
--- /dev/null
+++ b/mate-applet/org.mate.panel.applet.TopMenuMatePanelAppletFactory.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.mate.panel.applet.TopMenuMatePanelAppletFactory
+Exec=@LIBEXECDIR@/topmenu-mate-panel-applet
diff --git a/mate-applet/topmenu-mate-panel-applet.c b/mate-applet/topmenu-mate-panel-applet.c
new file mode 100644
index 0000000..c5b996f
--- /dev/null
+++ b/mate-applet/topmenu-mate-panel-applet.c
@@ -0,0 +1,82 @@
+#include "topmenu-mate-panel-applet.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+
+G_DEFINE_TYPE(TopMenuMatePanelApplet, topmenu_mate_panel_applet, PANEL_TYPE_APPLET)
+
+static void display_preferences_dialog(GtkAction *action, TopMenuMatePanelApplet *self)
+{
+ // TODO
+}
+
+static void display_about_dialog(GtkAction *action, TopMenuMatePanelApplet *self)
+{
+ GtkWindow *parent = NULL;
+ GtkWidget *parent_widget = gtk_widget_get_toplevel(GTK_WIDGET(self));
+ if (GTK_IS_WINDOW(parent_widget)) {
+ parent = GTK_WINDOW(parent_widget);
+ }
+
+ gtk_show_about_dialog(parent,
+ "program-name", "TopMenu Mate Panel Applet",
+ NULL);
+}
+
+static const GtkActionEntry menu_verbs[] = {
+ { "TopMenuPreferences", GTK_STOCK_PROPERTIES, N_("_Preferences"),
+ NULL, NULL,
+ G_CALLBACK (display_preferences_dialog) },
+ { "TopMenuAbout", GTK_STOCK_ABOUT, N_("_About"),
+ NULL, NULL,
+ G_CALLBACK (display_about_dialog) }
+};
+
+static void topmenu_mate_panel_applet_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+ TopMenuMatePanelApplet *self = TOPMENU_MATE_PANEL_APPLET(widget);
+ if (self->menu_widget) {
+ gtk_widget_size_allocate(GTK_WIDGET(self->menu_widget), allocation);
+ }
+ GTK_WIDGET_CLASS(topmenu_mate_panel_applet_parent_class)->size_allocate(widget, allocation);
+}
+
+static void topmenu_mate_panel_applet_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+ TopMenuMatePanelApplet *self = TOPMENU_MATE_PANEL_APPLET(widget);
+ if (self->menu_widget) {
+ gtk_widget_size_request(GTK_WIDGET(self->menu_widget), requisition);
+ }
+}
+
+static void topmenu_mate_panel_applet_class_init(TopMenuMatePanelAppletClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ widget_class->size_allocate = topmenu_mate_panel_applet_size_allocate;
+ widget_class->size_request = topmenu_mate_panel_applet_size_request;
+}
+
+static void topmenu_mate_panel_applet_init(TopMenuMatePanelApplet *self)
+{
+ self->menu_widget = TOPMENU_WIDGET(topmenu_widget_new());
+ gtk_widget_set_can_focus(GTK_WIDGET(self->menu_widget), TRUE);
+ gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->menu_widget));
+
+ GtkActionGroup *action_group = gtk_action_group_new("TopMenu Mate Panel Applet Actions");
+ gtk_action_group_add_actions(action_group,
+ menu_verbs, G_N_ELEMENTS(menu_verbs), self);
+
+ mate_panel_applet_set_flags(MATE_PANEL_APPLET(self),
+ MATE_PANEL_APPLET_EXPAND_MINOR);
+ mate_panel_applet_setup_menu(MATE_PANEL_APPLET(self),
+ "<menuitem name=\"TopMenu Preferences Item\" action=\"TopMenuPreferences\"/>"
+ "<menuitem name=\"TopMenu About Item\" action=\"TopMenuAbout\"/>",
+ action_group);
+
+ g_object_unref(action_group);
+}
+
+MatePanelApplet *topmenu_mate_panel_applet_new(void)
+{
+ return MATE_PANEL_APPLET(g_object_new(TOPMENU_TYPE_MATE_PANEL_APPLET, NULL));
+}
diff --git a/mate-applet/topmenu-mate-panel-applet.h b/mate-applet/topmenu-mate-panel-applet.h
new file mode 100644
index 0000000..0563597
--- /dev/null
+++ b/mate-applet/topmenu-mate-panel-applet.h
@@ -0,0 +1,38 @@
+#ifndef _TOPMENU_MATE_PANEL_APPLET_H_
+#define _TOPMENU_MATE_PANEL_APPLET_H_
+
+#include <mate-panel-applet.h>
+#include "../libtopmenu-server/topmenu-widget.h"
+
+G_BEGIN_DECLS
+
+#define TOPMENU_TYPE_MATE_PANEL_APPLET topmenu_mate_panel_applet_get_type()
+#define TOPMENU_MATE_PANEL_APPLET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TOPMENU_TYPE_MATE_PANEL_APPLET, TopMenuMatePanelApplet))
+#define TOPMENU_IS_MATE_PANEL_APPLET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TOPMENU_TYPE_MATE_PANEL_APPLET))
+#define TOPMENU_MATE_PANEL_APPLET_CLASS(c) (G_TYPE_CHECK_CLASS_CAST((c), TOPMENU_TYPE_MATE_PANEL_APPLET, TopMenuMatePanelAppletClass))
+#define TOPMENU_IS_MATE_PANEL_APPLET_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE((c), TOPMENU_TYPE_MATE_PANEL_APPLET))
+#define TOPMENU_MATE_PANEL_APPLET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TOPMENU_TYPE_MATE_PANEL_APPLET, TopMenuMatePanelAppletClass))
+
+typedef struct _TopMenuMatePanelApplet TopMenuMatePanelApplet;
+typedef struct _TopMenuMatePanelAppletClass TopMenuMatePanelAppletClass;
+
+struct _TopMenuMatePanelApplet
+{
+ MatePanelApplet parent_instance;
+ TopMenuWidget *menu_widget;
+ GdkWindow *cur_plug;
+ GtkActionGroup *actions;
+};
+
+struct _TopMenuMatePanelAppletClass
+{
+ MatePanelAppletClass parent_class;
+};
+
+GType topmenu_mate_panel_applet_get_type(void);
+
+MatePanelApplet *topmenu_mate_panel_applet_new(void);
+
+G_END_DECLS
+
+#endif /* _TOPMENU_MATE_PANEL_APPLET_H_ */
diff --git a/module/Makefile.am b/module/Makefile.am
new file mode 100644
index 0000000..ac75b74
--- /dev/null
+++ b/module/Makefile.am
@@ -0,0 +1,9 @@
+
+gtk_moduledir = $(GTK_MODULE_DIR)
+gtk_module_LTLIBRARIES = libtopmenu-gtk-module.la
+
+libtopmenu_gtk_module_la_SOURCES = main.c data.c data.h \
+ menuitem-proxy.c menuitem-proxy.h appmenu.c appmenu.h
+libtopmenu_gtk_module_la_CPPFLAGS = $(GTK_CFLAGS) -DG_LOG_DOMAIN=\"topmenu-module\"
+libtopmenu_gtk_module_la_LIBADD = $(GTK_LIBS) ../libtopmenu-client/libtopmenu-client.la
+libtopmenu_gtk_module_la_LDFLAGS = -avoid-version -module -shared
diff --git a/module/appmenu.c b/module/appmenu.c
new file mode 100644
index 0000000..a73846a
--- /dev/null
+++ b/module/appmenu.c
@@ -0,0 +1,222 @@
+#include <string.h>
+
+#include "appmenu.h"
+#include "data.h"
+#include "menuitem-proxy.h"
+
+typedef enum _AppMenuRole {
+ APP_MENU_ROLE_NONE = 0,
+ APP_MENU_ROLE_ABOUT,
+ APP_MENU_ROLE_PREFERENCES,
+ APP_MENU_ROLE_QUIT,
+ APP_MENU_ROLE_MAX
+} AppMenuRole;
+
+static AppMenuRole detect_role_by_stock(const gchar *stock_id)
+{
+ if (g_strcmp0(stock_id, GTK_STOCK_PREFERENCES) == 0) {
+ return APP_MENU_ROLE_PREFERENCES;
+ } else if (g_strcmp0(stock_id, GTK_STOCK_QUIT) == 0) {
+ return APP_MENU_ROLE_QUIT;
+ } else if (g_strcmp0(stock_id, GTK_STOCK_ABOUT) == 0) {
+ return APP_MENU_ROLE_ABOUT;
+ } else {
+ return APP_MENU_ROLE_NONE;
+ }
+}
+
+static AppMenuRole detect_item_role(GtkMenuItem *item)
+{
+ if (GTK_IS_IMAGE_MENU_ITEM(item)) {
+ GtkImageMenuItem *iitem = GTK_IMAGE_MENU_ITEM(item);
+ if (gtk_image_menu_item_get_use_stock(iitem)) {
+ return detect_role_by_stock(gtk_menu_item_get_label(item));
+ } else {
+ GtkWidget *iwidget = gtk_image_menu_item_get_image(iitem);
+ if (GTK_IS_IMAGE(iwidget)) {
+ GtkImage *image = GTK_IMAGE(iwidget);
+ if (gtk_image_get_storage_type(image) == GTK_IMAGE_STOCK) {
+ gchar *stock_id;
+ GtkIconSize icon_size;
+ gtk_image_get_stock(image, &stock_id, &icon_size);
+ return detect_role_by_stock(stock_id);
+ }
+ }
+ }
+ }
+
+ return APP_MENU_ROLE_NONE;
+}
+
+static void handle_default_quit(GtkMenuItem *item, gpointer user_data)
+{
+ gtk_main_quit();
+}
+
+static GtkMenuItem *create_separator()
+{
+ GtkWidget *sep = gtk_separator_menu_item_new();
+ gtk_widget_show(sep);
+ return GTK_MENU_ITEM(sep);
+}
+
+static GtkMenuItem *create_default_exit()
+{
+ GtkMenuItem *item = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL));
+ g_signal_connect(item, "activate", G_CALLBACK(handle_default_quit), NULL);
+ gtk_widget_show_all(GTK_WIDGET(item));
+ return item;
+}
+
+static gint get_role_position(AppMenu *self, AppMenuRole role)
+{
+ gint position = 0;
+ switch (role) {
+ case APP_MENU_ROLE_MAX:
+ if (self->quit_item) position++;
+ if (self->sep1_item) position++;
+ case APP_MENU_ROLE_QUIT:
+ if (self->prefs_item) position++;
+ case APP_MENU_ROLE_PREFERENCES:
+ if (self->about_item) position++;
+ case APP_MENU_ROLE_ABOUT:
+ break;
+ default:
+ g_warn_if_reached();
+ break;
+ }
+ return position;
+}
+
+static void set_item_for_role(AppMenu *self, GtkMenuItem *item, AppMenuRole role)
+{
+ g_return_if_fail(role != APP_MENU_ROLE_NONE);
+
+ switch (role) {
+ case APP_MENU_ROLE_ABOUT:
+ if (self->about_item) {
+ gtk_widget_destroy(GTK_WIDGET(self->about_item));
+ self->about_item = NULL;
+ }
+ if (item) {
+ if (self->quit_item && !self->sep1_item) {
+ self->sep1_item = create_separator();
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->sep1_item),
+ get_role_position(self, APP_MENU_ROLE_QUIT));
+ }
+ self->about_item = item;
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->about_item),
+ get_role_position(self, APP_MENU_ROLE_ABOUT));
+ }
+ break;
+ case APP_MENU_ROLE_PREFERENCES:
+ if (self->prefs_item) {
+ gtk_widget_destroy(GTK_WIDGET(self->prefs_item));
+ self->prefs_item = NULL;
+ }
+ if (item) {
+ if (self->quit_item && !self->sep1_item) {
+ self->sep1_item = create_separator();
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->sep1_item),
+ get_role_position(self, APP_MENU_ROLE_QUIT));
+ }
+ self->prefs_item = item;
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->prefs_item),
+ get_role_position(self, APP_MENU_ROLE_PREFERENCES));
+ }
+ break;
+ case APP_MENU_ROLE_QUIT:
+ if (self->quit_item) {
+ gtk_widget_destroy(GTK_WIDGET(self->quit_item));
+ self->quit_item = NULL;
+ }
+ if (item) {
+ if ((self->about_item || self->prefs_item) && !self->sep1_item) {
+ self->sep1_item = create_separator();
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->sep1_item),
+ get_role_position(self, APP_MENU_ROLE_QUIT));
+ }
+ self->quit_item = item;
+ gtk_menu_shell_insert(GTK_MENU_SHELL(self->menu),
+ GTK_WIDGET(self->quit_item),
+ get_role_position(self, APP_MENU_ROLE_QUIT) + 1);
+ }
+ break;
+ default:
+ g_warn_if_reached();
+ }
+}
+
+GtkWidget * topmenu_appmenu_build(AppMenu *self)
+{
+ self->menu = GTK_MENU(gtk_menu_new());
+
+ return GTK_WIDGET(self->menu);
+}
+
+typedef struct _MenuScanData
+{
+ AppMenu *appmenu;
+ gint depth;
+} MenuScanData;
+
+static void appmenu_scan_cb(GtkWidget *widget, gpointer user_data)
+{
+ MenuScanData *data = user_data;
+ g_return_if_fail(GTK_IS_MENU_ITEM(widget));
+
+ GtkMenuItem *item = GTK_MENU_ITEM(widget);
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+
+ if (!submenu) {
+ MenuItemData *item_data = topmenu_get_menu_item_data(item);
+ if (item_data && item_data->proxy) {
+ submenu = gtk_menu_item_get_submenu(item_data->proxy);
+ }
+ }
+
+ if (submenu) {
+ data->depth--;
+ if (data->depth >= 0) {
+ g_warn_if_fail(GTK_IS_CONTAINER(submenu));
+ gtk_container_foreach(GTK_CONTAINER(submenu), appmenu_scan_cb, data);
+ }
+ data->depth++;
+ } else {
+ AppMenuRole role = detect_item_role(item);
+ if (role != APP_MENU_ROLE_NONE) {
+ GtkMenuItem *proxy = topmenu_create_proxy_menu_item(item);
+ set_item_for_role(data->appmenu, proxy, role);
+ }
+ }
+}
+
+void topmenu_appmenu_scan_for_items(AppMenu *self, GtkMenuShell *menu_shell)
+{
+ g_return_if_fail(GTK_IS_MENU(self->menu));
+
+ MenuScanData data;
+ data.appmenu = self;
+ data.depth = 1;
+ gtk_container_foreach(GTK_CONTAINER(menu_shell), appmenu_scan_cb, &data);
+
+ // Create default items
+ if (!self->quit_item) {
+ GtkMenuItem *item = create_default_exit();
+ set_item_for_role(self, item, APP_MENU_ROLE_QUIT);
+ }
+}
+
+void topmenu_appmenu_destroy(AppMenu *self)
+{
+ if (self->menu) {
+ gtk_widget_destroy(GTK_WIDGET(self->menu));
+ self->menu = NULL;
+ }
+ memset(self, 0, sizeof(AppMenu));
+}
diff --git a/module/appmenu.h b/module/appmenu.h
new file mode 100644
index 0000000..e02f68c
--- /dev/null
+++ b/module/appmenu.h
@@ -0,0 +1,21 @@
+#ifndef _APPMENU_H_
+#define _APPMENU_H_
+
+#include <gtk/gtk.h>
+
+typedef struct _AppMenu
+{
+ GtkMenu *menu;
+ GtkMenuItem *about_item;
+ GtkMenuItem *prefs_item;
+ GtkMenuItem *sep1_item;
+ GtkMenuItem *quit_item;
+} AppMenu;
+
+GtkWidget * topmenu_appmenu_build(AppMenu *appmenu);
+
+void topmenu_appmenu_scan_for_items(AppMenu *self, GtkMenuShell *menu_shell);
+
+void topmenu_appmenu_destroy(AppMenu *self);
+
+#endif
diff --git a/module/data.c b/module/data.c
new file mode 100644
index 0000000..9225d01
--- /dev/null
+++ b/module/data.c
@@ -0,0 +1,156 @@
+#include "../libtopmenu-client/topmenu-monitor.h"
+
+#include "data.h"
+#include "appmenu.h"
+
+G_DEFINE_QUARK(topmenu-window-data, window_data)
+G_DEFINE_QUARK(topmenu-menu-shell-data, menu_shell_data)
+G_DEFINE_QUARK(topmenu-menu-item-data, menu_item_data)
+
+gboolean
+topmenu_is_blacklisted (void)
+{
+ return FALSE;
+}
+
+gboolean
+topmenu_is_window_blacklisted (GtkWindow *window)
+{
+ if (gtk_window_get_window_type (window) != GTK_WINDOW_TOPLEVEL)
+ return TRUE;
+
+ if (GTK_IS_PLUG (window))
+ return TRUE;
+
+ return FALSE;
+}
+
+static WindowData *
+window_data_new (void)
+{
+ return g_slice_new0 (WindowData);
+}
+
+static void
+window_data_free (gpointer data)
+{
+ WindowData *window_data = data;
+
+ if (window_data != NULL)
+ {
+ if (window_data->menus != NULL)
+ g_slist_free_full (window_data->menus, g_object_unref);
+
+ if (window_data->monitor_connection_id)
+ g_signal_handler_disconnect(topmenu_monitor_get_instance(),
+ window_data->monitor_connection_id);
+
+ if (window_data->appmenu.menu)
+ topmenu_appmenu_destroy(&window_data->appmenu);
+
+ if (window_data->appmenubar)
+ gtk_widget_destroy(GTK_WIDGET(window_data->appmenubar));
+
+ g_slice_free (WindowData, window_data);
+ }
+}
+
+static MenuShellData *
+menu_shell_data_new (void)
+{
+ return g_slice_new0 (MenuShellData);
+}
+
+static void
+menu_shell_data_free (gpointer data)
+{
+ if (data != NULL)
+ g_slice_free (MenuShellData, data);
+}
+
+static MenuItemData *
+menu_item_data_new (void)
+{
+ return g_slice_new0 (MenuItemData);
+}
+
+static void
+menu_item_data_free (gpointer data)
+{
+ if (data != NULL) {
+ MenuItemData *item_data = data;
+
+ if (item_data->proxy)
+ gtk_widget_destroy (GTK_WIDGET (item_data->proxy));
+
+ g_slice_free (MenuItemData, data);
+ }
+}
+
+MenuItemData *
+topmenu_get_menu_item_data (GtkMenuItem *menu_item)
+{
+ MenuItemData *menu_item_data;
+
+ g_return_val_if_fail (GTK_IS_MENU_ITEM (menu_item), NULL);
+
+ menu_item_data = g_object_get_qdata (G_OBJECT (menu_item), menu_item_data_quark ());
+
+ if (menu_item_data == NULL)
+ {
+ menu_item_data = menu_item_data_new ();
+
+ g_object_set_qdata_full (G_OBJECT (menu_item), menu_item_data_quark (), menu_item_data, menu_item_data_free);
+ }
+
+ return menu_item_data;
+}
+
+MenuShellData *
+topmenu_get_menu_shell_data (GtkMenuShell *menu_shell)
+{
+ MenuShellData *menu_shell_data;
+
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (menu_shell), NULL);
+
+ menu_shell_data = g_object_get_qdata (G_OBJECT (menu_shell), menu_shell_data_quark ());
+
+ if (menu_shell_data == NULL)
+ {
+ menu_shell_data = menu_shell_data_new ();
+
+ g_object_set_qdata_full (G_OBJECT (menu_shell), menu_shell_data_quark (), menu_shell_data, menu_shell_data_free);
+ }
+
+ return menu_shell_data;
+}
+
+WindowData *
+topmenu_get_window_data (GtkWindow *window)
+{
+ WindowData *window_data;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
+
+ window_data = g_object_get_qdata (G_OBJECT (window), window_data_quark ());
+
+ if (window_data == NULL)
+ {
+ if (topmenu_is_window_blacklisted (window))
+ return NULL;
+
+ window_data = window_data_new ();
+
+ // Nothing to initialize right now.
+
+ g_object_set_qdata_full (G_OBJECT (window), window_data_quark (), window_data, window_data_free);
+ }
+
+ return window_data;
+}
+
+void
+topmenu_remove_window_data (GtkWindow *window)
+{
+ g_object_set_qdata (G_OBJECT (window), window_data_quark (), NULL);
+}
diff --git a/module/data.h b/module/data.h
new file mode 100644
index 0000000..9bb643f
--- /dev/null
+++ b/module/data.h
@@ -0,0 +1,48 @@
+#ifndef _DATA_H_
+#define _DATA_H_
+
+#include <gtk/gtk.h>
+
+#include "../libtopmenu-client/topmenu-appmenubar.h"
+#include "appmenu.h"
+
+typedef struct _WindowData WindowData;
+typedef struct _MenuShellData MenuShellData;
+typedef struct _MenuItemData MenuItemData;
+
+struct _WindowData
+{
+ GSList *menus;
+ TopMenuAppMenuBar *appmenubar;
+ AppMenu appmenu;
+ gulong monitor_connection_id;
+};
+
+struct _MenuShellData
+{
+ GtkWindow *window;
+};
+
+struct _MenuItemData
+{
+ GtkMenuItem *proxy;
+};
+gboolean
+topmenu_is_blacklisted (void);
+
+gboolean
+topmenu_is_window_blacklisted (GtkWindow *window);
+
+WindowData *
+topmenu_get_window_data (GtkWindow *window);
+
+void
+topmenu_remove_window_data (GtkWindow *window);
+
+MenuShellData *
+topmenu_get_menu_shell_data (GtkMenuShell *menu_shell);
+
+MenuItemData *
+topmenu_get_menu_item_data (GtkMenuItem *menu_item);
+
+#endif
diff --git a/module/main.c b/module/main.c
new file mode 100644
index 0000000..42ea332
--- /dev/null
+++ b/module/main.c
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2012 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ * William Hua <william.hua@canonical.com>
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+#include "../global.h"
+#include "../libtopmenu-client/topmenu-client.h"
+#include "../libtopmenu-client/topmenu-monitor.h"
+
+#include "menuitem-proxy.h"
+#include "appmenu.h"
+#include "data.h"
+
+static gboolean already_initialized = FALSE;
+
+static void (* pre_hijacked_window_realize) (GtkWidget *widget);
+
+static void (* pre_hijacked_window_unrealize) (GtkWidget *widget);
+
+#if GTK_MAJOR_VERSION == 3
+static void (* pre_hijacked_application_window_realize) (GtkWidget *widget);
+#endif
+
+static void (* pre_hijacked_menu_bar_realize) (GtkWidget *widget);
+
+static void (* pre_hijacked_menu_bar_unrealize) (GtkWidget *widget);
+
+static void (* pre_hijacked_widget_size_allocate) (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+static void (* pre_hijacked_menu_bar_size_allocate) (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+#if GTK_MAJOR_VERSION == 2
+static void (* pre_hijacked_menu_bar_size_request) (GtkWidget *widget,
+ GtkRequisition *requisition);
+#elif GTK_MAJOR_VERSION == 3
+static void (* pre_hijacked_menu_bar_get_preferred_width) (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width);
+
+static void (* pre_hijacked_menu_bar_get_preferred_height) (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height);
+
+static void (* pre_hijacked_menu_bar_get_preferred_width_for_height) (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+
+static void (* pre_hijacked_menu_bar_get_preferred_height_for_width) (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+#endif
+
+static void
+handle_should_hide_menubar_updated (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void
+count_container_items_helper (GtkWidget *widget, gpointer data)
+{
+ gint *count = data;
+ (*count)++;
+}
+
+static gint
+count_container_items (GtkContainer *container)
+{
+ gint count = 0;
+ gtk_container_foreach (container, count_container_items_helper, &count);
+ return count;
+}
+
+
+
+
+static gboolean
+topmenu_should_hide_menubar_on_window (GtkWindow *window)
+{
+ TopMenuMonitor *monitor = topmenu_monitor_get_instance ();
+
+ if (topmenu_is_window_blacklisted (window))
+ return FALSE;
+
+ return monitor->available;
+}
+
+static gboolean
+topmenu_should_hide_menubar (GtkWidget *widget)
+{
+ GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (widget));
+
+ g_return_val_if_fail (GTK_IS_MENU_SHELL (widget), FALSE);
+
+ return topmenu_should_hide_menubar_on_window (window);
+}
+
+static void
+topmenu_prepare_window (GtkWindow *window)
+{
+ WindowData *window_data;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (window)));
+
+ window_data = topmenu_get_window_data (window);
+
+ if (window_data == NULL)
+ return; // Window is ignored
+
+ if (window_data->appmenubar != 0)
+ return; // Already prepared
+
+ window_data->appmenubar = TOPMENU_APP_MENU_BAR (topmenu_app_menu_bar_new ());
+ gtk_widget_show(GTK_WIDGET(window_data->appmenubar));
+
+ topmenu_app_menu_bar_set_app_menu (window_data->appmenubar,
+ topmenu_appmenu_build(&window_data->appmenu));
+}
+
+static void
+topmenu_connect_window (GtkWindow *window)
+{
+ WindowData *window_data;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (window)));
+
+ window_data = topmenu_get_window_data (window);
+
+ if (window_data == NULL)
+ return; // Window is ignored
+
+ if (window_data->monitor_connection_id != 0)
+ return; // Already connected
+
+ g_return_if_fail (window_data->menus != NULL); // Must contain one menu at least.
+ g_return_if_fail (window_data->appmenubar != NULL); // Must be prepared
+
+ TopMenuMonitor *monitor = topmenu_monitor_get_instance();
+ window_data->monitor_connection_id = g_signal_connect(monitor, "notify::available",
+ G_CALLBACK (handle_should_hide_menubar_updated), window);
+
+ topmenu_client_connect_window_widget (gtk_widget_get_window (GTK_WIDGET (window)),
+ GTK_WIDGET (window_data->appmenubar));
+}
+
+static void
+topmenu_disconnect_window (GtkWindow *window)
+{
+ WindowData *window_data;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ window_data = topmenu_get_window_data (window);
+
+ if (window_data == NULL)
+ return; // Already disconnected or ignored
+
+ if (window_data->monitor_connection_id == 0)
+ return; // Already disconnected
+
+ TopMenuMonitor *monitor = topmenu_monitor_get_instance();
+ g_signal_handler_disconnect(monitor, window_data->monitor_connection_id);
+ window_data->monitor_connection_id = 0;
+
+ if (window_data->appmenu.menu)
+ topmenu_appmenu_destroy(&window_data->appmenu);
+
+ if (window_data->appmenubar)
+ {
+ gtk_widget_destroy (GTK_WIDGET (window_data->appmenubar));
+ window_data->appmenubar = NULL;
+ }
+
+ if (gtk_widget_get_realized (GTK_WIDGET(window)))
+ {
+ topmenu_client_disconnect_window (gtk_widget_get_window (GTK_WIDGET (window)));
+ }
+}
+
+static gint
+compute_shell_position_in_appmenu (WindowData *window_data, GtkMenuShell *menu_shell)
+{
+ GSList *iter;
+ gint position = 1; // Skip app_menu_item
+
+ for (iter = window_data->menus; iter; iter = g_slist_next (iter))
+ {
+ if (iter->data == menu_shell) {
+ return position;
+ }
+
+ position += count_container_items (GTK_CONTAINER (iter->data));
+ }
+
+ return -1;
+}
+
+static void
+add_menu_item_to_appmenu (WindowData *window_data, GtkMenuItem *item, gint position)
+{
+ MenuItemData *item_data = topmenu_get_menu_item_data (item);
+ item_data->proxy = topmenu_create_proxy_menu_item (item);
+
+ gtk_menu_shell_insert (GTK_MENU_SHELL (window_data->appmenubar),
+ GTK_WIDGET (item_data->proxy), position);
+}
+
+static void
+remove_menu_item_from_appmenu (WindowData *window_data, GtkMenuItem *item)
+{
+ MenuItemData *item_data = topmenu_get_menu_item_data (item);
+
+ if (item_data->proxy) {
+ gtk_widget_destroy (GTK_WIDGET (item_data->proxy));
+ item_data->proxy = NULL;
+ }
+}
+
+static void
+handle_shell_insert (GtkMenuShell *menu_shell, GtkWidget *child, gint position, WindowData *window_data)
+{
+ GtkMenuItem *item = GTK_MENU_ITEM (child);
+ gint offset = compute_shell_position_in_appmenu (window_data, menu_shell);
+ g_return_if_fail (offset >= 0);
+
+ add_menu_item_to_appmenu (window_data, item, offset + position);
+}
+
+static void
+handle_shell_remove (GtkMenuShell *menu_shell, GtkWidget *widget, WindowData *window_data)
+{
+ GtkMenuItem *item = GTK_MENU_ITEM (widget);
+ remove_menu_item_from_appmenu (window_data, item);
+}
+
+typedef struct _AddShellCbData
+{
+ WindowData *window_data;
+ gint position;
+} AddShellCbData;
+
+static void
+add_shell_cb (GtkWidget *widget, gpointer user_data)
+{
+ AddShellCbData *data = user_data;
+ GtkMenuItem *item = GTK_MENU_ITEM (widget);
+ add_menu_item_to_appmenu (data->window_data, item, data->position);
+
+ data->position++;
+}
+
+static void
+add_shell_to_appmenu (WindowData *window_data, GtkMenuShell *menu_shell)
+{
+ AddShellCbData data;
+ data.window_data = window_data;
+ data.position = compute_shell_position_in_appmenu (window_data, menu_shell);
+
+ g_warn_if_fail (data.position >= 0);
+
+ gtk_container_foreach (GTK_CONTAINER (menu_shell),
+ add_shell_cb, &data);
+
+ topmenu_appmenu_scan_for_items (&window_data->appmenu, menu_shell);
+
+ g_signal_connect (menu_shell, "insert",
+ G_CALLBACK (handle_shell_insert), window_data);
+ g_signal_connect (menu_shell, "remove",
+ G_CALLBACK (handle_shell_remove), window_data);
+}
+
+static void
+remove_shell_cb (GtkWidget *widget, gpointer user_data)
+{
+ WindowData *window_data = user_data;
+ GtkMenuItem *item = GTK_MENU_ITEM (widget);
+ remove_menu_item_from_appmenu (window_data, item);
+}
+
+static void
+remove_shell_from_appmenu (WindowData *window_data, GtkMenuShell *menu_shell)
+{
+ gtk_container_foreach (GTK_CONTAINER (menu_shell),
+ remove_shell_cb, window_data);
+
+ g_signal_handlers_disconnect_by_data (menu_shell, window_data);
+}
+
+static void
+topmenu_disconnect_menu_shell (GtkWindow *window,
+ GtkMenuShell *menu_shell)
+{
+ WindowData *window_data;
+ MenuShellData *menu_shell_data;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+
+ menu_shell_data = topmenu_get_menu_shell_data (menu_shell);
+
+ g_warn_if_fail (window == menu_shell_data->window);
+
+ window_data = topmenu_get_window_data (menu_shell_data->window);
+
+ if (window_data != NULL)
+ {
+ GSList *iter;
+
+ for (iter = window_data->menus; iter != NULL; iter = g_slist_next (iter))
+ if (GTK_MENU_SHELL(iter->data) == menu_shell)
+ break;
+
+ if (iter != NULL)
+ {
+ GtkMenuShell *menu_shell = GTK_MENU_SHELL(iter->data);
+
+ remove_shell_from_appmenu (window_data, menu_shell);
+
+ window_data->menus = g_slist_delete_link (window_data->menus, iter);
+ }
+ }
+
+ menu_shell_data->window = NULL;
+}
+
+static void
+topmenu_connect_menu_shell (GtkWindow *window,
+ GtkMenuShell *menu_shell)
+{
+ MenuShellData *menu_shell_data;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell));
+
+ menu_shell_data = topmenu_get_menu_shell_data (menu_shell);
+
+ if (window != menu_shell_data->window)
+ {
+ WindowData *window_data;
+
+ if (menu_shell_data->window != NULL)
+ topmenu_disconnect_menu_shell (menu_shell_data->window, menu_shell);
+
+ window_data = topmenu_get_window_data (window);
+
+ if (window_data != NULL)
+ {
+ GSList *iter;
+
+ for (iter = window_data->menus; iter != NULL; iter = g_slist_next (iter))
+ if (GTK_MENU_SHELL(iter->data) == menu_shell)
+ break;
+
+ if (iter == NULL)
+ {
+ topmenu_prepare_window (window);
+
+ window_data->menus = g_slist_append (window_data->menus, menu_shell);
+
+ add_shell_to_appmenu (window_data, menu_shell);
+
+ topmenu_connect_window (window); // Does nothing if already connected
+ }
+ }
+
+ menu_shell_data->window = window;
+ }
+}
+
+static void
+handle_should_hide_menubar_updated (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ g_return_if_fail (GTK_IS_WINDOW (user_data));
+
+ GtkWindow *window = GTK_WINDOW (user_data);
+ WindowData *window_data = topmenu_get_window_data (window);
+ GSList *iter;
+
+ for (iter = window_data->menus; iter != NULL; iter = g_slist_next (iter))
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (iter->data));
+ }
+}
+
+static void
+hijacked_window_realize (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WINDOW (widget));
+
+ if (pre_hijacked_window_realize != NULL)
+ (* pre_hijacked_window_realize) (widget);
+
+#if GTK_MAJOR_VERSION == 3
+ if (!GTK_IS_APPLICATION_WINDOW (widget))
+#endif
+ topmenu_get_window_data (GTK_WINDOW (widget));
+}
+
+static void
+hijacked_window_unrealize (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_WINDOW (widget));
+
+ if (pre_hijacked_window_unrealize != NULL)
+ (* pre_hijacked_window_unrealize) (widget);
+
+ topmenu_disconnect_window (GTK_WINDOW (widget));
+ topmenu_remove_window_data (GTK_WINDOW (widget));
+}
+
+#if GTK_MAJOR_VERSION == 3
+static void
+hijacked_application_window_realize (GtkWidget *widget)
+{
+ g_return_if_fail (GTK_IS_APPLICATION_WINDOW (widget));
+
+ if (pre_hijacked_application_window_realize != NULL)
+ (* pre_hijacked_application_window_realize) (widget);
+
+ gtk_window_get_window_data (GTK_WINDOW (widget));
+}
+#endif
+
+static void
+hijacked_menu_bar_realize (GtkWidget *widget)
+{
+ GtkWidget *window;
+
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_realize != NULL)
+ (* pre_hijacked_menu_bar_realize) (widget);
+
+ window = gtk_widget_get_toplevel (widget);
+
+ if (GTK_IS_WINDOW (window))
+ topmenu_connect_menu_shell (GTK_WINDOW (window), GTK_MENU_SHELL (widget));
+}
+
+static void
+hijacked_menu_bar_unrealize (GtkWidget *widget)
+{
+ MenuShellData *menu_shell_data;
+
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_unrealize != NULL)
+ (* pre_hijacked_menu_bar_unrealize) (widget);
+
+ menu_shell_data = topmenu_get_menu_shell_data (GTK_MENU_SHELL (widget));
+
+ if (menu_shell_data->window != NULL)
+ topmenu_disconnect_menu_shell (menu_shell_data->window, GTK_MENU_SHELL (widget));
+}
+
+static void
+hijacked_menu_bar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkAllocation zero = { 0, 0, 0, 0 };
+ GdkWindow *window;
+
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (topmenu_should_hide_menubar (widget))
+ {
+ /*
+ * We manually assign an empty allocation to the menu bar to
+ * prevent the container from attempting to draw it at all.
+ */
+ if (pre_hijacked_widget_size_allocate != NULL)
+ (* pre_hijacked_widget_size_allocate) (widget, &zero);
+
+ /*
+ * Then we move the GdkWindow belonging to the menu bar outside of
+ * the clipping rectangle of the parent window so that we can't
+ * see it.
+ */
+ window = gtk_widget_get_window (widget);
+
+ if (window != NULL)
+ gdk_window_move_resize (window, -1, -1, 1, 1);
+ }
+ else if (pre_hijacked_menu_bar_size_allocate != NULL)
+ (* pre_hijacked_menu_bar_size_allocate) (widget, allocation);
+}
+
+#if GTK_MAJOR_VERSION == 2
+static void
+hijacked_menu_bar_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_size_request != NULL)
+ (* pre_hijacked_menu_bar_size_request) (widget, requisition);
+
+ if (topmenu_should_hide_menubar(widget))
+ {
+ requisition->width = 0;
+ requisition->height = 0;
+ }
+}
+#elif GTK_MAJOR_VERSION == 3
+static void
+hijacked_menu_bar_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_get_preferred_width != NULL)
+ (* pre_hijacked_menu_bar_get_preferred_width) (widget, minimum_width, natural_width);
+
+ if (gtk_widget_shell_shows_menubar (widget))
+ {
+ *minimum_width = 0;
+ *natural_width = 0;
+ }
+}
+
+static void
+hijacked_menu_bar_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_get_preferred_height != NULL)
+ (* pre_hijacked_menu_bar_get_preferred_height) (widget, minimum_height, natural_height);
+
+ if (gtk_widget_shell_shows_menubar (widget))
+ {
+ *minimum_height = 0;
+ *natural_height = 0;
+ }
+}
+
+static void
+hijacked_menu_bar_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_get_preferred_width_for_height != NULL)
+ (* pre_hijacked_menu_bar_get_preferred_width_for_height) (widget, height, minimum_width, natural_width);
+
+ if (gtk_widget_shell_shows_menubar (widget))
+ {
+ *minimum_width = 0;
+ *natural_width = 0;
+ }
+}
+
+static void
+hijacked_menu_bar_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ g_return_if_fail (GTK_IS_MENU_BAR (widget));
+
+ if (pre_hijacked_menu_bar_get_preferred_height_for_width != NULL)
+ (* pre_hijacked_menu_bar_get_preferred_height_for_width) (widget, width, minimum_height, natural_height);
+
+ if (gtk_widget_shell_shows_menubar (widget))
+ {
+ *minimum_height = 0;
+ *natural_height = 0;
+ }
+}
+#endif
+
+static void
+hijack_window_class_vtable (GType type)
+{
+ GtkWidgetClass *widget_class = g_type_class_ref (type);
+ GType *children;
+ guint n;
+ guint i;
+
+ if (widget_class->realize == pre_hijacked_window_realize)
+ widget_class->realize = hijacked_window_realize;
+
+#if GTK_MAJOR_VERSION == 3
+ if (widget_class->realize == pre_hijacked_application_window_realize)
+ widget_class->realize = hijacked_application_window_realize;
+#endif
+
+ if (widget_class->unrealize == pre_hijacked_window_unrealize)
+ widget_class->unrealize = hijacked_window_unrealize;
+
+ children = g_type_children (type, &n);
+
+ for (i = 0; i < n; i++)
+ hijack_window_class_vtable (children[i]);
+
+ g_free (children);
+}
+
+static void
+hijack_menu_bar_class_vtable (GType type)
+{
+ GtkWidgetClass *widget_class = g_type_class_ref (type);
+ GType *children;
+ guint n;
+ guint i;
+
+ /* This fixes lp:1113008. */
+ widget_class->hierarchy_changed = NULL;
+
+ if (widget_class->realize == pre_hijacked_menu_bar_realize)
+ widget_class->realize = hijacked_menu_bar_realize;
+
+ if (widget_class->unrealize == pre_hijacked_menu_bar_unrealize)
+ widget_class->unrealize = hijacked_menu_bar_unrealize;
+
+ if (widget_class->size_allocate == pre_hijacked_menu_bar_size_allocate)
+ widget_class->size_allocate = hijacked_menu_bar_size_allocate;
+
+#if GTK_MAJOR_VERSION == 2
+ if (widget_class->size_request == pre_hijacked_menu_bar_size_request)
+ widget_class->size_request = hijacked_menu_bar_size_request;
+#elif GTK_MAJOR_VERSION == 3
+ if (widget_class->get_preferred_width == pre_hijacked_menu_bar_get_preferred_width)
+ widget_class->get_preferred_width = hijacked_menu_bar_get_preferred_width;
+
+ if (widget_class->get_preferred_height == pre_hijacked_menu_bar_get_preferred_height)
+ widget_class->get_preferred_height = hijacked_menu_bar_get_preferred_height;
+
+ if (widget_class->get_preferred_width_for_height == pre_hijacked_menu_bar_get_preferred_width_for_height)
+ widget_class->get_preferred_width_for_height = hijacked_menu_bar_get_preferred_width_for_height;
+
+ if (widget_class->get_preferred_height_for_width == pre_hijacked_menu_bar_get_preferred_height_for_width)
+ widget_class->get_preferred_height_for_width = hijacked_menu_bar_get_preferred_height_for_width;
+#endif
+
+ children = g_type_children (type, &n);
+
+ for (i = 0; i < n; i++)
+ hijack_menu_bar_class_vtable (children[i]);
+
+ g_free (children);
+}
+
+G_MODULE_EXPORT
+void gtk_module_init(void)
+{
+ if (!topmenu_is_blacklisted())
+ {
+ GtkWidgetClass *widget_class;
+
+ /* gtk_module_init may be called more than once in a resident module */
+ g_return_if_fail(!already_initialized);
+ already_initialized = TRUE;
+
+ /* store the base GtkWidget size_allocate vfunc */
+ widget_class = g_type_class_ref (GTK_TYPE_WIDGET);
+ pre_hijacked_widget_size_allocate = widget_class->size_allocate;
+
+#if GTK_MAJOR_VERSION == 3
+ /* store the base GtkApplicationWindow realize vfunc */
+ widget_class = g_type_class_ref (GTK_TYPE_APPLICATION_WINDOW);
+ pre_hijacked_application_window_realize = widget_class->realize;
+#endif
+
+ /* intercept window realize vcalls on GtkWindow */
+ widget_class = g_type_class_ref (GTK_TYPE_WINDOW);
+ pre_hijacked_window_realize = widget_class->realize;
+ pre_hijacked_window_unrealize = widget_class->unrealize;
+ hijack_window_class_vtable (GTK_TYPE_WINDOW);
+
+ /* intercept size request and allocate vcalls on GtkMenuBar (for hiding) */
+ widget_class = g_type_class_ref (GTK_TYPE_MENU_BAR);
+ pre_hijacked_menu_bar_realize = widget_class->realize;
+ pre_hijacked_menu_bar_unrealize = widget_class->unrealize;
+ pre_hijacked_menu_bar_size_allocate = widget_class->size_allocate;
+#if GTK_MAJOR_VERSION == 2
+ pre_hijacked_menu_bar_size_request = widget_class->size_request;
+#elif GTK_MAJOR_VERSION == 3
+ pre_hijacked_menu_bar_get_preferred_width = widget_class->get_preferred_width;
+ pre_hijacked_menu_bar_get_preferred_height = widget_class->get_preferred_height;
+ pre_hijacked_menu_bar_get_preferred_width_for_height = widget_class->get_preferred_width_for_height;
+ pre_hijacked_menu_bar_get_preferred_height_for_width = widget_class->get_preferred_height_for_width;
+#endif
+ hijack_menu_bar_class_vtable (GTK_TYPE_MENU_BAR);
+ }
+}
+
+G_MODULE_EXPORT
+const gchar * g_module_check_init(GModule *module)
+{
+ /* It is hard to unhijack Gtk's vtables, specially if some other module
+ * has decided to also hijack them.
+ * Thus, we just prevent unloading of this module. */
+ g_module_make_resident(module);
+ return NULL;
+}
diff --git a/module/menuitem-proxy.c b/module/menuitem-proxy.c
new file mode 100644
index 0000000..6a3e555
--- /dev/null
+++ b/module/menuitem-proxy.c
@@ -0,0 +1,421 @@
+/* Contains code from GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "../libtopmenu-client/topmenu-monitor.h"
+
+#include "menuitem-proxy.h"
+
+static gboolean static_data_ok = FALSE;
+static const gchar *pname_visible;
+static const gchar *pname_sensitive;
+static const gchar *pname_label;
+static const gchar *pname_submenu;
+
+static void init_static_data()
+{
+ if (!static_data_ok) {
+ pname_visible = g_intern_string("visible");
+ pname_sensitive = g_intern_string("sensitive");
+ pname_label = g_intern_string("label");
+ pname_submenu = g_intern_string("submenu");
+
+ static_data_ok = TRUE;
+ }
+}
+
+static void
+free_timeval (GTimeVal *val)
+{
+ g_slice_free (GTimeVal, val);
+}
+
+static void
+get_offsets (GtkMenu *menu,
+ gint *horizontal_offset,
+ gint *vertical_offset)
+{
+ gint vertical_padding;
+ gint horizontal_padding;
+
+ gtk_widget_style_get (GTK_WIDGET (menu),
+ "horizontal-offset", horizontal_offset,
+ "vertical-offset", vertical_offset,
+ "horizontal-padding", &horizontal_padding,
+ "vertical-padding", &vertical_padding,
+ NULL);
+
+ *vertical_offset -= GTK_WIDGET (menu)->style->ythickness;
+ *vertical_offset -= vertical_padding;
+ *horizontal_offset += horizontal_padding;
+}
+
+static void
+menu_item_position_menu (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GtkMenuItem *menu_item;
+ GtkWidget *widget;
+ GtkMenuItem *parent_menu_item;
+ GdkScreen *screen;
+ gint twidth, theight;
+ gint tx, ty;
+ GtkTextDirection direction;
+ GdkRectangle monitor;
+ gint monitor_num;
+ gint horizontal_offset;
+ gint vertical_offset;
+ gint parent_xthickness;
+ gint available_left, available_right;
+
+ g_return_if_fail (menu != NULL);
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ menu_item = GTK_MENU_ITEM (user_data);
+ widget = GTK_WIDGET (user_data);
+
+ if (push_in)
+ *push_in = FALSE;
+
+ direction = gtk_widget_get_direction (widget);
+
+ twidth = GTK_WIDGET (menu)->requisition.width;
+ theight = GTK_WIDGET (menu)->requisition.height;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, menu_item->event_window);
+ if (monitor_num < 0)
+ monitor_num = 0;
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ if (!gdk_window_get_origin (widget->window, &tx, &ty))
+ {
+ g_warning ("Menu not on screen");
+ return;
+ }
+
+ tx += widget->allocation.x;
+ ty += widget->allocation.y;
+
+ get_offsets (menu, &horizontal_offset, &vertical_offset);
+
+ available_left = tx - monitor.x;
+ available_right = monitor.x + monitor.width - (tx + widget->allocation.width);
+
+ if (GTK_IS_MENU_BAR (widget->parent))
+ {
+ menu_item->from_menubar = TRUE;
+ }
+ else if (GTK_IS_MENU (widget->parent))
+ {
+ if (GTK_MENU (widget->parent)->parent_menu_item)
+ menu_item->from_menubar = GTK_MENU_ITEM (GTK_MENU (widget->parent)->parent_menu_item)->from_menubar;
+ else
+ menu_item->from_menubar = FALSE;
+ }
+ else
+ {
+ menu_item->from_menubar = FALSE;
+ }
+
+ switch (menu_item->submenu_placement)
+ {
+ case GTK_TOP_BOTTOM:
+ if (direction == GTK_TEXT_DIR_LTR)
+ menu_item->submenu_direction = GTK_DIRECTION_RIGHT;
+ else
+ {
+ menu_item->submenu_direction = GTK_DIRECTION_LEFT;
+ tx += widget->allocation.width - twidth;
+ }
+ if ((ty + widget->allocation.height + theight) <= monitor.y + monitor.height)
+ ty += widget->allocation.height;
+ else if ((ty - theight) >= monitor.y)
+ ty -= theight;
+ else if (monitor.y + monitor.height - (ty + widget->allocation.height) > ty)
+ ty += widget->allocation.height;
+ else
+ ty -= theight;
+ break;
+
+ case GTK_LEFT_RIGHT:
+ if (GTK_IS_MENU (widget->parent))
+ parent_menu_item = GTK_MENU_ITEM (GTK_MENU (widget->parent)->parent_menu_item);
+ else
+ parent_menu_item = NULL;
+
+ parent_xthickness = widget->parent->style->xthickness;
+
+ if (parent_menu_item && !GTK_MENU (widget->parent)->torn_off)
+ {
+ menu_item->submenu_direction = parent_menu_item->submenu_direction;
+ }
+ else
+ {
+ if (direction == GTK_TEXT_DIR_LTR)
+ menu_item->submenu_direction = GTK_DIRECTION_RIGHT;
+ else
+ menu_item->submenu_direction = GTK_DIRECTION_LEFT;
+ }
+
+ switch (menu_item->submenu_direction)
+ {
+ case GTK_DIRECTION_LEFT:
+ if (tx - twidth - parent_xthickness - horizontal_offset >= monitor.x ||
+ available_left >= available_right)
+ tx -= twidth + parent_xthickness + horizontal_offset;
+ else
+ {
+ menu_item->submenu_direction = GTK_DIRECTION_RIGHT;
+ tx += widget->allocation.width + parent_xthickness + horizontal_offset;
+ }
+ break;
+
+ case GTK_DIRECTION_RIGHT:
+ if (tx + widget->allocation.width + parent_xthickness + horizontal_offset + twidth <= monitor.x + monitor.width ||
+ available_right >= available_left)
+ tx += widget->allocation.width + parent_xthickness + horizontal_offset;
+ else
+ {
+ menu_item->submenu_direction = GTK_DIRECTION_LEFT;
+ tx -= twidth + parent_xthickness + horizontal_offset;
+ }
+ break;
+ }
+
+ ty += vertical_offset;
+
+ /* If the height of the menu doesn't fit we move it upward. */
+ ty = CLAMP (ty, monitor.y, MAX (monitor.y, monitor.y + monitor.height - theight));
+ break;
+ }
+
+ /* If we have negative, tx, here it is because we can't get
+ * the menu all the way on screen. Favor the left portion.
+ */
+ *x = CLAMP (tx, monitor.x, MAX (monitor.x, monitor.x + monitor.width - twidth));
+ *y = ty;
+
+ gtk_menu_set_monitor (menu, monitor_num);
+
+ if (!gtk_widget_get_visible (menu->toplevel))
+ {
+ gtk_window_set_type_hint (GTK_WINDOW (menu->toplevel), menu_item->from_menubar?
+ GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU : GDK_WINDOW_TYPE_HINT_POPUP_MENU);
+ }
+}
+
+static void handle_menuitem_notify(GtkMenuItem *item, GParamSpec *pspec, GtkMenuItem *proxy)
+{
+ // Note that it is OK to compare strings by pointer as they are all interned
+ if (pspec->name == pname_submenu) {
+ // Nothing to do!
+ } else if (pspec->name == pname_label) {
+ const gchar *label = gtk_menu_item_get_label(item);
+ gtk_menu_item_set_label(proxy, label);
+ } else if (pspec->name == pname_sensitive) {
+ gtk_widget_set_sensitive(GTK_WIDGET(proxy),
+ gtk_widget_get_sensitive(GTK_WIDGET(item)));
+ } else if (pspec->name == pname_visible) {
+ if (gtk_widget_get_visible(GTK_WIDGET(item))) {
+ gtk_widget_show(GTK_WIDGET(proxy));
+ } else {
+ gtk_widget_hide(GTK_WIDGET(proxy));
+ }
+ }
+}
+
+static gboolean handle_menuitem_mnemonic_activate(GtkMenuItem *item, gboolean cycling, GtkMenuItem *proxy)
+{
+ TopMenuMonitor *monitor = topmenu_monitor_get_instance();
+ GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(proxy));
+
+ if (parent && monitor->available) {
+ GtkMenuShell *parent_shell = GTK_MENU_SHELL(parent);
+ if (GTK_IS_MENU_BAR(parent_shell) || parent_shell->active) {
+ gtk_widget_mnemonic_activate(GTK_WIDGET(proxy), cycling);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean handle_menu_leave_notify(GtkMenu *menu, GdkEvent *event, GtkMenuItem *item)
+{
+ return TRUE;
+}
+
+static void handle_parent_move_current(GtkMenuShell *shell, GtkMenuDirectionType dir, GtkMenuItem *item)
+{
+ if (dir == GTK_MENU_DIR_CHILD) {
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+ if (submenu) {
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(submenu), TRUE);
+ }
+ }
+}
+
+static void handle_proxy_select(GtkMenuItem *proxy, GtkMenuItem *item)
+{
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+
+ if (submenu) {
+ if (!gtk_widget_is_sensitive(GTK_WIDGET(submenu)))
+ return;
+
+ GtkMenuShell *parent_shell = GTK_MENU_SHELL(GTK_WIDGET(proxy)->parent);
+ GTimeVal *popup_time = g_slice_new0(GTimeVal);
+
+ g_get_current_time(popup_time);
+ g_object_set_data_full(G_OBJECT(submenu),
+ "gtk-menu-exact-popup-time", popup_time,
+ (GDestroyNotify) free_timeval);
+
+ g_signal_connect_object(submenu, "leave-notify-event",
+ G_CALLBACK(handle_menu_leave_notify), item, 0);
+
+ g_signal_connect_object(gtk_widget_get_parent(GTK_WIDGET(proxy)), "move-current",
+ G_CALLBACK(handle_parent_move_current), item, 0);
+
+ gtk_menu_popup(GTK_MENU(submenu),
+ GTK_WIDGET(parent_shell),
+ GTK_WIDGET(proxy),
+ menu_item_position_menu,
+ proxy,
+ parent_shell->button,
+ 0);
+ }
+}
+
+static void handle_proxy_deselect(GtkMenuItem *proxy, GtkMenuItem *item)
+{
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+
+ if (submenu) {
+ g_signal_handlers_disconnect_by_func(submenu, handle_menu_leave_notify, item);
+ g_signal_handlers_disconnect_by_func(gtk_widget_get_parent(GTK_WIDGET(proxy)),
+ handle_parent_move_current, item);
+ gtk_menu_popdown(GTK_MENU(submenu));
+ }
+}
+
+static void handle_proxy_activate(GtkMenuItem *proxy, GtkMenuItem *item)
+{
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+
+ if (submenu) {
+ // Do nothing!
+ } else {
+ gtk_menu_item_activate(item);
+ }
+}
+
+static void handle_proxy_activate_item(GtkMenuItem *proxy, GtkMenuItem *item)
+{
+ GtkWidget *submenu = gtk_menu_item_get_submenu(item);
+
+ if (submenu) {
+ GtkMenuShell *parent = GTK_MENU_SHELL(gtk_widget_get_parent(GTK_WIDGET(proxy)));
+ if (parent) {
+ if (!parent->active) {
+ //gtk_grab_add(GTK_WIDGET(parent));
+ //parent->have_grab = TRUE;
+ parent->active = TRUE;
+ }
+ gtk_menu_shell_select_item(parent, GTK_WIDGET(proxy));
+ gtk_menu_shell_select_first(GTK_MENU_SHELL(submenu), TRUE);
+ }
+ }
+}
+
+static GtkWidget *construct_image_widget_proxy(GtkImage *widget)
+{
+ GtkImageType itype = gtk_image_get_storage_type(widget);
+ gchar *icon_name;
+ GtkIconSize icon_size;
+ switch (itype) {
+ case GTK_IMAGE_STOCK:
+ gtk_image_get_stock(widget, &icon_name, &icon_size);
+ return gtk_image_new_from_stock(icon_name, icon_size);
+ case GTK_IMAGE_EMPTY:
+ return gtk_image_new();
+ default:
+ return gtk_image_new();
+ }
+}
+
+GtkMenuItem *topmenu_create_proxy_menu_item(GtkMenuItem *item)
+{
+ init_static_data();
+
+ GtkMenuItem *proxy = NULL;
+
+ const gchar *label = gtk_menu_item_get_label(item);
+
+ if (GTK_IS_IMAGE_MENU_ITEM(item)) {
+ GtkImageMenuItem *iitem = GTK_IMAGE_MENU_ITEM(item);
+ if (gtk_image_menu_item_get_use_stock(iitem)) {
+ proxy = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(label, NULL));
+ } else {
+ GtkWidget *iwidget = gtk_image_menu_item_get_image(iitem);
+ proxy = GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(label));
+ if (iwidget) {
+ // Let's suppport some common widget types
+ if (GTK_IS_IMAGE(iwidget)) {
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(proxy),
+ construct_image_widget_proxy(GTK_IMAGE(iwidget)));
+ }
+ }
+ }
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(proxy),
+ gtk_image_menu_item_get_always_show_image(iitem));
+ }
+ else if (GTK_IS_SEPARATOR_MENU_ITEM(item)) {
+ proxy = GTK_MENU_ITEM(gtk_separator_menu_item_new());
+ } else {
+ proxy = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(label));
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(proxy),
+ gtk_widget_get_sensitive(GTK_WIDGET(item)));
+
+ if (gtk_widget_get_visible(GTK_WIDGET(item))) {
+ gtk_widget_show(GTK_WIDGET(proxy));
+ }
+
+ g_signal_connect_object(item, "notify",
+ G_CALLBACK(handle_menuitem_notify), proxy, 0);
+ g_signal_connect_object(item, "mnemonic-activate",
+ G_CALLBACK(handle_menuitem_mnemonic_activate), proxy, 0);
+
+ g_signal_connect_object(proxy, "select",
+ G_CALLBACK(handle_proxy_select), item, 0);
+ g_signal_connect_object(proxy, "deselect",
+ G_CALLBACK(handle_proxy_deselect), item, 0);
+ g_signal_connect_object(proxy, "activate",
+ G_CALLBACK(handle_proxy_activate), item, 0);
+ g_signal_connect_object(proxy, "activate-item",
+ G_CALLBACK(handle_proxy_activate_item), item, 0);
+
+ return proxy;
+}
diff --git a/module/menuitem-proxy.h b/module/menuitem-proxy.h
new file mode 100644
index 0000000..7a13a80
--- /dev/null
+++ b/module/menuitem-proxy.h
@@ -0,0 +1,8 @@
+#ifndef _MENUITEM_PROXY_H_
+#define _MENUITEM_PROXY_H_
+
+#include <gtk/gtk.h>
+
+GtkMenuItem * topmenu_create_proxy_menu_item (GtkMenuItem *item);
+
+#endif
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..4455221
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,12 @@
+noinst_PROGRAMS = client server
+
+AM_CPPFLAGS = $(GTK_CFLAGS)
+AM_LDFLAGS = $(GTK_LIBS)
+
+client_SOURCES = client.c
+client_CPPFLAGS = $(GTK_CFLAGS)
+client_LDADD = $(GTK_LIBS) ../libtopmenu-client/libtopmenu-client.la
+
+server_SOURCES = server.c
+server_CPPFLAGS = $(GTK_CFLAGS)
+server_LDADD = $(GTK_LIBS) ../libtopmenu-server/libtopmenu-server.la
diff --git a/test/client.c b/test/client.c
new file mode 100644
index 0000000..08312e6
--- /dev/null
+++ b/test/client.c
@@ -0,0 +1,79 @@
+#include <stdlib.h>
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "../libtopmenu-client/topmenu-client.h"
+#include "../libtopmenu-client/topmenu-monitor.h"
+
+static GtkWindow *mainwin;
+
+static GtkWidget * create_menu_bar(void)
+{
+ GtkMenuBar *bar = GTK_MENU_BAR(gtk_menu_bar_new());
+ GtkMenuItem *app = GTK_MENU_ITEM(gtk_menu_item_new_with_label("Client"));
+ GtkMenuItem *file = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic("_File"));
+ GtkMenuItem *edit = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic("_Edit"));
+ GtkMenuItem *help = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic("_Help"));
+
+ GtkLabel *app_label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(app)));
+ gtk_label_set_markup(app_label, "<b>Client</b>");
+
+ gtk_menu_bar_append(bar, GTK_WIDGET(app));
+ gtk_menu_bar_append(bar, GTK_WIDGET(file));
+ gtk_menu_bar_append(bar, GTK_WIDGET(edit));
+ gtk_menu_bar_append(bar, GTK_WIDGET(help));
+
+ GtkMenu *app_menu = GTK_MENU(gtk_menu_new());
+ GtkMenuItem *quit = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL));
+ gtk_menu_append(app_menu, GTK_WIDGET(quit));
+ gtk_menu_item_set_submenu(app, GTK_WIDGET(app_menu));
+
+ GtkMenu *file_menu = GTK_MENU(gtk_menu_new());
+ GtkMenuItem *new = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL));
+ gtk_menu_append(file_menu, GTK_WIDGET(new));
+ GtkMenuItem *open = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL));
+ gtk_menu_append(file_menu, GTK_WIDGET(open));
+ GtkMenuItem *close = GTK_MENU_ITEM(gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL));
+ gtk_menu_append(file_menu, GTK_WIDGET(close));
+ gtk_menu_item_set_submenu(file, GTK_WIDGET(file_menu));
+
+ return GTK_WIDGET(bar);
+}
+
+GtkWindow * create_main_window()
+{
+ GtkWindow *win = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ GtkVBox *box = GTK_VBOX(gtk_vbox_new(FALSE, 0));
+ GtkLabel *label = GTK_LABEL(gtk_label_new("Hello World"));
+
+ GtkWidget *bar = create_menu_bar();
+
+ gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(bar), FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(label), TRUE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(box));
+
+ return win;
+}
+
+int main(int argc, char **argv)
+{
+ gtk_set_locale();
+ gtk_init(&argc, &argv);
+
+ mainwin = create_main_window();
+ topmenu_monitor_get_instance();
+
+ g_signal_connect(mainwin, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+
+ gtk_widget_realize(GTK_WIDGET(mainwin));
+
+ topmenu_client_connect_window_widget(gtk_widget_get_window(GTK_WIDGET(mainwin)),
+ create_menu_bar());
+
+ gtk_widget_show_all(GTK_WIDGET(mainwin));
+
+ gtk_main();
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/server.c b/test/server.c
new file mode 100644
index 0000000..d0b9135
--- /dev/null
+++ b/test/server.c
@@ -0,0 +1,40 @@
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+
+#include "../libtopmenu-server/topmenu-widget.h"
+
+static GtkWindow *mainwin;
+static TopMenuWidget *topmenu;
+
+static gboolean handle_button_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ g_debug("Mainwin: button press");
+ return FALSE;
+}
+
+static void construct_main_window()
+{
+ mainwin = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ topmenu = TOPMENU_WIDGET(topmenu_widget_new());
+ gtk_container_add(GTK_CONTAINER(mainwin), GTK_WIDGET(topmenu));
+}
+
+int main(int argc, char **argv)
+{
+ gtk_set_locale();
+ gtk_init(&argc, &argv);
+
+ construct_main_window();
+
+ g_signal_connect(mainwin, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+ g_signal_connect(mainwin, "button-press-event", G_CALLBACK(handle_button_press), NULL);
+
+ gtk_window_set_keep_above(mainwin, TRUE);
+ gtk_window_set_accept_focus(mainwin, FALSE);
+ gtk_widget_show_all(GTK_WIDGET(mainwin));
+
+ gtk_main();
+
+ return EXIT_SUCCESS;
+}