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>'

Catching AppleEvents in wxMAC (last edited 2009-11-25 06:51:58 by vpn-8061f451)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.