Difficult-Rocket/libs/pyglet/app/cocoa.py
2023-03-22 00:08:03 +08:00

247 lines
8.8 KiB
Python

import signal
from pyglet import app
from pyglet.app.base import PlatformEventLoop, EventLoop
from pyglet.libs.darwin import cocoapy, AutoReleasePool, ObjCSubclass, PyObjectEncoding, ObjCInstance, send_super, \
ObjCClass, get_selector
NSApplication = cocoapy.ObjCClass('NSApplication')
NSMenu = cocoapy.ObjCClass('NSMenu')
NSMenuItem = cocoapy.ObjCClass('NSMenuItem')
NSDate = cocoapy.ObjCClass('NSDate')
NSEvent = cocoapy.ObjCClass('NSEvent')
NSUserDefaults = cocoapy.ObjCClass('NSUserDefaults')
NSTimer = cocoapy.ObjCClass('NSTimer')
def add_menu_item(menu, title, action, key):
with AutoReleasePool():
title = cocoapy.CFSTR(title)
action = cocoapy.get_selector(action)
key = cocoapy.CFSTR(key)
menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
title, action, key)
menu.addItem_(menuItem)
# cleanup
menuItem.release()
def create_menu():
with AutoReleasePool():
appMenu = NSMenu.alloc().init()
# Hide still doesn't work!?
add_menu_item(appMenu, 'Hide!', 'hide:', 'h')
appMenu.addItem_(NSMenuItem.separatorItem())
add_menu_item(appMenu, 'Quit!', 'terminate:', 'q')
menubar = NSMenu.alloc().init()
appMenuItem = NSMenuItem.alloc().init()
appMenuItem.setSubmenu_(appMenu)
menubar.addItem_(appMenuItem)
NSApp = NSApplication.sharedApplication()
NSApp.setMainMenu_(menubar)
# cleanup
appMenu.release()
menubar.release()
appMenuItem.release()
class _AppDelegate_Implementation:
_AppDelegate = ObjCSubclass('NSObject', '_AppDelegate')
@_AppDelegate.method(b'@' + PyObjectEncoding)
def init(self, pyglet_loop):
objc = ObjCInstance(send_super(self, 'init'))
self._pyglet_loop = pyglet_loop
return objc # objc is self
@_AppDelegate.method('v')
def updatePyglet_(self):
self._pyglet_loop.nsapp_step()
@_AppDelegate.method('v@')
def applicationWillTerminate_(self, notification):
self._pyglet_loop.is_running = False
self._pyglet_loop.has_exit = True
@_AppDelegate.method('v@')
def applicationDidFinishLaunching_(self, notification):
self._pyglet_loop._finished_launching = True
_AppDelegate = ObjCClass('_AppDelegate') # the actual class
class CocoaAlternateEventLoop(EventLoop):
"""This is an alternate loop developed mainly for ARM64 variants of macOS.
nextEventMatchingMask_untilDate_inMode_dequeue_ is very broken with ctypes calls. Events eventually stop
working properly after X returns. This event loop differs in that it uses the built-in NSApplication event
loop. We tie our schedule into it via timer.
"""
def run(self, interval=1/60):
if not interval:
self.clock.schedule(self._redraw_windows)
else:
self.clock.schedule_interval(self._redraw_windows, interval)
self.has_exit = False
from pyglet.window import Window
Window._enable_event_queue = False
# Dispatch pending events
for window in app.windows:
window.switch_to()
window.dispatch_pending_events()
self.platform_event_loop = app.platform_event_loop
self.dispatch_event('on_enter')
self.is_running = True
self.platform_event_loop.nsapp_start(interval)
def exit(self):
"""Safely exit the event loop at the end of the current iteration.
This method is a thread-safe equivalent for setting
:py:attr:`has_exit` to ``True``. All waiting threads will be
interrupted (see :py:meth:`sleep`).
"""
self.has_exit = True
self.platform_event_loop.notify()
self.is_running = False
self.dispatch_event('on_exit')
self.platform_event_loop.nsapp_stop()
class CocoaPlatformEventLoop(PlatformEventLoop):
def __init__(self):
super(CocoaPlatformEventLoop, self).__init__()
with AutoReleasePool():
# Prepare the default application.
self.NSApp = NSApplication.sharedApplication()
if self.NSApp.isRunning():
# Application was already started by GUI library (e.g. wxPython).
return
if not self.NSApp.mainMenu():
create_menu()
self.NSApp.setActivationPolicy_(cocoapy.NSApplicationActivationPolicyRegular)
# Prevent Lion / Mountain Lion from automatically saving application state.
# If we don't do this, new windows will not display on 10.8 after finishLaunching
# has been called.
defaults = NSUserDefaults.standardUserDefaults()
ignoreState = cocoapy.CFSTR("ApplePersistenceIgnoreState")
if not defaults.objectForKey_(ignoreState):
defaults.setBool_forKey_(True, ignoreState)
holdEnabled = cocoapy.CFSTR("ApplePressAndHoldEnabled")
if not defaults.objectForKey_(holdEnabled):
defaults.setBool_forKey_(False, holdEnabled)
self._finished_launching = False
def start(self):
with AutoReleasePool():
if not self.NSApp.isRunning() and not self._finished_launching:
# finishLaunching should be called only once. However isRunning will not
# guard this, as we are not using the normal event loop.
self.NSApp.finishLaunching()
self.NSApp.activateIgnoringOtherApps_(True)
self._finished_launching = True
def nsapp_start(self, interval):
"""Used only for CocoaAlternateEventLoop"""
from pyglet.app import event_loop
self._event_loop = event_loop
def term_received(*args):
if self.timer:
self.timer.invalidate()
self.timer = None
self.nsapp_stop()
# Force NSApp to close if Python receives sig events.
signal.signal(signal.SIGINT, term_received)
signal.signal(signal.SIGTERM, term_received)
self.appdelegate = _AppDelegate.alloc().init(self)
self.NSApp.setDelegate_(self.appdelegate)
self.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
interval, # Clamped internally to 0.0001 (including 0)
self.appdelegate,
get_selector('updatePyglet:'),
False,
True
)
self.NSApp.run()
def nsapp_step(self):
"""Used only for CocoaAlternateEventLoop"""
self._event_loop.idle()
self.dispatch_posted_events()
def nsapp_stop(self):
"""Used only for CocoaAlternateEventLoop"""
self.NSApp.terminate_(None)
def step(self, timeout=None):
with AutoReleasePool():
self.dispatch_posted_events()
# Determine the timeout date.
if timeout is None:
# Using distantFuture as untilDate means that nextEventMatchingMask
# will wait until the next event comes along.
timeout_date = NSDate.distantFuture()
elif timeout == 0.0:
timeout_date = NSDate.distantPast()
else:
timeout_date = NSDate.dateWithTimeIntervalSinceNow_(timeout)
# Retrieve the next event (if any). We wait for an event to show up
# and then process it, or if timeout_date expires we simply return.
# We only process one event per call of step().
self._is_running.set()
event = self.NSApp.nextEventMatchingMask_untilDate_inMode_dequeue_(
cocoapy.NSAnyEventMask, timeout_date, cocoapy.NSDefaultRunLoopMode, True)
# Dispatch the event (if any).
if event is not None:
event_type = event.type()
if event_type != cocoapy.NSApplicationDefined:
self.NSApp.sendEvent_(event)
self.NSApp.updateWindows()
did_time_out = False
else:
did_time_out = True
self._is_running.clear()
return did_time_out
def stop(self):
pass
def notify(self):
with AutoReleasePool():
notifyEvent = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(
cocoapy.NSApplicationDefined, # type
cocoapy.NSPoint(0.0, 0.0), # location
0, # modifierFlags
0, # timestamp
0, # windowNumber
None, # graphicsContext
0, # subtype
0, # data1
0, # data2
)
self.NSApp.postEvent_atStart_(notifyEvent, False)