开发者

Override window close behavior

I want to catch all tries to close some specific existing Cocoa window and add some own handler (which might indeed really close it or do something different).

I had different solutions in mind to do this. One was:

I want to replace the window close button of an existing Cocoa window at runtime with an own close widget where I can add some own code.

Right now, I have this code:

import objc
_NSThemeCloseWidget = objc.lookUpClass("_NSThemeCloseWidget")

def find_close_widget(window):
    contentView = window.contentView()
    grayFrame = contentView.superview()
    for i in range(len(grayFrame.subviews())):
        v = grayFrame.subviews()[i]
        if isinstance(v, _NSThemeCloseWidget):
            return v, i, grayFrame

class CustomCloseWidget(_NSThemeCloseWidget):
    pass

def replace_close_widget(window, clazz=CustomCloseWidget):
    v, i, grayFrame = find_close_widget(window)
    newv = clazz.alloc().init(开发者_如何学Python)
    grayFrame.subviews()[i] = newv

However, this doesn't seem quite right. (It crashes.)


The close widget isn't the only way to close the window. There's a public API to obtain the widget, so you don't need to go rifling through the frame view's subviews, but that's the wrong path anyway.

The right way is to make an object to be the window's delegate, and interfere with the window's closure there. Ideally, you should set the window's delegate in between creating the window and ordering it in.


I am going another route now. This is partly Chrome related but it can easily be adopted elsewhere. I wanted to catch several actions for closing the window as early as possible to avoid any other cleanups or so which resulted in the window being in a strange state.

def check_close_callback(obj):
    # check ...
    return True # or:
    return False

import objc
BrowserWindowController = objc.lookUpClass("BrowserWindowController")

# copied from objc.signature to avoid warning
def my_signature(signature, **kw):
    from objc._objc import selector
    kw['signature'] = signature
    def makeSignature(func):
        return selector(func, **kw)
    return makeSignature

windowWillCloseSig = "c12@0:4@8" # BrowserWindowController.windowWillClose_.signature
commandDispatchSig = "v12@0:4@8"
class BrowserWindowController(objc.Category(BrowserWindowController)):
    @my_signature(windowWillCloseSig)
    def myWindowShouldClose_(self, sender):
        print "myWindowShouldClose", self, sender
        if not check_close_callback(self): return objc.NO
        return self.myWindowShouldClose_(sender) # this is no recursion when we exchanged the methods

    @my_signature(commandDispatchSig)
    def myCommandDispatch_(self, cmd):
        try: print "myCommandDispatch_", self, cmd
        except: pass # like <type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode character u'\u2026' in position 37: ordinal not in range(128)
        if cmd.tag() == 34015: # IDC_CLOSE_TAB
            if not check_close_callback(self): return           
        self.myCommandDispatch_(cmd)

from ctypes import *
capi = pythonapi

# id objc_getClass(const char *name)
capi.objc_getClass.restype = c_void_p
capi.objc_getClass.argtypes = [c_char_p]

# SEL sel_registerName(const char *str)
capi.sel_registerName.restype = c_void_p
capi.sel_registerName.argtypes = [c_char_p]

def capi_get_selector(name):
    return c_void_p(capi.sel_registerName(name))

# Method class_getInstanceMethod(Class aClass, SEL aSelector)
# Will also search superclass for implementations.
capi.class_getInstanceMethod.restype = c_void_p
capi.class_getInstanceMethod.argtypes = [c_void_p, c_void_p]

# void method_exchangeImplementations(Method m1, Method m2)
capi.method_exchangeImplementations.restype = None
capi.method_exchangeImplementations.argtypes = [c_void_p, c_void_p]

def method_exchange(className, origSelName, newSelName):
    clazz = capi.objc_getClass(className)
    origMethod = capi.class_getInstanceMethod(clazz, capi_get_selector(origSelName))
    newMethod = capi.class_getInstanceMethod(clazz, capi_get_selector(newSelName))
    capi.method_exchangeImplementations(origMethod, newMethod)

def hook_into_windowShouldClose():
    method_exchange("BrowserWindowController", "windowShouldClose:", "myWindowShouldClose:")

def hook_into_commandDispatch():
    method_exchange("BrowserWindowController", "commandDispatch:", "myCommandDispatch:")

This code is from here and here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜