You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1367 lines
50 KiB
Python

# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
'''
'''
__docformat__ = 'restructuredtext'
__version__ = '$Id: $'
from ctypes import *
import os.path
import unicodedata
import warnings
import pyglet
from pyglet.window import WindowException, Platform, Display, Screen, \
BaseWindow, MouseCursor, DefaultMouseCursor, _PlatformEventHandler
from pyglet.window import key
from pyglet.window import mouse
from pyglet.window import event
from pyglet.window.carbon.constants import *
from pyglet.window.carbon.types import *
from pyglet.window.carbon.quartzkey import keymap
import pyglet.lib
from pyglet import gl
from pyglet.gl import agl
from pyglet.gl import gl_info
from pyglet.gl import glu_info
from pyglet.event import EventDispatcher
class CarbonException(WindowException):
pass
carbon = pyglet.lib.load_library(
framework='/System/Library/Frameworks/Carbon.framework')
quicktime = pyglet.lib.load_library(
framework='/System/Library/Frameworks/QuickTime.framework')
carbon.GetEventDispatcherTarget.restype = EventTargetRef
carbon.ReceiveNextEvent.argtypes = \
[c_uint32, c_void_p, c_double, c_ubyte, POINTER(EventRef)]
carbon.GetWindowPort.restype = agl.AGLDrawable
EventHandlerProcPtr = CFUNCTYPE(c_int, c_int, c_void_p, c_void_p)
carbon.NewEventHandlerUPP.restype = c_void_p
carbon.GetCurrentKeyModifiers = c_uint32
carbon.NewRgn.restype = RgnHandle
carbon.CGDisplayBounds.argtypes = [c_void_p]
carbon.CGDisplayBounds.restype = CGRect
# Map symbol,modifiers -> motion
# Determined by experiment with TextEdit.app
_motion_map = {
(key.UP, False): key.MOTION_UP,
(key.RIGHT, False): key.MOTION_RIGHT,
(key.DOWN, False): key.MOTION_DOWN,
(key.LEFT, False): key.MOTION_LEFT,
(key.LEFT, key.MOD_OPTION): key.MOTION_PREVIOUS_WORD,
(key.RIGHT, key.MOD_OPTION): key.MOTION_NEXT_WORD,
(key.LEFT, key.MOD_COMMAND): key.MOTION_BEGINNING_OF_LINE,
(key.RIGHT, key.MOD_COMMAND): key.MOTION_END_OF_LINE,
(key.PAGEUP, False): key.MOTION_PREVIOUS_PAGE,
(key.PAGEDOWN, False): key.MOTION_NEXT_PAGE,
(key.HOME, False): key.MOTION_BEGINNING_OF_FILE,
(key.END, False): key.MOTION_END_OF_FILE,
(key.UP, key.MOD_COMMAND): key.MOTION_BEGINNING_OF_FILE,
(key.DOWN, key.MOD_COMMAND): key.MOTION_END_OF_FILE,
(key.BACKSPACE, False): key.MOTION_BACKSPACE,
(key.DELETE, False): key.MOTION_DELETE,
}
class CarbonPlatform(Platform):
_display = None
def get_default_display(self):
if not self._display:
self._display = CarbonDisplay()
return self._display
class CarbonDisplay(Display):
# TODO: CarbonDisplay could be per display device, which would make
# reporting of screens and available configs more accurate. The number of
# Macs with more than one video card is probably small, though.
def __init__(self):
super(CarbonDisplay, self).__init__()
import MacOS
if not MacOS.WMAvailable():
raise CarbonException('Window manager is not available. ' \
'Ensure you run "pythonw", not "python"')
self._install_application_event_handlers()
def get_screens(self):
count = CGDisplayCount()
carbon.CGGetActiveDisplayList(0, None, byref(count))
displays = (CGDirectDisplayID * count.value)()
carbon.CGGetActiveDisplayList(count.value, displays, byref(count))
return [CarbonScreen(self, id) for id in displays]
def _install_application_event_handlers(self):
self._carbon_event_handlers = []
self._carbon_event_handler_refs = []
target = carbon.GetApplicationEventTarget()
# TODO something with a metaclass or hacky like CarbonWindow
# to make this list extensible
handlers = [
(self._on_mouse_down, kEventClassMouse, kEventMouseDown),
(self._on_apple_event, kEventClassAppleEvent, kEventAppleEvent),
(self._on_command, kEventClassCommand, kEventProcessCommand),
]
ae_handlers = [
(self._on_ae_quit, kCoreEventClass, kAEQuitApplication),
]
# Install the application-wide handlers
for method, cls, event in handlers:
proc = EventHandlerProcPtr(method)
self._carbon_event_handlers.append(proc)
upp = carbon.NewEventHandlerUPP(proc)
types = EventTypeSpec()
types.eventClass = cls
types.eventKind = event
handler_ref = EventHandlerRef()
carbon.InstallEventHandler(
target,
upp,
1,
byref(types),
c_void_p(),
byref(handler_ref))
self._carbon_event_handler_refs.append(handler_ref)
# Install Apple event handlers
for method, cls, event in ae_handlers:
proc = EventHandlerProcPtr(method)
self._carbon_event_handlers.append(proc)
upp = carbon.NewAEEventHandlerUPP(proc)
carbon.AEInstallEventHandler(
cls,
event,
upp,
0,
False)
def _on_command(self, next_handler, ev, data):
command = HICommand()
carbon.GetEventParameter(ev, kEventParamDirectObject,
typeHICommand, c_void_p(), sizeof(command), c_void_p(),
byref(command))
if command.commandID == kHICommandQuit:
self._on_quit()
return noErr
def _on_mouse_down(self, next_handler, ev, data):
# Check for menubar hit
position = Point()
carbon.GetEventParameter(ev, kEventParamMouseLocation,
typeQDPoint, c_void_p(), sizeof(position), c_void_p(),
byref(position))
if carbon.FindWindow(position, None) == inMenuBar:
# Mouse down in menu bar. MenuSelect() takes care of all
# menu tracking and blocks until the menu is dismissed.
# Use command events to handle actual menu item invokations.
# This function blocks, so tell the event loop it needs to install
# a timer.
from pyglet import app
if app.event_loop is not None:
app.event_loop._enter_blocking()
carbon.MenuSelect(position)
if app.event_loop is not None:
app.event_loop._exit_blocking()
# Menu selection has now returned. Remove highlight from the
# menubar.
carbon.HiliteMenu(0)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
def _on_apple_event(self, next_handler, ev, data):
# Somewhat involved way of redispatching Apple event contained
# within a Carbon event, described in
# http://developer.apple.com/documentation/AppleScript/
# Conceptual/AppleEvents/dispatch_aes_aepg/chapter_4_section_3.html
release = False
if carbon.IsEventInQueue(carbon.GetMainEventQueue(), ev):
carbon.RetainEvent(ev)
release = True
carbon.RemoveEventFromQueue(carbon.GetMainEventQueue(), ev)
ev_record = EventRecord()
carbon.ConvertEventRefToEventRecord(ev, byref(ev_record))
carbon.AEProcessAppleEvent(byref(ev_record))
if release:
carbon.ReleaseEvent(ev)
return noErr
def _on_ae_quit(self, ae, reply, refcon):
self._on_quit()
return noErr
def _on_quit(self):
'''Called when the user tries to quit the application.
This is not an actual event handler, it is called in response
to Command+Q, the Quit menu item, and the Dock context menu's Quit
item.
The default implementation sets `has_exit` to true on all open
windows. In pyglet 1.1 `has_exit` is set on `EventLoop` if it is
used instead of the windows.
'''
from pyglet import app
if app.event_loop is not None:
app.event_loop.exit()
else:
for window in self.get_windows():
window.has_exit = True
class CarbonScreen(Screen):
def __init__(self, display, id):
self.display = display
rect = carbon.CGDisplayBounds(id)
super(CarbonScreen, self).__init__(
int(rect.origin.x), int(rect.origin.y),
int(rect.size.width), int(rect.size.height))
self.id = id
mode = carbon.CGDisplayCurrentMode(id)
kCGDisplayRefreshRate = _create_cfstring('RefreshRate')
number = carbon.CFDictionaryGetValue(mode, kCGDisplayRefreshRate)
refresh = c_long()
kCFNumberLongType = 10
carbon.CFNumberGetValue(number, kCFNumberLongType, byref(refresh))
self._refresh_rate = refresh.value
def get_gdevice(self):
gdevice = GDHandle()
r = carbon.DMGetGDeviceByDisplayID(self.id, byref(gdevice), False)
_oscheck(r)
return gdevice
def get_matching_configs(self, template):
# Construct array of attributes for aglChoosePixelFormat
attrs = []
for name, value in template.get_gl_attributes():
attr = CarbonGLConfig._attribute_ids.get(name, None)
if not attr or not value:
continue
attrs.append(attr)
if attr not in CarbonGLConfig._boolean_attributes:
attrs.append(int(value))
# Support for RAGE-II, which is not compliant
attrs.append(agl.AGL_ALL_RENDERERS)
# Force selection policy and RGBA
attrs.append(agl.AGL_MAXIMUM_POLICY)
attrs.append(agl.AGL_RGBA)
# In 10.3 and later, AGL_FULLSCREEN is specified so the window can
# be toggled to/from fullscreen without losing context. pyglet
# no longer supports earlier versions of OS X, so we always supply it.
attrs.append(agl.AGL_FULLSCREEN)
# Terminate the list.
attrs.append(agl.AGL_NONE)
attrib_list = (c_int * len(attrs))(*attrs)
device = self.get_gdevice()
pformat = agl.aglChoosePixelFormat(device, 1, attrib_list)
_aglcheck()
if not pformat:
return []
else:
return [CarbonGLConfig(self, pformat)]
class CarbonGLConfig(gl.Config):
# Valid names for GL attributes, and their corresponding AGL constant.
_attribute_ids = {
'double_buffer': agl.AGL_DOUBLEBUFFER,
'stereo': agl.AGL_STEREO,
'buffer_size': agl.AGL_BUFFER_SIZE,
'sample_buffers': agl.AGL_SAMPLE_BUFFERS_ARB,
'samples': agl.AGL_SAMPLES_ARB,
'aux_buffers': agl.AGL_AUX_BUFFERS,
'red_size': agl.AGL_RED_SIZE,
'green_size': agl.AGL_GREEN_SIZE,
'blue_size': agl.AGL_BLUE_SIZE,
'alpha_size': agl.AGL_ALPHA_SIZE,
'depth_size': agl.AGL_DEPTH_SIZE,
'stencil_size': agl.AGL_STENCIL_SIZE,
'accum_red_size': agl.AGL_ACCUM_RED_SIZE,
'accum_green_size': agl.AGL_ACCUM_GREEN_SIZE,
'accum_blue_size': agl.AGL_ACCUM_BLUE_SIZE,
'accum_alpha_size': agl.AGL_ACCUM_ALPHA_SIZE,
# Not exposed by pyglet API (set internally)
'all_renderers': agl.AGL_ALL_RENDERERS,
'rgba': agl.AGL_RGBA,
'fullscreen': agl.AGL_FULLSCREEN,
'minimum_policy': agl.AGL_MINIMUM_POLICY,
'maximum_policy': agl.AGL_MAXIMUM_POLICY,
# Not supported in current pyglet API
'level': agl.AGL_LEVEL,
'pixel_size': agl.AGL_PIXEL_SIZE, # == buffer_size
'aux_depth_stencil': agl.AGL_AUX_DEPTH_STENCIL,
'color_float': agl.AGL_COLOR_FLOAT,
'offscreen': agl.AGL_OFFSCREEN,
'sample_alpha': agl.AGL_SAMPLE_ALPHA,
'multisample': agl.AGL_MULTISAMPLE,
'supersample': agl.AGL_SUPERSAMPLE,
}
# AGL constants which do not require a value.
_boolean_attributes = \
(agl.AGL_ALL_RENDERERS,
agl.AGL_RGBA,
agl.AGL_DOUBLEBUFFER,
agl.AGL_STEREO,
agl.AGL_MINIMUM_POLICY,
agl.AGL_MAXIMUM_POLICY,
agl.AGL_OFFSCREEN,
agl.AGL_FULLSCREEN,
agl.AGL_AUX_DEPTH_STENCIL,
agl.AGL_COLOR_FLOAT,
agl.AGL_MULTISAMPLE,
agl.AGL_SUPERSAMPLE,
agl.AGL_SAMPLE_ALPHA)
def __init__(self, screen, pformat):
super(CarbonGLConfig, self).__init__()
self.screen = screen
self._pformat = pformat
self._attributes = {}
for name, attr in self._attribute_ids.items():
value = c_int()
result = agl.aglDescribePixelFormat(pformat, attr, byref(value))
if result:
setattr(self, name, value.value)
def create_context(self, share):
if share:
context = agl.aglCreateContext(self._pformat, share._context)
else:
context = agl.aglCreateContext(self._pformat, None)
_aglcheck()
return CarbonGLContext(self, context, share, self._pformat)
class CarbonGLContext(gl.Context):
def __init__(self, config, context, share, pixelformat):
super(CarbonGLContext, self).__init__(share)
self.config = config
self._context = context
self._pixelformat = pixelformat
def destroy(self):
super(CarbonGLContext, self).destroy()
agl.aglDestroyContext(self._context)
class CarbonMouseCursor(MouseCursor):
drawable = False
def __init__(self, theme):
self.theme = theme
def CarbonEventHandler(event_class, event_kind):
return _PlatformEventHandler((event_class, event_kind))
class CarbonWindow(BaseWindow):
_window = None # Carbon WindowRef
_agl_context = None # AGL context ID
_recreate_deferred = None
# Window properties
_minimum_size = None
_maximum_size = None
_fullscreen_restore = None
_event_dispatcher = None
_current_modifiers = 0
_mapped_modifers = 0
_carbon_event_handlers = []
_carbon_event_handler_refs = []
_track_ref = 0
_track_region = None
_mouse_exclusive = False
_mouse_platform_visible = True
_mouse_ignore_motion = False
def _recreate(self, changes):
# We can't destroy the window while event handlers are active,
# otherwise the (OS X) event dispatcher gets lost and segfaults.
#
# Defer actual recreation until dispatch_events next finishes.
self._recreate_deferred = changes
def _recreate_immediate(self):
# The actual _recreate function.
changes = self._recreate_deferred
self._recreate_deferred = None
if ('context' in changes):
agl.aglSetDrawable(self._agl_context, None)
if ('fullscreen' in changes and
not self._fullscreen and
self._fullscreen_restore):
# Leaving fullscreen -- destroy everything before the window.
self._remove_track_region()
self._remove_event_handlers()
agl.aglSetDrawable(self._agl_context, None)
# EndFullScreen disposes _window.
quicktime.EndFullScreen(self._fullscreen_restore, 0)
self._window = None
self._create()
def _create(self):
self._agl_context = self.context._context
if self._window:
# The window is about to be recreated; destroy everything
# associated with the old window, then the window itself.
self._remove_track_region()
self._remove_event_handlers()
agl.aglSetDrawable(self._agl_context, None)
carbon.DisposeWindow(self._window)
self._window = None
self._window = WindowRef()
if self._fullscreen:
# Switch to fullscreen mode with QuickTime
fs_width = c_short(0)
fs_height = c_short(0)
self._fullscreen_restore = c_void_p()
quicktime.BeginFullScreen(byref(self._fullscreen_restore),
self.screen.get_gdevice(),
byref(fs_width),
byref(fs_height),
byref(self._window),
None,
0)
# the following may be used for debugging if you have a second
# monitor - only the main monitor will go fullscreen
agl.aglEnable(self._agl_context, agl.AGL_FS_CAPTURE_SINGLE)
self._width = fs_width.value
self._height = fs_height.value
#self._width = self.screen.width
#self._height = self.screen.height
agl.aglSetFullScreen(self._agl_context,
self._width, self._height,
self.screen._refresh_rate, 0)
self._mouse_in_window = True
self.dispatch_event('on_resize', self._width, self._height)
self.dispatch_event('on_show')
self.dispatch_event('on_expose')
else:
# Create floating window
rect = Rect()
location = None # TODO
if location is not None:
rect.left = location[0]
rect.top = location[1]
else:
rect.top = rect.left = 0
rect.right = rect.left + self._width
rect.bottom = rect.top + self._height
styles = {
self.WINDOW_STYLE_DEFAULT: (kDocumentWindowClass,
kWindowCloseBoxAttribute |
kWindowCollapseBoxAttribute),
self.WINDOW_STYLE_DIALOG: (kDocumentWindowClass,
kWindowCloseBoxAttribute),
self.WINDOW_STYLE_TOOL: (kUtilityWindowClass,
kWindowCloseBoxAttribute),
self.WINDOW_STYLE_BORDERLESS: (kSimpleWindowClass,
kWindowNoAttributes)
}
window_class, window_attributes = \
styles.get(self._style, kDocumentWindowClass)
if self._resizable:
window_attributes |= (kWindowFullZoomAttribute |
kWindowLiveResizeAttribute |
kWindowResizableAttribute)
r = carbon.CreateNewWindow(window_class,
window_attributes,
byref(rect),
byref(self._window))
_oscheck(r)
if location is None:
carbon.RepositionWindow(self._window, c_void_p(),
kWindowCascadeOnMainScreen)
agl.aglSetDrawable(self._agl_context,
carbon.GetWindowPort(self._window))
_aglcheck()
self.set_caption(self._caption)
# Get initial state
self._event_dispatcher = carbon.GetEventDispatcherTarget()
self._current_modifiers = carbon.GetCurrentKeyModifiers().value
self._mapped_modifiers = self._map_modifiers(self._current_modifiers)
# (re)install Carbon event handlers
self._install_event_handlers()
self._create_track_region()
self.switch_to()
self.set_vsync(self._vsync)
if self._visible:
self.set_visible(True)
def _create_track_region(self):
self._remove_track_region()
# Create a tracking region for the content part of the window
# to receive enter/leave events.
track_id = MouseTrackingRegionID()
track_id.signature = DEFAULT_CREATOR_CODE
track_id.id = 1
self._track_ref = MouseTrackingRef()
self._track_region = carbon.NewRgn()
carbon.GetWindowRegion(self._window,
kWindowContentRgn, self._track_region)
carbon.CreateMouseTrackingRegion(self._window,
self._track_region, None, kMouseTrackingOptionsGlobalClip,
track_id, None, None,
byref(self._track_ref))
def _remove_track_region(self):
if self._track_region:
carbon.ReleaseMouseTrackingRegion(self._track_region)
self._track_region = None
def close(self):
super(CarbonWindow, self).close()
if not self._agl_context:
return
self._agl_context = None
self._remove_event_handlers()
self._remove_track_region()
# Restore cursor visibility
self.set_mouse_platform_visible(True)
self.set_exclusive_mouse(False)
if self._fullscreen:
quicktime.EndFullScreen(self._fullscreen_restore, 0)
else:
carbon.DisposeWindow(self._window)
self._window = None
def switch_to(self):
agl.aglSetCurrentContext(self._agl_context)
self._context.set_current()
_aglcheck()
gl_info.set_active_context()
glu_info.set_active_context()
def flip(self):
self.draw_mouse_cursor()
agl.aglSwapBuffers(self._agl_context)
_aglcheck()
def _get_vsync(self):
swap = c_long()
agl.aglGetInteger(self._agl_context, agl.AGL_SWAP_INTERVAL, byref(swap))
return bool(swap.value)
vsync = property(_get_vsync) # overrides BaseWindow property
def set_vsync(self, vsync):
if pyglet.options['vsync'] is not None:
vsync = pyglet.options['vsync']
self._vsync = vsync # _recreate depends on this
swap = c_long(int(vsync))
agl.aglSetInteger(self._agl_context, agl.AGL_SWAP_INTERVAL, byref(swap))
def dispatch_events(self):
self._allow_dispatch_event = True
while self._event_queue:
EventDispatcher.dispatch_event(self, *self._event_queue.pop(0))
e = EventRef()
result = carbon.ReceiveNextEvent(0, c_void_p(), 0, True, byref(e))
while result == noErr:
carbon.SendEventToEventTarget(e, self._event_dispatcher)
carbon.ReleaseEvent(e)
if self._recreate_deferred:
self._recreate_immediate()
result = carbon.ReceiveNextEvent(0, c_void_p(), 0, True, byref(e))
self._allow_dispatch_event = False
# Return value from ReceiveNextEvent can be ignored if not
# noErr; we check here only to look for new bugs.
# eventLoopQuitErr: the inner event loop was quit, see
# http://lists.apple.com/archives/Carbon-dev/2006/Jun/msg00850.html
# Can occur when mixing with other toolkits, e.g. Tk.
# Fixes issue 180.
if result not in (eventLoopTimedOutErr, eventLoopQuitErr):
raise 'Error %d' % result
def dispatch_pending_events(self):
while self._event_queue:
EventDispatcher.dispatch_event(self, *self._event_queue.pop(0))
if self._recreate_deferred:
self._recreate_immediate()
def set_caption(self, caption):
self._caption = caption
s = _create_cfstring(caption)
carbon.SetWindowTitleWithCFString(self._window, s)
carbon.CFRelease(s)
def set_location(self, x, y):
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
rect.right += x - rect.left
rect.bottom += y - rect.top
rect.left = x
rect.top = y
carbon.SetWindowBounds(self._window, kWindowContentRgn, byref(rect))
def get_location(self):
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
return rect.left, rect.top
def set_size(self, width, height):
if self._fullscreen:
raise WindowException('Cannot set size of fullscreen window.')
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
rect.right = rect.left + width
rect.bottom = rect.top + height
carbon.SetWindowBounds(self._window, kWindowContentRgn, byref(rect))
self._width = width
self._height = height
self.dispatch_event('on_resize', width, height)
self.dispatch_event('on_expose')
def get_size(self):
if self._fullscreen:
return self._width, self._height
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
return rect.right - rect.left, rect.bottom - rect.top
def set_minimum_size(self, width, height):
self._minimum_size = (width, height)
minimum = HISize()
minimum.width = width
minimum.height = height
if self._maximum_size:
maximum = HISize()
maximum.width, maximum.height = self._maximum_size
maximum = byref(maximum)
else:
maximum = None
carbon.SetWindowResizeLimits(self._window,
byref(minimum), maximum)
def set_maximum_size(self, width, height):
self._maximum_size = (width, height)
maximum = HISize()
maximum.width = width
maximum.height = height
if self._minimum_size:
minimum = HISize()
minimum.width, minimum.height = self._minimum_size
minimum = byref(minimum)
else:
minimum = None
carbon.SetWindowResizeLimits(self._window,
minimum, byref(maximum))
def activate(self):
carbon.ActivateWindow(self._window, 1)
# Also make the application the "front" application. TODO
# maybe don't bring forward all of the application's windows?
psn = ProcessSerialNumber()
psn.highLongOfPSN = 0
psn.lowLongOfPSN = kCurrentProcess
carbon.SetFrontProcess(byref(psn))
def set_visible(self, visible=True):
self._visible = visible
if visible:
self.dispatch_event('on_resize', self._width, self._height)
self.dispatch_event('on_show')
carbon.ShowWindow(self._window)
else:
carbon.HideWindow(self._window)
def minimize(self):
self._mouse_in_window = False
self.set_mouse_platform_visible()
carbon.CollapseWindow(self._window, True)
def maximize(self):
# Maximum "safe" value, gets trimmed to screen size automatically.
p = Point()
p.v, p.h = 16000,16000
if not carbon.IsWindowInStandardState(self._window, byref(p), None):
carbon.ZoomWindowIdeal(self._window, inZoomOut, byref(p))
def set_mouse_platform_visible(self, platform_visible=None):
if platform_visible is None:
platform_visible = self._mouse_visible and \
not self._mouse_exclusive and \
not self._mouse_cursor.drawable
if not self._mouse_in_window:
platform_visible = True
if self._mouse_in_window and \
isinstance(self._mouse_cursor, CarbonMouseCursor):
carbon.SetThemeCursor(self._mouse_cursor.theme)
else:
carbon.SetThemeCursor(kThemeArrowCursor)
if self._mouse_platform_visible == platform_visible:
return
if platform_visible:
carbon.ShowCursor()
else:
carbon.HideCursor()
self._mouse_platform_visible = platform_visible
def set_exclusive_mouse(self, exclusive=True):
self._mouse_exclusive = exclusive
if exclusive:
# Move mouse to center of window
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
point = CGPoint()
point.x = (rect.right + rect.left) / 2
point.y = (rect.bottom + rect.top) / 2
# Skip the next motion event, which would return a large delta.
self._mouse_ignore_motion = True
carbon.CGWarpMouseCursorPosition(point)
carbon.CGAssociateMouseAndMouseCursorPosition(False)
else:
carbon.CGAssociateMouseAndMouseCursorPosition(True)
self.set_mouse_platform_visible()
def set_exclusive_keyboard(self, exclusive=True):
if exclusive:
# Note: power switch can also be disabled, with
# kUIOptionDisableSessionTerminate. That seems
# a little extreme though.
carbon.SetSystemUIMode(kUIModeAllHidden,
(kUIOptionDisableAppleMenu |
kUIOptionDisableProcessSwitch |
kUIOptionDisableForceQuit |
kUIOptionDisableHide))
else:
carbon.SetSystemUIMode(kUIModeNormal, 0)
def get_system_mouse_cursor(self, name):
if name == self.CURSOR_DEFAULT:
return DefaultMouseCursor()
themes = {
self.CURSOR_CROSSHAIR: kThemeCrossCursor,
self.CURSOR_HAND: kThemePointingHandCursor,
self.CURSOR_HELP: kThemeArrowCursor,
self.CURSOR_NO: kThemeNotAllowedCursor,
self.CURSOR_SIZE: kThemeArrowCursor,
self.CURSOR_SIZE_UP: kThemeResizeUpCursor,
self.CURSOR_SIZE_UP_RIGHT: kThemeArrowCursor,
self.CURSOR_SIZE_RIGHT: kThemeResizeRightCursor,
self.CURSOR_SIZE_DOWN_RIGHT: kThemeArrowCursor,
self.CURSOR_SIZE_DOWN: kThemeResizeDownCursor,
self.CURSOR_SIZE_DOWN_LEFT: kThemeArrowCursor,
self.CURSOR_SIZE_LEFT: kThemeResizeLeftCursor,
self.CURSOR_SIZE_UP_LEFT: kThemeArrowCursor,
self.CURSOR_SIZE_UP_DOWN: kThemeResizeUpDownCursor,
self.CURSOR_SIZE_LEFT_RIGHT: kThemeResizeLeftRightCursor,
self.CURSOR_TEXT: kThemeIBeamCursor,
self.CURSOR_WAIT: kThemeWatchCursor,
self.CURSOR_WAIT_ARROW: kThemeWatchCursor,
}
if name not in themes:
raise CarbonException('Unknown cursor name "%s"' % name)
return CarbonMouseCursor(themes[name])
def set_icon(self, *images):
# Only use the biggest image
image = images[0]
size = image.width * image.height
for img in images:
if img.width * img.height > size:
size = img.width * img.height
image = img
image = image.get_image_data()
format = 'ARGB'
pitch = -len(format) * image.width
data = image.get_data(format, pitch)
provider = carbon.CGDataProviderCreateWithData(
None, data, len(data), None)
colorspace = carbon.CGColorSpaceCreateDeviceRGB()
cgi = carbon.CGImageCreate(
image.width, image.height, 8, 32, -pitch,
colorspace,
kCGImageAlphaFirst,
provider,
None,
True,
kCGRenderingIntentDefault)
carbon.SetApplicationDockTileImage(cgi)
carbon.CGDataProviderRelease(provider)
carbon.CGColorSpaceRelease(colorspace)
# Non-public utilities
def _update_drawable(self):
# We can get there after context has been disposed, in which case
# just do nothing.
if not self._agl_context:
return
agl.aglUpdateContext(self._agl_context)
_aglcheck()
# Need a redraw
self.dispatch_event('on_expose')
def _update_track_region(self):
carbon.GetWindowRegion(self._window,
kWindowContentRgn, self._track_region)
carbon.ChangeMouseTrackingRegion(self._track_ref,
self._track_region, None)
def _install_event_handlers(self):
self._remove_event_handlers()
if self._fullscreen:
target = carbon.GetApplicationEventTarget()
else:
target = carbon.GetWindowEventTarget(self._window)
carbon.InstallStandardEventHandler(target)
self._carbon_event_handlers = []
self._carbon_event_handler_refs = []
for func_name in self._platform_event_names:
if not hasattr(self, func_name):
continue
func = getattr(self, func_name)
for event_class, event_kind in func._platform_event_data:
# TODO: could just build up array of class/kind
proc = EventHandlerProcPtr(func)
self._carbon_event_handlers.append(proc)
upp = carbon.NewEventHandlerUPP(proc)
types = EventTypeSpec()
types.eventClass = event_class
types.eventKind = event_kind
handler_ref = EventHandlerRef()
carbon.InstallEventHandler(
target,
upp,
1,
byref(types),
c_void_p(),
byref(handler_ref))
self._carbon_event_handler_refs.append(handler_ref)
def _remove_event_handlers(self):
for ref in self._carbon_event_handler_refs:
carbon.RemoveEventHandler(ref)
self._carbon_event_handler_refs = []
self._carbon_event_handlers = []
# Carbon event handlers
@CarbonEventHandler(kEventClassTextInput, kEventTextInputUnicodeForKeyEvent)
def _on_text_input(self, next_handler, ev, data):
size = c_uint32()
carbon.GetEventParameter(ev, kEventParamTextInputSendText,
typeUTF8Text, c_void_p(), 0, byref(size), c_void_p())
text = create_string_buffer(size.value)
carbon.GetEventParameter(ev, kEventParamTextInputSendText,
typeUTF8Text, c_void_p(), size.value, c_void_p(), byref(text))
text = text.value.decode('utf8')
raw_event = EventRef()
carbon.GetEventParameter(ev, kEventParamTextInputSendKeyboardEvent,
typeEventRef, c_void_p(), sizeof(raw_event), c_void_p(),
byref(raw_event))
symbol, modifiers = self._get_symbol_and_modifiers(raw_event)
motion_modifiers = modifiers & \
(key.MOD_COMMAND | key.MOD_CTRL | key.MOD_OPTION)
if (symbol, motion_modifiers) in _motion_map:
motion = _motion_map[symbol, motion_modifiers]
if modifiers & key.MOD_SHIFT:
self.dispatch_event('on_text_motion_select', motion)
else:
self.dispatch_event('on_text_motion', motion)
elif ((unicodedata.category(text[0]) != 'Cc' or text == u'\r') and
not (modifiers & key.MOD_COMMAND)):
self.dispatch_event('on_text', text)
return noErr
@CarbonEventHandler(kEventClassKeyboard, kEventRawKeyUp)
def _on_key_up(self, next_handler, ev, data):
symbol, modifiers = self._get_symbol_and_modifiers(ev)
if symbol:
self.dispatch_event('on_key_release', symbol, modifiers)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassKeyboard, kEventRawKeyDown)
def _on_key_down(self, next_handler, ev, data):
symbol, modifiers = self._get_symbol_and_modifiers(ev)
if symbol:
self.dispatch_event('on_key_press', symbol, modifiers)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@staticmethod
def _get_symbol_and_modifiers(ev):
sym = c_uint32()
carbon.GetEventParameter(ev, kEventParamKeyCode,
typeUInt32, c_void_p(), sizeof(sym), c_void_p(), byref(sym))
modifiers = c_uint32()
carbon.GetEventParameter(ev, kEventParamKeyModifiers,
typeUInt32, c_void_p(), sizeof(modifiers), c_void_p(),
byref(modifiers))
symbol = keymap.get(sym.value, None)
if symbol is None:
symbol = key.user_key(sym.value)
return (symbol, CarbonWindow._map_modifiers(modifiers.value))
@staticmethod
def _map_modifiers(modifiers):
mapped_modifiers = 0
if modifiers & (shiftKey | rightShiftKey):
mapped_modifiers |= key.MOD_SHIFT
if modifiers & (controlKey | rightControlKey):
mapped_modifiers |= key.MOD_CTRL
if modifiers & (optionKey | rightOptionKey):
mapped_modifiers |= key.MOD_OPTION
if modifiers & alphaLock:
mapped_modifiers |= key.MOD_CAPSLOCK
if modifiers & cmdKey:
mapped_modifiers |= key.MOD_COMMAND
return mapped_modifiers
@CarbonEventHandler(kEventClassKeyboard, kEventRawKeyModifiersChanged)
def _on_modifiers_changed(self, next_handler, ev, data):
modifiers = c_uint32()
carbon.GetEventParameter(ev, kEventParamKeyModifiers,
typeUInt32, c_void_p(), sizeof(modifiers), c_void_p(),
byref(modifiers))
modifiers = modifiers.value
deltas = modifiers ^ self._current_modifiers
for mask, k in [
(controlKey, key.LCTRL),
(shiftKey, key.LSHIFT),
(cmdKey, key.LCOMMAND),
(optionKey, key.LOPTION),
(rightShiftKey, key.RSHIFT),
(rightOptionKey, key.ROPTION),
(rightControlKey, key.RCTRL),
(alphaLock, key.CAPSLOCK),
(numLock, key.NUMLOCK)]:
if deltas & mask:
if modifiers & mask:
self.dispatch_event('on_key_press',
k, self._mapped_modifiers)
else:
self.dispatch_event('on_key_release',
k, self._mapped_modifiers)
carbon.CallNextEventHandler(next_handler, ev)
self._mapped_modifiers = self._map_modifiers(modifiers)
self._current_modifiers = modifiers
return noErr
def _get_mouse_position(self, ev):
position = HIPoint()
carbon.GetEventParameter(ev, kEventParamMouseLocation,
typeHIPoint, c_void_p(), sizeof(position), c_void_p(),
byref(position))
bounds = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(bounds))
return int(position.x - bounds.left), int(position.y - bounds.top)
@staticmethod
def _get_mouse_button_and_modifiers(ev):
button = EventMouseButton()
carbon.GetEventParameter(ev, kEventParamMouseButton,
typeMouseButton, c_void_p(), sizeof(button), c_void_p(),
byref(button))
if button.value == 1:
button = mouse.LEFT
elif button.value == 2:
button = mouse.RIGHT
elif button.value == 3:
button = mouse.MIDDLE
else:
button = None
modifiers = c_uint32()
carbon.GetEventParameter(ev, kEventParamKeyModifiers,
typeUInt32, c_void_p(), sizeof(modifiers), c_void_p(),
byref(modifiers))
return button, CarbonWindow._map_modifiers(modifiers.value)
@staticmethod
def _get_mouse_in_content(ev):
position = Point()
carbon.GetEventParameter(ev, kEventParamMouseLocation,
typeQDPoint, c_void_p(), sizeof(position), c_void_p(),
byref(position))
return carbon.FindWindow(position, None) == inContent
@CarbonEventHandler(kEventClassMouse, kEventMouseDown)
def _on_mouse_down(self, next_handler, ev, data):
if self._fullscreen or self._get_mouse_in_content(ev):
button, modifiers = self._get_mouse_button_and_modifiers(ev)
if button is not None:
x, y = self._get_mouse_position(ev)
y = self.height - y
self.dispatch_event('on_mouse_press', x, y, button, modifiers)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseUp)
def _on_mouse_up(self, next_handler, ev, data):
# Always report mouse up, even out of content area, because it's
# probably after a drag gesture.
button, modifiers = self._get_mouse_button_and_modifiers(ev)
if button is not None:
x, y = self._get_mouse_position(ev)
y = self.height - y
self.dispatch_event('on_mouse_release', x, y, button, modifiers)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseMoved)
def _on_mouse_moved(self, next_handler, ev, data):
if ((self._fullscreen or self._get_mouse_in_content(ev))
and not self._mouse_ignore_motion):
x, y = self._get_mouse_position(ev)
y = self.height - y
self._mouse_x = x
self._mouse_y = y
delta = HIPoint()
carbon.GetEventParameter(ev, kEventParamMouseDelta,
typeHIPoint, c_void_p(), sizeof(delta), c_void_p(),
byref(delta))
# Motion event
self.dispatch_event('on_mouse_motion',
x, y, delta.x, -delta.y)
elif self._mouse_ignore_motion:
self._mouse_ignore_motion = False
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseDragged)
def _on_mouse_dragged(self, next_handler, ev, data):
button, modifiers = self._get_mouse_button_and_modifiers(ev)
if button is not None:
x, y = self._get_mouse_position(ev)
y = self.height - y
self._mouse_x = x
self._mouse_y = y
delta = HIPoint()
carbon.GetEventParameter(ev, kEventParamMouseDelta,
typeHIPoint, c_void_p(), sizeof(delta), c_void_p(),
byref(delta))
# Drag event
self.dispatch_event('on_mouse_drag',
x, y, delta.x, -delta.y, button, modifiers)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseEntered)
def _on_mouse_entered(self, next_handler, ev, data):
x, y = self._get_mouse_position(ev)
y = self.height - y
self._mouse_x = x
self._mouse_y = y
self._mouse_in_window = True
self.set_mouse_platform_visible()
self.dispatch_event('on_mouse_enter', x, y)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseExited)
def _on_mouse_exited(self, next_handler, ev, data):
if not self._fullscreen:
x, y = self._get_mouse_position(ev)
y = self.height - y
self._mouse_in_window = False
self.set_mouse_platform_visible()
self.dispatch_event('on_mouse_leave', x, y)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassMouse, kEventMouseWheelMoved)
def _on_mouse_wheel_moved(self, next_handler, ev, data):
x, y = self._get_mouse_position(ev)
y = self.height - y
axis = EventMouseWheelAxis()
carbon.GetEventParameter(ev, kEventParamMouseWheelAxis,
typeMouseWheelAxis, c_void_p(), sizeof(axis), c_void_p(),
byref(axis))
delta = c_long()
carbon.GetEventParameter(ev, kEventParamMouseWheelDelta,
typeSInt32, c_void_p(), sizeof(delta), c_void_p(),
byref(delta))
if axis.value == kEventMouseWheelAxisX:
self.dispatch_event('on_mouse_scroll',
x, y, delta.value, 0)
else:
self.dispatch_event('on_mouse_scroll',
x, y, 0, delta.value)
# _Don't_ call the next handler, which is application, as this then
# calls our window handler again.
#carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowClose)
def _on_window_close(self, next_handler, ev, data):
self.dispatch_event('on_close')
# Presumably the next event handler is the one that closes
# the window; don't do that here.
#carbon.CallNextEventHandler(next_handler, ev)
return noErr
_resizing = None
@CarbonEventHandler(kEventClassWindow, kEventWindowResizeStarted)
def _on_window_resize_started(self, next_handler, ev, data):
self._resizing = (self.width, self.height)
from pyglet import app
if app.event_loop is not None:
app.event_loop._stop_polling()
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowResizeCompleted)
def _on_window_resize_completed(self, next_handler, ev, data):
self._resizing = None
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
width = rect.right - rect.left
height = rect.bottom - rect.top
self.switch_to()
self.dispatch_event('on_resize', width, height)
self.dispatch_event('on_expose')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
_dragging = False
@CarbonEventHandler(kEventClassWindow, kEventWindowDragStarted)
def _on_window_drag_started(self, next_handler, ev, data):
self._dragging = True
from pyglet import app
if app.event_loop is not None:
app.event_loop._stop_polling()
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowDragCompleted)
def _on_window_drag_completed(self, next_handler, ev, data):
self._dragging = False
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
self.dispatch_event('on_move', rect.left, rect.top)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowBoundsChanging)
def _on_window_bounds_changing(self, next_handler, ev, data):
from pyglet import app
if app.event_loop is not None:
carbon.SetEventLoopTimerNextFireTime(app.event_loop._timer,
c_double(0.0))
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowBoundsChanged)
def _on_window_bounds_change(self, next_handler, ev, data):
self._update_track_region()
self._update_drawable()
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowZoomed)
def _on_window_zoomed(self, next_handler, ev, data):
rect = Rect()
carbon.GetWindowBounds(self._window, kWindowContentRgn, byref(rect))
width = rect.right - rect.left
height = rect.bottom - rect.top
self.dispatch_event('on_move', rect.left, rect.top)
self.dispatch_event('on_resize', width, height)
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowActivated)
def _on_window_activated(self, next_handler, ev, data):
self.dispatch_event('on_activate')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowDeactivated)
def _on_window_deactivated(self, next_handler, ev, data):
self.dispatch_event('on_deactivate')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowShown)
@CarbonEventHandler(kEventClassWindow, kEventWindowExpanded)
def _on_window_shown(self, next_handler, ev, data):
self._update_drawable()
self.dispatch_event('on_show')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowHidden)
@CarbonEventHandler(kEventClassWindow, kEventWindowCollapsed)
def _on_window_hidden(self, next_handler, ev, data):
self.dispatch_event('on_hide')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
@CarbonEventHandler(kEventClassWindow, kEventWindowDrawContent)
def _on_window_draw_content(self, next_handler, ev, data):
self.dispatch_event('on_expose')
carbon.CallNextEventHandler(next_handler, ev)
return noErr
def _create_cfstring(text):
return carbon.CFStringCreateWithCString(c_void_p(),
text.encode('utf8'),
kCFStringEncodingUTF8)
def _oscheck(result):
if result != noErr:
raise 'Carbon error %d' % result
return result
def _aglcheck():
err = agl.aglGetError()
if err != agl.AGL_NO_ERROR:
raise CarbonException(cast(agl.aglErrorString(err), c_char_p).value)