diff options
Diffstat (limited to 'libtopmenu-client')
-rw-r--r-- | libtopmenu-client/Makefile.am | 8 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-appmenubar.c | 86 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-appmenubar.h | 38 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-client.c | 113 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-client.h | 13 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-menubar-proxy.h | 44 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-monitor.c | 163 | ||||
-rw-r--r-- | libtopmenu-client/topmenu-monitor.h | 39 |
8 files changed, 504 insertions, 0 deletions
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_ */ |