diff options
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)>k_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; +} |