Introduction
On the Mac platform applications use a form of IPC called "AppleEvents" for things like opening files, printing files, killing the application, etc.
Typically this is done by installing AE handlers using the MiniAEFrame.py or Carbon.AE modules included with MacPython. But this won't work in wxPython. This is because wxPython must mediate the communication between native(the AE event loop) and Python(the installed handler) to maintain the current interpreter state.
Solution
The solution is to write a native extension module that will catch the AppleEvent, restore the Python interpreter state, then call the Python handler. This is done by the two functions wxPyBeginBlockThreads() and wxPyEndBlockThreads().
The example below is a simple extension that allows setting of a handler for GURL(get URL) events. GURL events signal an application to load a particular URL. It should be fairly straightforward to modify it to handle other event types.
This technique may not work for events that are already caught by wxMAC, such as kAEOpenDocuments, kAEOpenApplication, kAEPrintDocuments, and kAEQuitApplication. The wxMAC event loop traps these events and translates them to method calls on the wxApp object, and you can override these methods in your app object. See Optimizing for Mac OS X for an example.
The Extension Module
#include <Python.h> #include <wx/wxPython/wxPython.h> #include <stdio.h> #ifdef __cplusplus extern "C" { #endif #include <Carbon/Carbon.h> #ifdef __cplusplus } #endif #define kURLEventClass 'GURL' #define kGetURLEvent 'GURL' static PyObject *my_callback = NULL; static OSErr MyHandleGURL(AppleEvent *theAppleEvent, AppleEvent* reply, long handlerRefCon) { OSErr err; DescType returnedType; Size actualSize; char URLString[255]; if ((err = AEGetParamPtr(theAppleEvent, keyDirectObject, typeChar, &returnedType, URLString, sizeof(URLString)-1, &actualSize)) != noErr){ return err; } URLString[actualSize] = 0; // Terminate the C string if (my_callback){ PyObject *arglist, *result; arglist = Py_BuildValue("(s)", URLString); wxPyBlock_t blocked = wxPyBeginBlockThreads(); result = PyEval_CallObject(my_callback, arglist); if (result){ Py_DECREF(arglist); } wxPyEndBlockThreads(blocked); } return noErr; } static PyObject * setgurlhandler(PyObject *dummy, PyObject *args) { OSErr err; PyObject *result = NULL; PyObject *temp; AEEventHandlerUPP upp; if (PyArg_ParseTuple(args, "O:set_callback", &temp)) { if (!PyCallable_Check(temp)) { PyErr_SetString(PyExc_TypeError, "parameter must be callable"); return NULL; } Py_XINCREF(temp); /* Add a reference to new callback */ Py_XDECREF(my_callback); /* Dispose of previous callback */ my_callback = temp; /* Remember new callback */ /* Boilerplate to return "None" */ Py_INCREF(Py_None); result = Py_None; upp = NewAEEventHandlerUPP((AEEventHandlerProcPtr)MyHandleGURL); err = AEInstallEventHandler('GURL', 'GURL', upp, 0, FALSE); } return result; } static PyMethodDef wxAEMethods[] = { {"setgurlhandler", setgurlhandler, METH_VARARGS, "setgurlhandler(func)"}, {NULL, NULL, 0, NULL} }; #ifdef __cplusplus extern "C" { #endif void initwxae(void){ wxPyCoreAPI_IMPORT(); (void)Py_InitModule("wxae", wxAEMethods); } #ifdef __cplusplus } #endif
Building
To compile this you will need to adjust the setup script given here to match the version of wxPython you have installed.
Here's the distutils setup.py I used to compile:
from distutils.core import setup, Extension import os WXBASE="/usr/local/lib/wxPython-unicode-2.6.1.0/" defines = [('SWIG_GLOBAL', None), ('HAVE_CONFIG_H', None), ('WXP_USE_THREAD', '1'), ] cflags = os.popen(WXBASE+'bin/wx-config --cxxflags', 'r').read()[:-1] cflags = cflags.split() lflags = os.popen(WXBASE+'bin/wx-config --libs', 'r').read()[:-1] lflags = lflags.split() setup(name = "wxae", version = "1.0", description = "", ext_modules = [Extension("wxae", ["wxaemodule.cpp"], define_macros = defines, extra_compile_args = cflags, extra_link_args = lflags, )] )
Run with the command:
python setup.py build install
Example Usage
import wx import wxae def printURL(url): dlg = wx.MessageDialog(self, 'Got URL Event: '+url, '', wx.OK | wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() class App(wx.App): def OnInit(self): wxae.setgurlhandler(printURL) frame = wx.Frame(None, -1, 'AE Test', size=(300,200)) frame.Show(True) return True if __name__ == '__main__': app = App(0) app.MainLoop()
Comments
author = 'Jason Petrone <jpetrone@cnri.reston.va.us>'