changeset 20882:38c4fe48ebb5

Add new API to the docklet for retrieving the geometry (x,y,w,h) and the GdkScreen (if available). The win32 implementation of this is really ugly and could use some testing. Refs #521 and #1632.
author Casey Harkins <charkins@pidgin.im>
date Fri, 12 Oct 2007 02:47:48 +0000
parents c7b20abc3885
children e26c39c96f26 d3e0eb05ac94
files pidgin/gtkdocklet-x11.c pidgin/gtkdocklet.c pidgin/gtkdocklet.h pidgin/win32/gtkdocklet-win32.c
diffstat 4 files changed, 269 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/pidgin/gtkdocklet-x11.c	Fri Oct 12 02:33:31 2007 +0000
+++ b/pidgin/gtkdocklet-x11.c	Fri Oct 12 02:47:48 2007 +0000
@@ -229,6 +229,35 @@
 }
 
 static gboolean
+docklet_x11_get_geometry(gint *x, gint *y, gint *w, gint *h)
+{
+	int lx,ly;
+	GtkWidget *widget = GTK_WIDGET(docklet);
+
+	if(docklet==NULL) return FALSE;
+
+	gdk_window_get_origin(GDK_WINDOW(widget->window), &lx, &ly);
+
+	if(x!=NULL) *x = lx + widget->allocation.x;
+	if(y!=NULL) *y = ly + widget->allocation.y;
+
+	if(w!=NULL) *w = widget->allocation.width;
+	if(h!=NULL) *h = widget->allocation.height;
+
+	return TRUE;
+}
+
+static GObject *
+docklet_x11_get_gdk_screen()
+{
+#if GTK_CHECK_VERSION(2,2,0)
+	return (GObject *)gtk_widget_get_screen(GTK_WIDGET(docklet));
+#else
+	return NULL;
+#endif
+}
+
+static gboolean
 docklet_x11_embed_timeout_cb()
 {
 	/* The docklet was not embedded within the timeout.
@@ -313,10 +342,12 @@
 	docklet_x11_blank_icon,
 	docklet_x11_set_tooltip,
 #if GTK_CHECK_VERSION(2,2,0)
-	docklet_x11_position_menu
+	docklet_x11_position_menu,
 #else
-	NULL
+	NULL,
 #endif
+	docklet_x11_get_geometry,
+	docklet_x11_get_gdk_screen
 };
 
 void
--- a/pidgin/gtkdocklet.c	Fri Oct 12 02:33:31 2007 +0000
+++ b/pidgin/gtkdocklet.c	Fri Oct 12 02:47:48 2007 +0000
@@ -709,6 +709,22 @@
 	return &i;
 }
 
+gboolean
+pidgin_docklet_get_geometry(gint *x, gint *y, gint *w, gint *h)
+{
+	if(!visible) return FALSE;
+	if(ui_ops && ui_ops->get_geometry) return ui_ops->get_geometry(x, y, w, h);
+	return FALSE;
+}
+
+GObject *
+pidgin_docklet_get_gdk_screen()
+{
+	if(!visible) return NULL;
+	if(ui_ops && ui_ops->get_gdk_screen) return ui_ops->get_gdk_screen();
+	return NULL;
+}
+
 void
 pidgin_docklet_init()
 {
--- a/pidgin/gtkdocklet.h	Fri Oct 12 02:33:31 2007 +0000
+++ b/pidgin/gtkdocklet.h	Fri Oct 12 02:47:48 2007 +0000
@@ -35,6 +35,8 @@
 	void (*blank_icon)(void);
 	void (*set_tooltip)(gchar *);
 	GtkMenuPositionFunc position_menu;
+	gboolean (*get_geometry)(gint *x, gint *y, gint *w, gint *h);
+	GObject *(*get_gdk_screen)(void);
 };
 
 
@@ -49,6 +51,28 @@
 void pidgin_docklet_uninit(void);
 void*pidgin_docklet_get_handle(void);
 
+/**
+ * Get the geometry of the docklet. Any of the parameters may be
+ * NULL if that value is not desired.
+ *
+ * @param x x coordinate of the top left corner of the docklet in screen coordinates
+ * @param y y coordinate of the top left corner of the docklet in screen coordinates
+ * @param w width of the docklet
+ * @param h height of the docklet
+ *
+ * @return TRUE if the geometry was found, otherwise FALSE
+ */
+gboolean pidgin_docklet_get_geometry(gint *x, gint *y, gint *w, gint *h);
+
+/**
+ * Get the GdkScreen of the docklet. If the GdkScreen is not available, including
+ * running on versions of Gtk/Gdk where GdkScreen does not exist (<2.2), NULL is
+ * returned.
+ *
+ * @return GdkScreen if available, otherwise NULL
+ */
+GObject *pidgin_docklet_get_gdk_screen(void);
+
 /* function in gtkdocklet-{x11,win32}.c */
 void docklet_ui_init(void);
 
--- a/pidgin/win32/gtkdocklet-win32.c	Fri Oct 12 02:33:31 2007 +0000
+++ b/pidgin/win32/gtkdocklet-win32.c	Fri Oct 12 02:47:48 2007 +0000
@@ -570,6 +570,199 @@
 	RestoreWndFromTray(GDK_WINDOW_HWND(gtkblist->window->window));
 }
 
+/* Checks to see if a window matches a specified name. If it matches,
+ * the matched_hwnd pointer is set to the checked window.
+ *
+ * hwnd is the window to check
+ * matched_hwnd points to hwnd on a match
+ * name is the expected class name
+ * 
+ * returns TRUE if there was a match, otherwise FALSE
+ */
+static BOOL
+check_hwnd_class_name(HWND hwnd, HWND *matched_hwnd, char *name)
+{
+	TCHAR class_name[256];
+
+	/* get class name of window */
+	GetClassName(hwnd, class_name, 255);
+
+	/* compare class name with specified name */
+	if(strncmp(class_name, name, 255)!=0) return FALSE;
+
+	/* set matched_hwnd to hwnd */
+	*matched_hwnd = hwnd;
+	return TRUE;
+}
+
+/* callback for EnumChildWindows looking for TrayNotifyWnd */
+static BOOL CALLBACK
+find_tray_notify_hwnd_cb(HWND hwnd, LPARAM lparam)
+{
+	return !check_hwnd_class_name(hwnd, (HWND*)lparam, "TrayNotifyWnd");
+}
+
+/* callback for EnumChildWindows looking for ToolbarWindow32 */
+static BOOL CALLBACK
+find_tray_toolbar_hwnd_cb(HWND hwnd, LPARAM lparam)
+{
+	return !check_hwnd_class_name(hwnd, (HWND*)lparam, "ToolbarWindow32");
+}
+
+static HWND
+get_tray_toolbar_hwnd()
+{
+	HWND shell_tray_hwnd = NULL;
+	HWND tray_notify_hwnd = NULL;
+	HWND tray_toolbar_hwnd = NULL;
+
+	/* find the top-level window of the system tray area */
+	shell_tray_hwnd = FindWindow("Shell_TrayWnd", NULL);
+	if(!shell_tray_hwnd) return NULL;
+
+	/* enumerate over the shell_tray_hwnd children windows looking for the tray_notify_hwnd */
+	EnumChildWindows(shell_tray_hwnd, find_tray_notify_hwnd_cb, (LPARAM)&tray_notify_hwnd);
+	if(!tray_notify_hwnd || !IsWindow(tray_notify_hwnd)) return NULL;
+
+	/* enumerate over the tray_notify_hwnd children windows looking for tray_toolbar_hwnd */
+	EnumChildWindows(tray_notify_hwnd, find_tray_toolbar_hwnd_cb, (LPARAM)&tray_toolbar_hwnd);
+	if(!tray_toolbar_hwnd || !IsWindow(tray_toolbar_hwnd)) return NULL;
+
+	return tray_toolbar_hwnd;
+}
+
+
+/* Get the geometry of the tray icon. This might break if the user is running a
+ * non-standard shell, in which case this function will return FALSE. If the
+ * tray icon is hidden (possible >= winxp), then the geometry of the tray itself
+ * is returned. If FALSE is returned, x, y, w and h are left unchanged.
+ * Any of the parameters (x, y, w, h) may be NULL if that value is not
+ * desired.
+ *
+ * This code is based on the method and code described here by Irek Zielinski:
+ *     http://www.codeproject.com/shell/ctrayiconposition.asp?msg=999295
+ */
+static gboolean
+winpidgin_tray_get_geometry(gint *x, gint *y, gint *w, gint *h)
+{
+	/* systray_hwnd is the parent window of our systray icon */
+	HWND tray_toolbar_hwnd = NULL;
+	DWORD tray_toolbar_pid = -1;
+	HANDLE tray_toolbar_proc = NULL;
+	int tray_toolbar_bcount = 0;
+	LPVOID tray_toolbar_mem = NULL;
+
+	TBBUTTON button;
+	DWORD nbytes = -1;
+	DWORD hwnd_id_pair[2] = { -1, -1};
+	RECT rect;
+	POINT top_left;
+	POINT bot_right;
+	gboolean found_docklet = FALSE;
+	int i;
+
+	/* get the tray_toolbar_hwnd */
+	tray_toolbar_hwnd = get_tray_toolbar_hwnd();
+	if(!tray_toolbar_hwnd) {
+		return FALSE;
+	}
+
+	/* count buttons in the tray_toolbar_hwnd */
+	tray_toolbar_bcount = SendMessage(tray_toolbar_hwnd, TB_BUTTONCOUNT, 0, 0);
+	if(tray_toolbar_bcount < 1) {
+		return FALSE;
+	}
+
+	/* get pid of the tray_toolbar_hwnd parent process */
+	GetWindowThreadProcessId(tray_toolbar_hwnd, &tray_toolbar_pid);
+	if(tray_toolbar_pid <= 0) {
+		return FALSE;
+	}
+
+	/* open the tray_toolbar_hwnd parent process */
+	tray_toolbar_proc = OpenProcess(PROCESS_ALL_ACCESS, 0, tray_toolbar_pid);
+	if(!tray_toolbar_proc) {
+		return FALSE;
+	}
+
+	/* allocate some memory in the tray_toolbar_hwnd process space */
+	tray_toolbar_mem = VirtualAllocEx(tray_toolbar_proc, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
+	if(!tray_toolbar_mem) {
+		CloseHandle(tray_toolbar_proc);
+		return FALSE;
+	}
+
+	/* loop through buttons, looking for the docklet */
+	for(i=0; i<tray_toolbar_bcount; i++) {
+
+		/* get the button */
+		SendMessage(tray_toolbar_hwnd, TB_GETBUTTON, i, (LPARAM)tray_toolbar_mem);
+		ReadProcessMemory(tray_toolbar_proc, tray_toolbar_mem, &button, sizeof(TBBUTTON), &nbytes);
+		if(nbytes < sizeof(TBBUTTON)) {
+			continue;
+		}
+
+		/* get the dwData from the button */
+		ReadProcessMemory(tray_toolbar_proc, (LPVOID)button.dwData, &hwnd_id_pair, sizeof(hwnd_id_pair), &nbytes);
+		if(nbytes < sizeof(hwnd_id_pair)) {
+			continue;
+		}
+
+		/* compare hwnd of button against systray_hwnd */
+		if((HWND)hwnd_id_pair[0] != systray_hwnd) {
+			continue;
+		}
+
+		/* check if button is hidden */
+		if(button.fsState & TBSTATE_HIDDEN) {
+			break;
+		}
+
+		/* get RECT of docklet icon */
+		SendMessage(tray_toolbar_hwnd, TB_GETITEMRECT, i, (LPARAM)tray_toolbar_mem);
+		ReadProcessMemory(tray_toolbar_proc, tray_toolbar_mem, &rect, sizeof(RECT), &nbytes);
+		if(nbytes < sizeof(RECT)) {
+			break;
+		}
+
+		/* translate to screen coordinates */
+		top_left.x = rect.left;
+		top_left.y = rect.top;
+		bot_right.x = rect.right;
+		bot_right.y = rect.bottom;
+
+		MapWindowPoints(tray_toolbar_hwnd, NULL, (LPPOINT)&top_left, 1);
+		MapWindowPoints(tray_toolbar_hwnd, NULL, (LPPOINT)&bot_right, 1);
+
+		found_docklet = TRUE;
+		break;
+	}
+
+	if(!found_docklet) {
+		/* fallback on geometry of tray itself */
+		GetWindowRect(tray_toolbar_hwnd, &rect);
+		if(x!=NULL) *x = rect.left;
+		if(y!=NULL) *y = rect.top;
+		if(w!=NULL) *w = rect.right - rect.left;
+		if(h!=NULL) *h = rect.bottom - rect.top;
+	} else {
+		if(x!=NULL) *x = top_left.x;
+		if(y!=NULL) *y = top_left.y;
+		if(w!=NULL) *w = bot_right.x - top_left.x;
+		if(h!=NULL) *h = bot_right.y - top_left.y;
+	}
+
+	/* clean up */
+	VirtualFreeEx(tray_toolbar_proc, tray_toolbar_mem, 0, MEM_RELEASE);
+	CloseHandle(tray_toolbar_proc);
+	return TRUE;
+}
+
+static GObject *
+winpidgin_tray_get_gdk_screen()
+{
+	return (GObject *)gdk_screen_get_default();
+}
 
 static void winpidgin_tray_create() {
 	OSVERSIONINFO osinfo;
@@ -658,7 +851,9 @@
 	winpidgin_tray_update_icon,
 	winpidgin_tray_blank_icon,
 	winpidgin_tray_set_tooltip,
-	NULL
+	NULL,
+	winpidgin_tray_get_geometry,
+	winpidgin_tray_get_gdk_screen
 };
 
 /* Used by docklet's plugin load func */