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.
精彩评论