soc.2009.vulture: e3c8c88d: Create thread for glib and libpurple. Me...

gdick at soc.pidgin.im gdick at soc.pidgin.im
Tue May 26 17:20:34 EDT 2009


-----------------------------------------------------------------
Revision: e3c8c88d222f3a44a062679d623efc96e8650e43
Ancestor: e609da9de37a0dda998591037bcfa294343687b1
Author: gdick at soc.pidgin.im
Date: 2009-05-26T21:02:47
Branch: im.pidgin.soc.2009.vulture
URL: http://d.pidgin.im/viewmtn/revision/info/e3c8c88d222f3a44a062679d623efc96e8650e43

Added files:
        vulture/purplemain.c vulture/purplemain.h
        vulture/purplequeue.c vulture/purplequeue.h
Modified files:
        vulture/Makefile.mingw vulture/vulture.c

ChangeLog: 

Create thread for glib and libpurple. Mechanism for calling into this thread
from the UI thread.

-------------- next part --------------
============================================================
--- vulture/purplemain.c	e8c5342881802739b4e1b87081736515bf125c1c
+++ vulture/purplemain.c	e8c5342881802739b4e1b87081736515bf125c1c
@@ -0,0 +1,106 @@
+/*
+ * Vulture - Win32 libpurple client
+ *
+ * purplemain.c: Fundamentals of the interface with libpurple, and the glib
+ * main loop.
+ *
+ * Copyright (C) 2009, Gregor Dick <gdick at soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+/**
+ * \file purplemain.c
+ * Fundamentals of the interface with libpurple, and the glib main loop.
+ * 
+ * Since the window manager can cause the UI thread to enter a modal loop for
+ * various reasons, the glib main loop (and consequently libpurple) runs in its
+ * own thread. This is not without its inconveniences: in particular, the UI
+ * thread can't call libpurple directly; instead it must do so using the
+ * message-queue mechanism. Another caveat is that this thread must wake up
+ * whenever there are synchronous messages queued, which in practice means that
+ * this thread should not make blocking calls, apart from to g_main_loop_run
+ * itself.
+ */
+
+#include <windows.h>
+#include <process.h>
+#include <glib.h>
+
+#include "vulture.h"
+#include "purplemain.h"
+#include "purplequeue.h"
+
+
+static UINT CALLBACK PurpleThread(void *lpvData);
+
+
+GMainLoop *g_lpgmainloop = NULL;
+
+
+/**
+ * Initialises libpurple by spawning a thread on which to run the glib main
+ * loop. The queues are first initialised in the context of the calling thread
+ * so that the caller may start enqueuing calls as soon as this function
+ * returns.
+ *
+ * @param[out]	lphthread	Handle to new thread is returned here. -1L on
+ *				error.
+ */
+void VultureInitLibpurple(HANDLE *lphthread)
+{
+	VulturePurpleInitQueue();
+
+	*lphthread = (HANDLE)_beginthreadex(NULL, 0, PurpleThread, NULL, 0, NULL);
+}
+
+
+/**
+ * Shuts down libpurple. Its thread should already have terminated before this
+ * function is called.
+ */
+void VultureShutDownLibpurple(void)
+{
+	VulturePurpleDestroyQueue();
+}
+
+
+/**
+ * Thread procedure for the thread in which libpurple runs.
+ *
+ * @param	lpvData		Currently unused.
+ *
+ * @return Always zero. This is used as the thread's exit code.
+ */
+static UINT CALLBACK PurpleThread(void *lpvData)
+{
+	GSource *lpgsourceSync = VultureCreateSyncQueueSource();
+	GSource *lpgsourceAsync = VultureCreateAsyncQueueSource();
+
+	UNREFERENCED_PARAMETER(lpvData);
+
+	g_lpgmainloop = g_main_loop_new(NULL, TRUE);
+
+	g_source_attach(lpgsourceSync, NULL);
+	g_source_attach(lpgsourceAsync, NULL);
+
+	g_main_loop_run(g_lpgmainloop);
+
+	g_main_loop_unref(g_lpgmainloop);
+	g_source_unref(lpgsourceAsync);
+	g_source_unref(lpgsourceSync);
+
+	return 0;
+}
============================================================
--- vulture/purplemain.h	b1e574134ccb2954c14aa7528fa5173224e8ac97
+++ vulture/purplemain.h	b1e574134ccb2954c14aa7528fa5173224e8ac97
@@ -0,0 +1,36 @@
+/*
+ * Vulture - Win32 libpurple client
+ *
+ * purplemain.c: Fundamentals of the interface with libpurple, and the glib
+ * main loop.
+ *
+ * Copyright (C) 2009, Gregor Dick <gdick at soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+
+#ifndef _VULTURE_PURPLEMAIN_H_
+#define _VULTURE_PURPLEMAIN_H_
+
+#include <windows.h>
+#include <glib.h>
+
+void VultureInitLibpurple(HANDLE *lphthread);
+void VultureShutDownLibpurple(void);
+
+extern GMainLoop *g_lpgmainloop;
+
+#endif
============================================================
--- vulture/purplequeue.c	6c545ceaa7b26d23fbd7c8753a55017416af3b20
+++ vulture/purplequeue.c	6c545ceaa7b26d23fbd7c8753a55017416af3b20
@@ -0,0 +1,325 @@
+/*
+ * Vulture - Win32 libpurple client
+ *
+ * purplequeue.c: Event queue for the interface with libpurple.
+ *
+ * Copyright (C) 2009, Gregor Dick <gdick at soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include <windows.h>
+#include <glib.h>
+
+#include "vulture.h"
+#include "purplemain.h"
+#include "purplequeue.h"
+
+
+/** Queue node representing a libpurple call. */
+typedef struct _PURPLE_CALL
+{
+	int	iCallID;	/** < Function identifier. */
+	void	*lpvParam;	/** < Function-specific data. */
+	HANDLE	heventSync;	/** < Event for synchronous calls. */
+} PURPLE_CALL;
+
+
+/** A GSource-derived structure used to poll libpurple call queues. */
+typedef struct _QUEUE_SOURCE
+{
+	GSource			gsource;	/** < Opaque structure used internally by glib. */
+	GPollFD			gpollfdQueue;	/** < Event that signals queue population. */
+	GQueue			*lpgq;		/** < Queue. */
+	LPCRITICAL_SECTION	lpcs;		/** < Critical section protecting queue. */
+} QUEUE_SOURCE;
+
+
+static INLINE PURPLE_CALL* NewPurpleCall(int iCallID, void *lpvParam, HANDLE heventSync);
+static void DispatchPurpleCall(PURPLE_CALL *lppurplecall);
+static void DequeueAndDispatchPurpleCall(GQueue *lpgq, LPCRITICAL_SECTION lpcs, HANDLE heventQueue);
+static QUEUE_SOURCE* CreateQueueSource(GQueue *lpgq, LPCRITICAL_SECTION lpcs, HANDLE heventQueue, gint iPriority);
+static gboolean QueuePrepare(GSource *lpgsource, int *lpiTimeout);
+static gboolean QueueCheck(GSource *lpgsource);
+static gboolean QueueDispatch(GSource *lpgsource, GSourceFunc gsfCallback, gpointer lpvData);
+
+
+#define ASYNC_QUEUE_PRIORITY	G_PRIORITY_DEFAULT
+#define SYNC_QUEUE_PRIORITY	(G_PRIORITY_DEFAULT - 1)
+
+
+/* Events that indicate whether there are libpurple calls waiting in each
+ * queue.
+ */
+static HANDLE g_heventSyncQueue = NULL, g_heventAsyncQueue = NULL;
+
+static CRITICAL_SECTION g_csSyncQueue, g_csAsyncQueue;
+static GQueue *g_lpgqSyncQueue = NULL, *g_lpgqAsyncQueue = NULL;
+
+
+/** Initialises the queues of calls to libpurple from outside the thread. */
+void VulturePurpleInitQueue(void)
+{
+	g_heventSyncQueue = CreateEvent(NULL, TRUE, FALSE, NULL);
+	g_heventAsyncQueue = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+	InitializeCriticalSection(&g_csSyncQueue);
+	InitializeCriticalSection(&g_csAsyncQueue);
+
+	g_lpgqSyncQueue = g_queue_new();
+	g_lpgqAsyncQueue = g_queue_new();
+}
+
+
+/** Destroys the queues of calls to libpurple from outside the thread. */
+void VulturePurpleDestroyQueue(void)
+{
+	while(!g_queue_is_empty(g_lpgqAsyncQueue))
+		g_free(g_queue_pop_head(g_lpgqAsyncQueue));
+
+	while(!g_queue_is_empty(g_lpgqSyncQueue))
+		g_free(g_queue_pop_head(g_lpgqSyncQueue));
+
+	g_queue_free(g_lpgqAsyncQueue);
+	g_queue_free(g_lpgqSyncQueue);
+
+	DeleteCriticalSection(&g_csAsyncQueue);
+	DeleteCriticalSection(&g_csSyncQueue);
+
+	CloseHandle(g_heventAsyncQueue);
+	CloseHandle(g_heventSyncQueue);
+}
+
+
+/**
+ * Creates a PURPLE_CALL queue node.
+ *
+ * @param	iCallID		ID of the operation to perform.
+ * @param	lpvParam	Function-specific data.
+ * @param	heventSync	Event handle. Should be NULL iff async.
+ *
+ * @return New queue node. Free with g_free.
+ */
+static INLINE PURPLE_CALL* NewPurpleCall(int iCallID, void *lpvParam, HANDLE heventSync)
+{
+	PURPLE_CALL *lppurplecall = g_malloc(sizeof(PURPLE_CALL));
+
+	lppurplecall->iCallID = iCallID;
+	lppurplecall->lpvParam = lpvParam;
+	lppurplecall->heventSync = heventSync;
+
+	return lppurplecall;
+}
+
+
+/**
+ * Enqueues an asynchronous call to libpurple.
+ *
+ * @param	iCallID		ID of the operation to perform.
+ * @param	lpvParam	Function-specific data.
+ */
+void VultureEnqueueAsyncPurpleCall(int iCallID, void *lpvParam)
+{
+	PURPLE_CALL *lppurplecall = NewPurpleCall(iCallID, lpvParam, NULL);
+
+	EnterCriticalSection(&g_csAsyncQueue);
+		g_queue_push_tail(g_lpgqAsyncQueue, lppurplecall);
+		SetEvent(g_heventAsyncQueue);
+	LeaveCriticalSection(&g_csAsyncQueue);
+}
+
+
+/**
+ * Enqueues a synchronous call to libpurple.
+ *
+ * @param	iCallID		ID of the operation to perform.
+ * @param	lpvParam	Function-specific data.
+ * @param	heventSync	Event handle for synchronisation.
+ */
+void VultureEnqueueSyncPurpleCall(int iCallID, void *lpvParam, HANDLE heventSync)
+{
+	PURPLE_CALL *lppurplecall = NewPurpleCall(iCallID, lpvParam, heventSync);
+
+	EnterCriticalSection(&g_csSyncQueue);
+		g_queue_push_tail(g_lpgqSyncQueue, lppurplecall);
+		SetEvent(g_heventSyncQueue);
+	LeaveCriticalSection(&g_csSyncQueue);
+}
+
+
+/**
+ * Dispatches and frees a call to libpurple.
+ *
+ * @param	lppurplecall	Call info.
+ */
+static void DispatchPurpleCall(PURPLE_CALL *lppurplecall)
+{
+	switch(lppurplecall->iCallID)
+	{
+	case PC_QUIT:
+		g_main_loop_quit(g_lpgmainloop);
+		break;
+	}
+
+	/* Let the caller know we're done if they care. */
+	if(lppurplecall->heventSync)
+		SetEvent(lppurplecall->heventSync);
+
+	g_free(lppurplecall);
+}
+
+
+/**
+ * Dequeues the call at the head of a queue, dispatches and frees it. The queue
+ * must not be empty. The population event is cleared if the last event in the
+ * queue is dispatched.
+ *
+ * @param	lpgq		Queue of calls.
+ * @param	lpcs		Critical section for access to the queue.
+ * @param	heventQueue	Population event for the queue.
+ */
+static void DequeueAndDispatchPurpleCall(GQueue *lpgq, LPCRITICAL_SECTION lpcs, HANDLE heventQueue)
+{
+	PURPLE_CALL *lppurplecall;
+
+	EnterCriticalSection(lpcs);
+		lppurplecall = g_queue_pop_head(lpgq);
+	LeaveCriticalSection(lpcs);
+
+	DispatchPurpleCall(lppurplecall);
+
+	EnterCriticalSection(lpcs);
+		if(g_queue_is_empty(lpgq))
+			ResetEvent(heventQueue);
+	LeaveCriticalSection(lpcs);
+}
+
+
+/**
+ * Creates a new GSource-derived object for polling call-queues.
+ *
+ * @param	lpgq		Queue of calls.
+ * @param	lpcs		Critical section for access to the queue.
+ * @param	heventQueue	Handle signalling queue population.
+ * @param	iPriority	Priority to assign to the source.
+ *
+ * @return Source object. Release a reference using g_source_unref when the
+ * caller is finished with it.
+ */
+static QUEUE_SOURCE* CreateQueueSource(GQueue *lpgq, LPCRITICAL_SECTION lpcs, HANDLE heventQueue, gint iPriority)
+{
+	static GSourceFuncs gsfQueue = {QueuePrepare, QueueCheck, QueueDispatch, NULL, NULL, NULL};
+	QUEUE_SOURCE *lpqsource = (QUEUE_SOURCE*)g_source_new(&gsfQueue, sizeof(QUEUE_SOURCE));
+
+	lpqsource->gpollfdQueue.events = G_IO_IN;
+	lpqsource->gpollfdQueue.fd = (gint)heventQueue;
+
+	lpqsource->lpgq = lpgq;
+	lpqsource->lpcs = lpcs;
+
+	g_source_add_poll(&lpqsource->gsource, &lpqsource->gpollfdQueue);
+
+	g_source_set_priority(&lpqsource->gsource, iPriority);
+
+	return lpqsource;
+}
+
+
+/**
+ * prepare() function for a QUEUE_SOURCE. We always want to poll, so we just
+ * return FALSE after specifying an infinite timeout.
+ *
+ * @param	lpgsource	Ignored.
+ * @param[out]	lpiTimeout	Set to -1.
+ *
+ * @return Always FALSE.
+ */
+static gboolean QueuePrepare(GSource *lpgsource, int *lpiTimeout)
+{
+	UNREFERENCED_PARAMETER(lpgsource);
+	*lpiTimeout = -1;
+	return FALSE;
+}
+
+
+/**
+ * check() function for a QUEUE_SOURCE. Determines whether there are any calls
+ * enqueued (which trivially there will be, as we are only polling one handle
+ * in each source and *one* handle in the source is set).
+ *
+ * @param	lpgsource	QUEUE_SOURCE.
+ *
+ * @return Whether there are messages waiting.
+ */
+static gboolean QueueCheck(GSource *lpgsource)
+{
+	/* Check whether the event is signalled. */
+	return (WaitForSingleObject((HANDLE)((QUEUE_SOURCE*)lpgsource)->gpollfdQueue.fd, 0) == WAIT_OBJECT_0);
+}
+
+
+/**
+ * dispatch() function for a QUEUE_SOURCE. Dispatches a message from the front
+ * of the queue.
+ *
+ * @param	lpgsource	QUEUE_SOURCE.
+ * @param	gsfCallback	Ignored.
+ * @param	lpvData		Ignored.
+ *
+ * @return Always TRUE.
+ */
+static gboolean QueueDispatch(GSource *lpgsource, GSourceFunc gsfCallback, gpointer lpvData)
+{
+	QUEUE_SOURCE *lpqsource = (QUEUE_SOURCE*)lpgsource;
+
+	UNREFERENCED_PARAMETER(gsfCallback);
+	UNREFERENCED_PARAMETER(lpvData);
+
+	EnterCriticalSection(lpqsource->lpcs);
+
+		/* Sanity check: make sure we have a call to dispatch. */
+		if(WaitForSingleObject((HANDLE)lpqsource->gpollfdQueue.fd, 0) == WAIT_OBJECT_0)
+		{
+			DequeueAndDispatchPurpleCall(lpqsource->lpgq, lpqsource->lpcs, (HANDLE)lpqsource->gpollfdQueue.fd);
+		}
+
+	LeaveCriticalSection(lpqsource->lpcs);
+
+	return TRUE;
+}
+
+
+/**
+ * Creates a new GSource-derived object for polling the async queue.
+ *
+ * @return Source object. Release a reference using g_source_unref when the
+ * caller is finished with it.
+ */
+GSource* VultureCreateAsyncQueueSource(void)
+{
+	return &CreateQueueSource(g_lpgqAsyncQueue, &g_csAsyncQueue, g_heventAsyncQueue, ASYNC_QUEUE_PRIORITY)->gsource;
+}
+
+
+/**
+ * Creates a new GSource-derived object for polling the sync queue.
+ *
+ * @return Source object. Release a reference using g_source_unref when the
+ * caller is finished with it.
+ */
+GSource* VultureCreateSyncQueueSource(void)
+{
+	return &CreateQueueSource(g_lpgqSyncQueue, &g_csSyncQueue, g_heventSyncQueue, SYNC_QUEUE_PRIORITY)->gsource;
+}
============================================================
--- vulture/purplequeue.h	04e1c2fbd16adb662b8f251eccb4e56031b6ea08
+++ vulture/purplequeue.h	04e1c2fbd16adb662b8f251eccb4e56031b6ea08
@@ -0,0 +1,49 @@
+/*
+ * Vulture - Win32 libpurple client
+ *
+ * purplequeue.h: Event queue for the interface with libpurple.
+ *
+ * Copyright (C) 2009, Gregor Dick <gdick at soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+
+#ifndef _VULTURE_PURPLEQUEUE_H_
+#define _VULTURE_PURPLEQUEUE_H_
+
+
+#include <windows.h>
+#include <glib.h>
+
+
+/** IDs of calls to libpurple. Used as values for iCallID member of
+ * PURPLE_CALL.
+ */
+enum PURPLE_CALL_ID
+{
+	PC_QUIT,
+};
+
+
+void VulturePurpleInitQueue(void);
+void VulturePurpleDestroyQueue(void);
+void VultureEnqueueAsyncPurpleCall(int iCallID, void *lpvParam);
+void VultureEnqueueSyncPurpleCall(int iCallID, void *lpvParam, HANDLE heventSync);
+GSource* VultureCreateAsyncQueueSource(void);
+GSource* VultureCreateSyncQueueSource(void);
+
+
+#endif
============================================================
--- vulture/Makefile.mingw	496be4f1917cc9baa264fddb5a5c3c94268a98ef
+++ vulture/Makefile.mingw	a78cc081f80cb404068906796366af446c50c20e
@@ -52,7 +52,9 @@ VULTURE_C_SRC =	\
 ##
 VULTURE_C_SRC =	\
 			vulture.c \
-			blist.c
+			blist.c \
+			purplemain.c \
+			purplequeue.c
 
 # VULTURE_RC_SRC = win32/pidgin_dll_rc.rc
 VULTURE_OBJECTS = $(VULTURE_C_SRC:%.c=%.o) $(VULTURE_RC_SRC:%.rc=%.o)
============================================================
--- vulture/vulture.c	eb6152f146d96bfe47df224a1f85fb1e405e6a19
+++ vulture/vulture.c	c5cb303b8b87b8aa843418e4014c77b52bae4c4b
@@ -25,6 +25,8 @@
 
 #include "vulture.h"
 #include "blist.h"
+#include "purplemain.h"
+#include "purplequeue.h"
 
 
 HINSTANCE g_hInstance;
@@ -44,16 +46,35 @@ int WINAPI WinMain(HINSTANCE hinst, HINS
 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int iCmdShow)
 {
 	MSG msg;
+	HANDLE hthreadPurple;
 
 	g_hInstance = hinst;
 
-	VultureCreateBuddyList(iCmdShow);
+	if(VultureCreateBuddyList(iCmdShow) != 0)
+	{
+		return 1;
+	}
 
+	VultureInitLibpurple(&hthreadPurple);
+	if(hthreadPurple == (HANDLE)-1L)
+	{
+		return 2;
+	}
+
 	while(GetMessage(&msg, NULL, 0, 0))
 	{
 		TranslateMessage(&msg);
 		DispatchMessage(&msg);
 	}
 
+	/* UI has shut down; do the same to libpurple, waiting until it's
+	 * complete.
+	 */
+	VultureEnqueueAsyncPurpleCall(PC_QUIT, NULL);
+	WaitForSingleObject(hthreadPurple, INFINITE);
+	CloseHandle(hthreadPurple);
+
+	VultureShutDownLibpurple();
+
 	return msg.wParam;
 }


More information about the Commits mailing list