Spaces:
Runtime error
Runtime error
import unicodedata | |
import urllib.parse | |
from ctypes import * | |
from functools import lru_cache | |
import pyglet | |
from pyglet.window import WindowException, MouseCursorException | |
from pyglet.window import MouseCursor, DefaultMouseCursor, ImageMouseCursor | |
from pyglet.window import BaseWindow, _PlatformEventHandler, _ViewEventHandler | |
from pyglet.window import key | |
from pyglet.window import mouse | |
from pyglet.event import EventDispatcher | |
from pyglet.canvas.xlib import XlibCanvas | |
from pyglet.libs.x11 import xlib | |
from pyglet.libs.x11 import cursorfont | |
from pyglet.util import asbytes | |
try: | |
from pyglet.libs.x11 import xsync | |
_have_xsync = True | |
except ImportError: | |
_have_xsync = False | |
class mwmhints_t(Structure): | |
_fields_ = [ | |
('flags', c_uint32), | |
('functions', c_uint32), | |
('decorations', c_uint32), | |
('input_mode', c_int32), | |
('status', c_uint32) | |
] | |
# XXX: wraptypes can't parse the header this function is in yet | |
XkbSetDetectableAutoRepeat = xlib._lib.XkbSetDetectableAutoRepeat | |
XkbSetDetectableAutoRepeat.restype = c_int | |
XkbSetDetectableAutoRepeat.argtypes = [POINTER(xlib.Display), c_int, POINTER(c_int)] | |
_can_detect_autorepeat = None | |
XA_CARDINAL = 6 # Xatom.h:14 | |
XA_ATOM = 4 | |
XDND_VERSION = 5 | |
# Do we have the November 2000 UTF8 extension? | |
_have_utf8 = hasattr(xlib._lib, 'Xutf8TextListToTextProperty') | |
# symbol,ctrl -> motion mapping | |
_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.RIGHT, True): key.MOTION_NEXT_WORD, | |
(key.LEFT, True): key.MOTION_PREVIOUS_WORD, | |
(key.HOME, False): key.MOTION_BEGINNING_OF_LINE, | |
(key.END, False): key.MOTION_END_OF_LINE, | |
(key.PAGEUP, False): key.MOTION_PREVIOUS_PAGE, | |
(key.PAGEDOWN, False): key.MOTION_NEXT_PAGE, | |
(key.HOME, True): key.MOTION_BEGINNING_OF_FILE, | |
(key.END, True): key.MOTION_END_OF_FILE, | |
(key.BACKSPACE, False): key.MOTION_BACKSPACE, | |
(key.DELETE, False): key.MOTION_DELETE, | |
(key.C, True): key.MOTION_COPY, | |
(key.V, True): key.MOTION_PASTE | |
} | |
class XlibException(WindowException): | |
"""An X11-specific exception. This exception is probably a programming | |
error in pyglet.""" | |
pass | |
class XlibMouseCursor(MouseCursor): | |
gl_drawable = False | |
hw_drawable = True | |
def __init__(self, cursor): | |
self.cursor = cursor | |
# Platform event data is single item, so use platform event handler directly. | |
XlibEventHandler = _PlatformEventHandler | |
ViewEventHandler = _ViewEventHandler | |
class XlibWindow(BaseWindow): | |
_x_display = None # X display connection | |
_x_screen_id = None # X screen index | |
_x_ic = None # X input context | |
_window = None # Xlib window handle | |
_override_redirect = False | |
_x = 0 | |
_y = 0 # Last known window position | |
_mouse_exclusive_client = None # x,y of "real" mouse during exclusive | |
_mouse_buttons = [False] * 6 # State of each xlib button | |
_active = True | |
_applied_mouse_exclusive = False | |
_applied_keyboard_exclusive = False | |
_mapped = False | |
_lost_context = False | |
_lost_context_state = False | |
_enable_xsync = False | |
_current_sync_value = None | |
_current_sync_valid = False | |
_default_event_mask = (0x1ffffff & ~xlib.PointerMotionHintMask | |
& ~xlib.ResizeRedirectMask | |
& ~xlib.SubstructureNotifyMask) | |
def __init__(self, *args, **kwargs): | |
# Bind event handlers | |
self._event_handlers = {} | |
self._view_event_handlers = {} | |
for name in self._platform_event_names: | |
if not hasattr(self, name): | |
continue | |
func = getattr(self, name) | |
for message in func._platform_event_data: | |
if hasattr(func, '_view'): | |
self._view_event_handlers[message] = func | |
else: | |
self._event_handlers[message] = func | |
super(XlibWindow, self).__init__(*args, **kwargs) | |
global _can_detect_autorepeat | |
if _can_detect_autorepeat is None: | |
supported_rtrn = c_int() | |
_can_detect_autorepeat = XkbSetDetectableAutoRepeat(self.display._display, c_int(1), | |
byref(supported_rtrn)) | |
if _can_detect_autorepeat: | |
self.pressed_keys = set() | |
def _recreate(self, changes): | |
# If flipping to/from fullscreen, need to recreate the window. (This | |
# is the case with both override_redirect method and | |
# _NET_WM_STATE_FULLSCREEN). | |
# | |
# A possible improvement could be to just hide the top window, | |
# destroy the GLX window, and reshow it again when leaving fullscreen. | |
# This would prevent the floating window from being moved by the | |
# WM. | |
if 'fullscreen' in changes or 'resizable' in changes: | |
# clear out the GLX context | |
self.context.detach() | |
xlib.XDestroyWindow(self._x_display, self._window) | |
del self.display._window_map[self._window] | |
del self.display._window_map[self._view] | |
self._window = None | |
self._mapped = False | |
# TODO: detect state loss only by examining context share. | |
if 'context' in changes: | |
self._lost_context = True | |
self._lost_context_state = True | |
self._create() | |
def _create_xdnd_atoms(self, display): | |
self._xdnd_atoms = { | |
'XdndAware' : xlib.XInternAtom(display, asbytes('XdndAware'), False), | |
'XdndEnter' : xlib.XInternAtom(display, asbytes('XdndEnter'), False), | |
'XdndTypeList' : xlib.XInternAtom(display, asbytes('XdndTypeList'), False), | |
'XdndDrop' : xlib.XInternAtom(display, asbytes('XdndDrop'), False), | |
'XdndFinished' : xlib.XInternAtom(display, asbytes('XdndFinished'), False), | |
'XdndSelection' : xlib.XInternAtom(display, asbytes('XdndSelection'), False), | |
'XdndPosition' : xlib.XInternAtom(display, asbytes('XdndPosition'), False), | |
'XdndStatus' : xlib.XInternAtom(display, asbytes('XdndStatus'), False), | |
'XdndActionCopy' : xlib.XInternAtom(display, asbytes('XdndActionCopy'), False), | |
'text/uri-list' : xlib.XInternAtom(display, asbytes("text/uri-list"), False) | |
} | |
def _create(self): | |
# Unmap existing window if necessary while we fiddle with it. | |
if self._window and self._mapped: | |
self._unmap() | |
self._x_display = self.display._display | |
self._x_screen_id = self.display.x_screen | |
# Create X window if not already existing. | |
if not self._window: | |
root = xlib.XRootWindow(self._x_display, self._x_screen_id) | |
visual_info = self.config.get_visual_info() | |
if self.style in ('transparent', 'overlay'): | |
xlib.XMatchVisualInfo(self._x_display, self._x_screen_id, 32, xlib.TrueColor, visual_info) | |
visual = visual_info.visual | |
visual_id = xlib.XVisualIDFromVisual(visual) | |
default_visual = xlib.XDefaultVisual(self._x_display, self._x_screen_id) | |
default_visual_id = xlib.XVisualIDFromVisual(default_visual) | |
window_attributes = xlib.XSetWindowAttributes() | |
if visual_id != default_visual_id: | |
window_attributes.colormap = xlib.XCreateColormap(self._x_display, root, | |
visual, xlib.AllocNone) | |
else: | |
window_attributes.colormap = xlib.XDefaultColormap(self._x_display, | |
self._x_screen_id) | |
window_attributes.bit_gravity = xlib.StaticGravity | |
# Issue 287: Compiz on Intel/Mesa doesn't draw window decoration | |
# unless CWBackPixel is given in mask. Should have | |
# no effect on other systems, so it's set | |
# unconditionally. | |
mask = xlib.CWColormap | xlib.CWBitGravity | xlib.CWBackPixel | |
if self.style in ('transparent', 'overlay'): | |
mask |= xlib.CWBorderPixel | |
window_attributes.border_pixel = 0 | |
window_attributes.background_pixel = 0 | |
if self._fullscreen: | |
width, height = self.screen.width, self.screen.height | |
self._view_x = (width - self._width) // 2 | |
self._view_y = (height - self._height) // 2 | |
else: | |
width, height = self._width, self._height | |
self._view_x = self._view_y = 0 | |
self._window = xlib.XCreateWindow(self._x_display, root, | |
0, 0, width, height, 0, visual_info.depth, | |
xlib.InputOutput, visual, mask, | |
byref(window_attributes)) | |
self._view = xlib.XCreateWindow(self._x_display, | |
self._window, self._view_x, self._view_y, | |
self._width, self._height, 0, visual_info.depth, | |
xlib.InputOutput, visual, mask, | |
byref(window_attributes)) | |
xlib.XMapWindow(self._x_display, self._view) | |
xlib.XSelectInput(self._x_display, self._view, self._default_event_mask) | |
self.display._window_map[self._window] = self.dispatch_platform_event | |
self.display._window_map[self._view] = self.dispatch_platform_event_view | |
self.canvas = XlibCanvas(self.display, self._view) | |
self.context.attach(self.canvas) | |
self.context.set_vsync(self._vsync) # XXX ? | |
# Setting null background pixmap disables drawing the background, | |
# preventing flicker while resizing (in theory). | |
# | |
# Issue 287: Compiz on Intel/Mesa doesn't draw window decoration if | |
# this is called. As it doesn't seem to have any | |
# effect anyway, it's just commented out. | |
# xlib.XSetWindowBackgroundPixmap(self._x_display, self._window, 0) | |
self._enable_xsync = (pyglet.options['xsync'] and | |
self.display._enable_xsync and | |
self.config.double_buffer) | |
# Set supported protocols | |
protocols = [] | |
protocols.append(xlib.XInternAtom(self._x_display, asbytes('WM_DELETE_WINDOW'), False)) | |
if self._enable_xsync: | |
protocols.append(xlib.XInternAtom(self._x_display, | |
asbytes('_NET_WM_SYNC_REQUEST'), | |
False)) | |
protocols = (c_ulong * len(protocols))(*protocols) | |
xlib.XSetWMProtocols(self._x_display, self._window, protocols, len(protocols)) | |
# Create window resize sync counter | |
if self._enable_xsync: | |
value = xsync.XSyncValue() | |
self._sync_counter = xlib.XID(xsync.XSyncCreateCounter(self._x_display, value)) | |
atom = xlib.XInternAtom(self._x_display, | |
asbytes('_NET_WM_SYNC_REQUEST_COUNTER'), False) | |
ptr = pointer(self._sync_counter) | |
xlib.XChangeProperty(self._x_display, self._window, | |
atom, XA_CARDINAL, 32, | |
xlib.PropModeReplace, | |
cast(ptr, POINTER(c_ubyte)), 1) | |
# Atoms required for Xdnd | |
self._create_xdnd_atoms(self._x_display) | |
# Support for drag and dropping files needs to be enabled. | |
if self._file_drops: | |
# Some variables set because there are 4 different drop events that need shared data. | |
self._xdnd_source = None | |
self._xdnd_version = None | |
self._xdnd_format = None | |
self._xdnd_position = (0, 0) # For position callback. | |
VERSION = c_ulong(int(XDND_VERSION)) | |
ptr = pointer(VERSION) | |
xlib.XChangeProperty(self._x_display, self._window, | |
self._xdnd_atoms['XdndAware'], XA_ATOM, 32, | |
xlib.PropModeReplace, | |
cast(ptr, POINTER(c_ubyte)), 1) | |
# Set window attributes | |
attributes = xlib.XSetWindowAttributes() | |
attributes_mask = 0 | |
self._override_redirect = False | |
if self._fullscreen: | |
if pyglet.options['xlib_fullscreen_override_redirect']: | |
# Try not to use this any more, it causes problems; disabled | |
# by default in favour of _NET_WM_STATE_FULLSCREEN. | |
attributes.override_redirect = self._fullscreen | |
attributes_mask |= xlib.CWOverrideRedirect | |
self._override_redirect = True | |
else: | |
self._set_wm_state('_NET_WM_STATE_FULLSCREEN') | |
if self._fullscreen: | |
xlib.XMoveResizeWindow(self._x_display, self._window, | |
self.screen.x, self.screen.y, | |
self.screen.width, self.screen.height) | |
else: | |
xlib.XResizeWindow(self._x_display, self._window, self._width, self._height) | |
xlib.XChangeWindowAttributes(self._x_display, self._window, | |
attributes_mask, byref(attributes)) | |
# Set style | |
styles = { | |
self.WINDOW_STYLE_DEFAULT: '_NET_WM_WINDOW_TYPE_NORMAL', | |
self.WINDOW_STYLE_DIALOG: '_NET_WM_WINDOW_TYPE_DIALOG', | |
self.WINDOW_STYLE_TOOL: '_NET_WM_WINDOW_TYPE_UTILITY', | |
} | |
if self._style in styles: | |
self._set_atoms_property('_NET_WM_WINDOW_TYPE', (styles[self._style],)) | |
elif self._style in (self.WINDOW_STYLE_BORDERLESS, self.WINDOW_STYLE_OVERLAY): | |
MWM_HINTS_DECORATIONS = 1 << 1 | |
PROP_MWM_HINTS_ELEMENTS = 5 | |
mwmhints = mwmhints_t() | |
mwmhints.flags = MWM_HINTS_DECORATIONS | |
mwmhints.decorations = 0 | |
name = xlib.XInternAtom(self._x_display, asbytes('_MOTIF_WM_HINTS'), False) | |
xlib.XChangeProperty(self._x_display, self._window, | |
name, name, 32, xlib.PropModeReplace, | |
cast(pointer(mwmhints), POINTER(c_ubyte)), | |
PROP_MWM_HINTS_ELEMENTS) | |
# Set resizeable | |
if not self._resizable and not self._fullscreen: | |
self.set_minimum_size(self._width, self._height) | |
self.set_maximum_size(self._width, self._height) | |
# Set caption | |
self.set_caption(self._caption) | |
# Set WM_CLASS for modern desktop environments | |
self.set_wm_class(self._caption) | |
# this is supported by some compositors (ie gnome-shell), and more to come | |
# see: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idp6357888 | |
_NET_WM_BYPASS_COMPOSITOR_HINT_ON = c_ulong(int(self._fullscreen)) | |
name = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_BYPASS_COMPOSITOR'), False) | |
ptr = pointer(_NET_WM_BYPASS_COMPOSITOR_HINT_ON) | |
xlib.XChangeProperty(self._x_display, self._window, | |
name, XA_CARDINAL, 32, | |
xlib.PropModeReplace, | |
cast(ptr, POINTER(c_ubyte)), 1) | |
# Create input context. A good but very outdated reference for this | |
# is http://www.sbin.org/doc/Xlib/chapt_11.html | |
if _have_utf8 and not self._x_ic: | |
if not self.display._x_im: | |
xlib.XSetLocaleModifiers(asbytes('@im=none')) | |
self.display._x_im = xlib.XOpenIM(self._x_display, None, None, None) | |
xlib.XFlush(self._x_display) | |
# Need to set argtypes on this function because its vararg, | |
# and ctypes guesses wrong. | |
xlib.XCreateIC.argtypes = [xlib.XIM, | |
c_char_p, c_int, | |
c_char_p, xlib.Window, | |
c_char_p, xlib.Window, | |
c_void_p] | |
self._x_ic = xlib.XCreateIC(self.display._x_im, | |
asbytes('inputStyle'), | |
xlib.XIMPreeditNothing | xlib.XIMStatusNothing, | |
asbytes('clientWindow'), self._window, | |
asbytes('focusWindow'), self._window, | |
None) | |
filter_events = c_ulong() | |
xlib.XGetICValues(self._x_ic, 'filterEvents', byref(filter_events), None) | |
self._default_event_mask |= filter_events.value | |
xlib.XSetICFocus(self._x_ic) | |
self.switch_to() | |
if self._visible: | |
self.set_visible(True) | |
self.set_mouse_platform_visible() | |
self._applied_mouse_exclusive = None | |
self._update_exclusivity() | |
def _map(self): | |
if self._mapped: | |
return | |
# Map the window, wait for map event before continuing. | |
xlib.XSelectInput(self._x_display, self._window, xlib.StructureNotifyMask) | |
xlib.XMapRaised(self._x_display, self._window) | |
e = xlib.XEvent() | |
while True: | |
xlib.XNextEvent(self._x_display, e) | |
if e.type == xlib.ConfigureNotify: | |
self._width = e.xconfigure.width | |
self._height = e.xconfigure.height | |
elif e.type == xlib.MapNotify: | |
break | |
xlib.XSelectInput(self._x_display, self._window, self._default_event_mask) | |
self._mapped = True | |
if self._override_redirect: | |
# Possibly an override_redirect issue. | |
self.activate() | |
self._update_view_size() | |
self.dispatch_event('on_resize', self._width, self._height) | |
self.dispatch_event('on_show') | |
self.dispatch_event('on_expose') | |
def _unmap(self): | |
if not self._mapped: | |
return | |
xlib.XSelectInput(self._x_display, self._window, xlib.StructureNotifyMask) | |
xlib.XUnmapWindow(self._x_display, self._window) | |
e = xlib.XEvent() | |
while True: | |
xlib.XNextEvent(self._x_display, e) | |
if e.type == xlib.UnmapNotify: | |
break | |
xlib.XSelectInput(self._x_display, self._window, self._default_event_mask) | |
self._mapped = False | |
def _get_root(self): | |
attributes = xlib.XWindowAttributes() | |
xlib.XGetWindowAttributes(self._x_display, self._window, byref(attributes)) | |
return attributes.root | |
def _is_reparented(self): | |
root = c_ulong() | |
parent = c_ulong() | |
children = pointer(c_ulong()) | |
n_children = c_uint() | |
xlib.XQueryTree(self._x_display, self._window, | |
byref(root), byref(parent), byref(children), | |
byref(n_children)) | |
return root.value != parent.value | |
def close(self): | |
if not self._window: | |
return | |
self.context.destroy() | |
self._unmap() | |
if self._window: | |
xlib.XDestroyWindow(self._x_display, self._window) | |
del self.display._window_map[self._window] | |
del self.display._window_map[self._view] | |
self._window = None | |
self._view_event_handlers.clear() | |
self._event_handlers.clear() | |
if _have_utf8: | |
xlib.XDestroyIC(self._x_ic) | |
self._x_ic = None | |
super(XlibWindow, self).close() | |
def switch_to(self): | |
if self.context: | |
self.context.set_current() | |
def flip(self): | |
self.draw_mouse_cursor() | |
# TODO canvas.flip? | |
if self.context: | |
self.context.flip() | |
self._sync_resize() | |
def set_vsync(self, vsync: bool) -> None: | |
if pyglet.options['vsync'] is not None: | |
vsync = pyglet.options['vsync'] | |
super().set_vsync(vsync) | |
self.context.set_vsync(vsync) | |
def set_caption(self, caption): | |
if caption is None: | |
caption = '' | |
self._caption = caption | |
self._set_text_property('WM_NAME', caption, allow_utf8=False) | |
self._set_text_property('WM_ICON_NAME', caption, allow_utf8=False) | |
self._set_text_property('_NET_WM_NAME', caption) | |
self._set_text_property('_NET_WM_ICON_NAME', caption) | |
def set_wm_class(self, name): | |
# WM_CLASS can only contain Ascii characters | |
try: | |
name = name.encode('ascii') | |
except UnicodeEncodeError: | |
name = "pyglet" | |
hint = xlib.XAllocClassHint() | |
hint.contents.res_class = asbytes(name) | |
hint.contents.res_name = asbytes(name.lower()) | |
xlib.XSetClassHint(self._x_display, self._window, hint.contents) | |
xlib.XFree(hint) | |
def get_caption(self): | |
return self._caption | |
def set_size(self, width: int, height: int) -> None: | |
super().set_size(width, height) | |
if not self._resizable: | |
self.set_minimum_size(width, height) | |
self.set_maximum_size(width, height) | |
xlib.XResizeWindow(self._x_display, self._window, width, height) | |
self._update_view_size() | |
self.dispatch_event('on_resize', width, height) | |
def _update_view_size(self): | |
xlib.XResizeWindow(self._x_display, self._view, self._width, self._height) | |
def set_location(self, x, y): | |
if self._is_reparented(): | |
# Assume the window manager has reparented our top-level window | |
# only once, in which case attributes.x/y give the offset from | |
# the frame to the content window. Better solution would be | |
# to use _NET_FRAME_EXTENTS, where supported. | |
attributes = xlib.XWindowAttributes() | |
xlib.XGetWindowAttributes(self._x_display, self._window, byref(attributes)) | |
# XXX at least under KDE's WM these attrs are both 0 | |
x -= attributes.x | |
y -= attributes.y | |
xlib.XMoveWindow(self._x_display, self._window, x, y) | |
def get_location(self): | |
child = xlib.Window() | |
x = c_int() | |
y = c_int() | |
xlib.XTranslateCoordinates(self._x_display, | |
self._window, | |
self._get_root(), | |
0, 0, | |
byref(x), | |
byref(y), | |
byref(child)) | |
return x.value, y.value | |
def activate(self): | |
# Issue 218 | |
if self._x_display and self._window: | |
xlib.XSetInputFocus(self._x_display, self._window, xlib.RevertToParent, xlib.CurrentTime) | |
def set_visible(self, visible: bool = True) -> None: | |
super().set_visible(visible) | |
if visible: | |
self._map() | |
else: | |
self._unmap() | |
def set_minimum_size(self, width: int, height: int) -> None: | |
super().set_minimum_size(width, height) | |
self._set_wm_normal_hints() | |
def set_maximum_size(self, width: int, height: int) -> None: | |
super().set_maximum_size(width, height) | |
self._set_wm_normal_hints() | |
def minimize(self): | |
xlib.XIconifyWindow(self._x_display, self._window, self._x_screen_id) | |
def maximize(self): | |
self._set_wm_state('_NET_WM_STATE_MAXIMIZED_HORZ', | |
'_NET_WM_STATE_MAXIMIZED_VERT') | |
def _downsample_1bit(pixelarray): | |
byte_list = [] | |
value = 0 | |
for i, pixel in enumerate(pixelarray): | |
index = i % 8 | |
if pixel: | |
value |= 1 << index | |
if index == 7: | |
byte_list.append(value) | |
value = 0 | |
return bytes(byte_list) | |
def _create_cursor_from_image(self, cursor): | |
"""Creates platform cursor from an ImageCursor instance.""" | |
texture = cursor.texture | |
width = texture.width | |
height = texture.height | |
alpha_luma_bytes = texture.get_image_data().get_data('AL', -width * 2) | |
mask_data = self._downsample_1bit(alpha_luma_bytes[0::2]) | |
bmp_data = self._downsample_1bit(alpha_luma_bytes[1::2]) | |
bitmap = xlib.XCreateBitmapFromData(self._x_display, self._window, bmp_data, width, height) | |
mask = xlib.XCreateBitmapFromData(self._x_display, self._window, mask_data, width, height) | |
white = xlib.XColor(red=65535, green=65535, blue=65535) # background color | |
black = xlib.XColor() # foreground color | |
# hot_x/y must be within the image dimension, or the cursor will not display: | |
hot_x = min(max(0, int(self._mouse_cursor.hot_x)), width) | |
hot_y = min(max(0, int(height - self._mouse_cursor.hot_y)), height) | |
cursor = xlib.XCreatePixmapCursor(self._x_display, bitmap, mask, white, black, hot_x, hot_y) | |
xlib.XFreePixmap(self._x_display, bitmap) | |
xlib.XFreePixmap(self._x_display, mask) | |
return cursor | |
def set_mouse_platform_visible(self, platform_visible=None): | |
if not self._window: | |
return | |
if platform_visible is None: | |
platform_visible = self._mouse_visible and not self._mouse_cursor.gl_drawable | |
if platform_visible is False: | |
# Hide pointer by creating an empty cursor: | |
black = xlib.XColor() | |
bitmap = xlib.XCreateBitmapFromData(self._x_display, self._window, bytes(8), 8, 8) | |
cursor = xlib.XCreatePixmapCursor(self._x_display, bitmap, bitmap, black, black, 0, 0) | |
xlib.XDefineCursor(self._x_display, self._window, cursor) | |
xlib.XFreeCursor(self._x_display, cursor) | |
xlib.XFreePixmap(self._x_display, bitmap) | |
elif isinstance(self._mouse_cursor, ImageMouseCursor) and self._mouse_cursor.hw_drawable: | |
# Create a custom hardware cursor: | |
cursor = self._create_cursor_from_image(self._mouse_cursor) | |
xlib.XDefineCursor(self._x_display, self._window, cursor) | |
else: | |
# Restore standard hardware cursor: | |
if isinstance(self._mouse_cursor, XlibMouseCursor): | |
xlib.XDefineCursor(self._x_display, self._window, self._mouse_cursor.cursor) | |
else: | |
xlib.XUndefineCursor(self._x_display, self._window) | |
def set_mouse_position(self, x, y): | |
xlib.XWarpPointer(self._x_display, | |
0, # src window | |
self._window, # dst window | |
0, 0, # src x, y | |
0, 0, # src w, h | |
x, self._height - y) | |
def _update_exclusivity(self): | |
mouse_exclusive = self._active and self._mouse_exclusive | |
keyboard_exclusive = self._active and self._keyboard_exclusive | |
if mouse_exclusive != self._applied_mouse_exclusive: | |
if mouse_exclusive: | |
self.set_mouse_platform_visible(False) | |
# Restrict to client area | |
xlib.XGrabPointer(self._x_display, self._window, | |
True, | |
0, | |
xlib.GrabModeAsync, | |
xlib.GrabModeAsync, | |
self._window, | |
0, | |
xlib.CurrentTime) | |
# Move pointer to center of window | |
x = self._width // 2 | |
y = self._height // 2 | |
self._mouse_exclusive_client = x, y | |
self.set_mouse_position(x, y) | |
elif self._fullscreen and not self.screen._xinerama: | |
# Restrict to fullscreen area (prevent viewport scrolling) | |
self.set_mouse_position(0, 0) | |
r = xlib.XGrabPointer(self._x_display, self._view, | |
True, 0, | |
xlib.GrabModeAsync, | |
xlib.GrabModeAsync, | |
self._view, | |
0, | |
xlib.CurrentTime) | |
if r: | |
# Failed to grab, try again later | |
self._applied_mouse_exclusive = None | |
return | |
self.set_mouse_platform_visible() | |
else: | |
# Unclip | |
xlib.XUngrabPointer(self._x_display, xlib.CurrentTime) | |
self.set_mouse_platform_visible() | |
self._applied_mouse_exclusive = mouse_exclusive | |
if keyboard_exclusive != self._applied_keyboard_exclusive: | |
if keyboard_exclusive: | |
xlib.XGrabKeyboard(self._x_display, | |
self._window, | |
False, | |
xlib.GrabModeAsync, | |
xlib.GrabModeAsync, | |
xlib.CurrentTime) | |
else: | |
xlib.XUngrabKeyboard(self._x_display, xlib.CurrentTime) | |
self._applied_keyboard_exclusive = keyboard_exclusive | |
def set_exclusive_mouse(self, exclusive=True): | |
if exclusive == self._mouse_exclusive: | |
return | |
super().set_exclusive_mouse(exclusive) | |
self._update_exclusivity() | |
def set_exclusive_keyboard(self, exclusive=True): | |
if exclusive == self._keyboard_exclusive: | |
return | |
super().set_exclusive_keyboard(exclusive) | |
self._update_exclusivity() | |
def get_system_mouse_cursor(self, name): | |
if name == self.CURSOR_DEFAULT: | |
return DefaultMouseCursor() | |
# NQR means default shape is not pretty... surely there is another | |
# cursor font? | |
cursor_shapes = { | |
self.CURSOR_CROSSHAIR: cursorfont.XC_crosshair, | |
self.CURSOR_HAND: cursorfont.XC_hand2, | |
self.CURSOR_HELP: cursorfont.XC_question_arrow, # NQR | |
self.CURSOR_NO: cursorfont.XC_pirate, # NQR | |
self.CURSOR_SIZE: cursorfont.XC_fleur, | |
self.CURSOR_SIZE_UP: cursorfont.XC_top_side, | |
self.CURSOR_SIZE_UP_RIGHT: cursorfont.XC_top_right_corner, | |
self.CURSOR_SIZE_RIGHT: cursorfont.XC_right_side, | |
self.CURSOR_SIZE_DOWN_RIGHT: cursorfont.XC_bottom_right_corner, | |
self.CURSOR_SIZE_DOWN: cursorfont.XC_bottom_side, | |
self.CURSOR_SIZE_DOWN_LEFT: cursorfont.XC_bottom_left_corner, | |
self.CURSOR_SIZE_LEFT: cursorfont.XC_left_side, | |
self.CURSOR_SIZE_UP_LEFT: cursorfont.XC_top_left_corner, | |
self.CURSOR_SIZE_UP_DOWN: cursorfont.XC_sb_v_double_arrow, | |
self.CURSOR_SIZE_LEFT_RIGHT: cursorfont.XC_sb_h_double_arrow, | |
self.CURSOR_TEXT: cursorfont.XC_xterm, | |
self.CURSOR_WAIT: cursorfont.XC_watch, | |
self.CURSOR_WAIT_ARROW: cursorfont.XC_watch, # NQR | |
} | |
if name not in cursor_shapes: | |
raise MouseCursorException('Unknown cursor name "%s"' % name) | |
cursor = xlib.XCreateFontCursor(self._x_display, cursor_shapes[name]) | |
return XlibMouseCursor(cursor) | |
def set_icon(self, *images): | |
# Careful! XChangeProperty takes an array of long when data type | |
# is 32-bit (but long can be 64 bit!), so pad high bytes of format if | |
# necessary. | |
import sys | |
fmt = {('little', 4): 'BGRA', | |
('little', 8): 'BGRAAAAA', | |
('big', 4): 'ARGB', | |
('big', 8): 'AAAAARGB'}[(sys.byteorder, sizeof(c_ulong))] | |
data = asbytes('') | |
for image in images: | |
image = image.get_image_data() | |
pitch = -(image.width * len(fmt)) | |
s = c_buffer(sizeof(c_ulong) * 2) | |
memmove(s, cast((c_ulong * 2)(image.width, image.height), POINTER(c_ubyte)), len(s)) | |
data += s.raw + image.get_data(fmt, pitch) | |
buffer = (c_ubyte * len(data))() | |
memmove(buffer, data, len(data)) | |
atom = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_ICON'), False) | |
xlib.XChangeProperty(self._x_display, self._window, atom, XA_CARDINAL, | |
32, xlib.PropModeReplace, buffer, len(data)//sizeof(c_ulong)) | |
# Private utility | |
def _set_wm_normal_hints(self): | |
hints = xlib.XAllocSizeHints().contents | |
if self._minimum_size: | |
hints.flags |= xlib.PMinSize | |
hints.min_width, hints.min_height = self._minimum_size | |
if self._maximum_size: | |
hints.flags |= xlib.PMaxSize | |
hints.max_width, hints.max_height = self._maximum_size | |
xlib.XSetWMNormalHints(self._x_display, self._window, byref(hints)) | |
def _set_text_property(self, name, value, allow_utf8=True): | |
atom = xlib.XInternAtom(self._x_display, asbytes(name), False) | |
if not atom: | |
raise XlibException('Undefined atom "%s"' % name) | |
text_property = xlib.XTextProperty() | |
if _have_utf8 and allow_utf8: | |
buf = create_string_buffer(value.encode('utf8')) | |
result = xlib.Xutf8TextListToTextProperty(self._x_display, | |
cast(pointer(buf), c_char_p), | |
1, xlib.XUTF8StringStyle, | |
byref(text_property)) | |
if result < 0: | |
raise XlibException('Could not create UTF8 text property') | |
else: | |
buf = create_string_buffer(value.encode('ascii', 'ignore')) | |
result = xlib.XStringListToTextProperty( | |
cast(pointer(buf), c_char_p), 1, byref(text_property)) | |
if result < 0: | |
raise XlibException('Could not create text property') | |
xlib.XSetTextProperty(self._x_display, self._window, byref(text_property), atom) | |
# XXX <rj> Xlib doesn't like us freeing this | |
# xlib.XFree(text_property.value) | |
def _set_atoms_property(self, name, values, mode=xlib.PropModeReplace): | |
name_atom = xlib.XInternAtom(self._x_display, asbytes(name), False) | |
atoms = [] | |
for value in values: | |
atoms.append(xlib.XInternAtom(self._x_display, asbytes(value), False)) | |
atom_type = xlib.XInternAtom(self._x_display, asbytes('ATOM'), False) | |
if len(atoms): | |
atoms_ar = (xlib.Atom * len(atoms))(*atoms) | |
xlib.XChangeProperty(self._x_display, self._window, | |
name_atom, atom_type, 32, mode, | |
cast(pointer(atoms_ar), POINTER(c_ubyte)), len(atoms)) | |
else: | |
net_wm_state = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_STATE'), False) | |
if net_wm_state: | |
xlib.XDeleteProperty(self._x_display, self._window, net_wm_state) | |
def _set_wm_state(self, *states): | |
# Set property | |
net_wm_state = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_STATE'), False) | |
atoms = [] | |
for state in states: | |
atoms.append(xlib.XInternAtom(self._x_display, asbytes(state), False)) | |
atom_type = xlib.XInternAtom(self._x_display, asbytes('ATOM'), False) | |
if len(atoms): | |
atoms_ar = (xlib.Atom * len(atoms))(*atoms) | |
xlib.XChangeProperty(self._x_display, self._window, | |
net_wm_state, atom_type, 32, xlib.PropModePrepend, | |
cast(pointer(atoms_ar), POINTER(c_ubyte)), len(atoms)) | |
else: | |
xlib.XDeleteProperty(self._x_display, self._window, net_wm_state) | |
# Nudge the WM | |
e = xlib.XEvent() | |
e.xclient.type = xlib.ClientMessage | |
e.xclient.message_type = net_wm_state | |
e.xclient.display = cast(self._x_display, POINTER(xlib.Display)) | |
e.xclient.window = self._window | |
e.xclient.format = 32 | |
e.xclient.data.l[0] = xlib.PropModePrepend | |
for i, atom in enumerate(atoms): | |
e.xclient.data.l[i + 1] = atom | |
xlib.XSendEvent(self._x_display, self._get_root(), | |
False, xlib.SubstructureRedirectMask, byref(e)) | |
# Event handling | |
def dispatch_events(self): | |
self.dispatch_pending_events() | |
self._allow_dispatch_event = True | |
e = xlib.XEvent() | |
# Cache these in case window is closed from an event handler | |
_x_display = self._x_display | |
_window = self._window | |
_view = self._view | |
# Check for the events specific to this window | |
while xlib.XCheckWindowEvent(_x_display, _window, 0x1ffffff, byref(e)): | |
# Key events are filtered by the xlib window event | |
# handler so they get a shot at the prefiltered event. | |
if e.xany.type not in (xlib.KeyPress, xlib.KeyRelease): | |
if xlib.XFilterEvent(e, 0): | |
continue | |
self.dispatch_platform_event(e) | |
# Check for the events specific to this view | |
while xlib.XCheckWindowEvent(_x_display, _view, 0x1ffffff, byref(e)): | |
# Key events are filtered by the xlib window event | |
# handler so they get a shot at the prefiltered event. | |
if e.xany.type not in (xlib.KeyPress, xlib.KeyRelease): | |
if xlib.XFilterEvent(e, 0): | |
continue | |
self.dispatch_platform_event_view(e) | |
# Generic events for this window (the window close event). | |
while xlib.XCheckTypedWindowEvent(_x_display, _window, xlib.ClientMessage, byref(e)): | |
self.dispatch_platform_event(e) | |
self._allow_dispatch_event = False | |
def dispatch_pending_events(self): | |
while self._event_queue: | |
EventDispatcher.dispatch_event(self, *self._event_queue.pop(0)) | |
# Dispatch any context-related events | |
if self._lost_context: | |
self._lost_context = False | |
EventDispatcher.dispatch_event(self, 'on_context_lost') | |
if self._lost_context_state: | |
self._lost_context_state = False | |
EventDispatcher.dispatch_event(self, 'on_context_state_lost') | |
def dispatch_platform_event(self, e): | |
if self._applied_mouse_exclusive is None: | |
self._update_exclusivity() | |
event_handler = self._event_handlers.get(e.type) | |
if event_handler: | |
event_handler(e) | |
def dispatch_platform_event_view(self, e): | |
event_handler = self._view_event_handlers.get(e.type) | |
if event_handler: | |
event_handler(e) | |
def _translate_modifiers(state): | |
modifiers = 0 | |
if state & xlib.ShiftMask: | |
modifiers |= key.MOD_SHIFT | |
if state & xlib.ControlMask: | |
modifiers |= key.MOD_CTRL | |
if state & xlib.LockMask: | |
modifiers |= key.MOD_CAPSLOCK | |
if state & xlib.Mod1Mask: | |
modifiers |= key.MOD_ALT | |
if state & xlib.Mod2Mask: | |
modifiers |= key.MOD_NUMLOCK | |
if state & xlib.Mod4Mask: | |
modifiers |= key.MOD_WINDOWS | |
if state & xlib.Mod5Mask: | |
modifiers |= key.MOD_SCROLLLOCK | |
return modifiers | |
# Event handlers | |
""" | |
def _event_symbol(self, event): | |
# pyglet.self.key keysymbols are identical to X11 keysymbols, no | |
# need to map the keysymbol. | |
symbol = xlib.XKeycodeToKeysym(self._x_display, event.xkey.keycode, 0) | |
if symbol == 0: | |
# XIM event | |
return None | |
elif symbol not in key._key_names.keys(): | |
symbol = key.user_key(event.xkey.keycode) | |
return symbol | |
""" | |
def _event_text_symbol(self, ev): | |
text = None | |
symbol = xlib.KeySym() | |
buffer = create_string_buffer(128) | |
# Look up raw keysym before XIM filters it (default for keypress and | |
# keyrelease) | |
count = xlib.XLookupString(ev.xkey, buffer, len(buffer) - 1, byref(symbol), None) | |
# Give XIM a shot | |
filtered = xlib.XFilterEvent(ev, ev.xany.window) | |
if ev.type == xlib.KeyPress and not filtered: | |
status = c_int() | |
if _have_utf8: | |
encoding = 'utf8' | |
count = xlib.Xutf8LookupString(self._x_ic, | |
ev.xkey, | |
buffer, len(buffer) - 1, | |
byref(symbol), byref(status)) | |
if status.value == xlib.XBufferOverflow: | |
raise NotImplementedError('TODO: XIM buffer resize') | |
else: | |
encoding = 'ascii' | |
count = xlib.XLookupString(ev.xkey, buffer, len(buffer) - 1, byref(symbol), None) | |
if count: | |
status.value = xlib.XLookupBoth | |
if status.value & (xlib.XLookupChars | xlib.XLookupBoth): | |
text = buffer.value[:count].decode(encoding) | |
# Don't treat Unicode command codepoints as text, except Return. | |
if text and unicodedata.category(text) == 'Cc' and text != '\r': | |
text = None | |
symbol = symbol.value | |
# If the event is a XIM filtered event, the keysym will be virtual | |
# (e.g., aacute instead of A after a dead key). Drop it, we don't | |
# want these kind of key events. | |
if ev.xkey.keycode == 0 and not filtered: | |
symbol = None | |
# pyglet.self.key keysymbols are identical to X11 keysymbols, no | |
# need to map the keysymbol. For keysyms outside the pyglet set, map | |
# raw key code to a user key. | |
if symbol and symbol not in key._key_names and ev.xkey.keycode: | |
# Issue 353: Symbol is uppercase when shift key held down. | |
try: | |
symbol = ord(chr(symbol).lower()) | |
except ValueError: | |
# Not a valid unichr, use the keycode | |
symbol = key.user_key(ev.xkey.keycode) | |
else: | |
# If still not recognised, use the keycode | |
if symbol not in key._key_names: | |
symbol = key.user_key(ev.xkey.keycode) | |
if filtered: | |
# The event was filtered, text must be ignored, but the symbol is | |
# still good. | |
return None, symbol | |
return text, symbol | |
def _event_text_motion(symbol, modifiers): | |
if modifiers & key.MOD_ALT: | |
return None | |
ctrl = modifiers & key.MOD_CTRL != 0 | |
return _motion_map.get((symbol, ctrl), None) | |
def _event_key_view(self, ev): | |
# Try to detect autorepeat ourselves if the server doesn't support it | |
# XXX: Doesn't always work, better off letting the server do it | |
global _can_detect_autorepeat | |
if not _can_detect_autorepeat and ev.type == xlib.KeyRelease: | |
# Look in the queue for a matching KeyPress with same timestamp, | |
# indicating an auto-repeat rather than actual key event. | |
saved = [] | |
while True: | |
auto_event = xlib.XEvent() | |
result = xlib.XCheckWindowEvent(self._x_display, | |
self._window, xlib.KeyPress|xlib.KeyRelease, | |
byref(auto_event)) | |
if not result: | |
break | |
saved.append(auto_event) | |
if auto_event.type == xlib.KeyRelease: | |
# just save this off for restoration back to the queue | |
continue | |
if ev.xkey.keycode == auto_event.xkey.keycode: | |
# Found a key repeat: dispatch EVENT_TEXT* event | |
text, symbol = self._event_text_symbol(auto_event) | |
modifiers = self._translate_modifiers(ev.xkey.state) | |
modifiers_ctrl = modifiers & (key.MOD_CTRL | key.MOD_ALT) | |
motion = self._event_text_motion(symbol, modifiers) | |
if motion: | |
if modifiers & key.MOD_SHIFT: | |
self.dispatch_event('on_text_motion_select', motion) | |
else: | |
self.dispatch_event('on_text_motion', motion) | |
elif text and not modifiers_ctrl: | |
self.dispatch_event('on_text', text) | |
ditched = saved.pop() | |
for auto_event in reversed(saved): | |
xlib.XPutBackEvent(self._x_display, byref(auto_event)) | |
return | |
else: | |
# Key code of press did not match, therefore no repeating | |
# is going on, stop searching. | |
break | |
# Whoops, put the events back, it's for real. | |
for auto_event in reversed(saved): | |
xlib.XPutBackEvent(self._x_display, byref(auto_event)) | |
text, symbol = self._event_text_symbol(ev) | |
modifiers = self._translate_modifiers(ev.xkey.state) | |
modifiers_ctrl = modifiers & (key.MOD_CTRL | key.MOD_ALT) | |
motion = self._event_text_motion(symbol, modifiers) | |
if ev.type == xlib.KeyPress: | |
if symbol and (not _can_detect_autorepeat or symbol not in self.pressed_keys): | |
self.dispatch_event('on_key_press', symbol, modifiers) | |
if _can_detect_autorepeat: | |
self.pressed_keys.add(symbol) | |
if motion: | |
if modifiers & key.MOD_SHIFT: | |
self.dispatch_event('on_text_motion_select', motion) | |
else: | |
self.dispatch_event('on_text_motion', motion) | |
elif text and not modifiers_ctrl: | |
self.dispatch_event('on_text', text) | |
elif ev.type == xlib.KeyRelease: | |
if symbol: | |
self.dispatch_event('on_key_release', symbol, modifiers) | |
if _can_detect_autorepeat and symbol in self.pressed_keys: | |
self.pressed_keys.remove(symbol) | |
def _event_key(self, ev): | |
return self._event_key_view(ev) | |
def _event_motionnotify_view(self, ev): | |
x = ev.xmotion.x | |
y = self.height - ev.xmotion.y - 1 | |
if self._mouse_in_window: | |
dx = x - self._mouse_x | |
dy = y - self._mouse_y | |
else: | |
dx = dy = 0 | |
if self._applied_mouse_exclusive and (ev.xmotion.x, ev.xmotion.y) == self._mouse_exclusive_client: | |
# Ignore events caused by XWarpPointer | |
self._mouse_x = x | |
self._mouse_y = y | |
return | |
if self._applied_mouse_exclusive: | |
# Reset pointer position | |
ex, ey = self._mouse_exclusive_client | |
xlib.XWarpPointer(self._x_display, | |
0, | |
self._window, | |
0, 0, | |
0, 0, | |
ex, ey) | |
self._mouse_x = x | |
self._mouse_y = y | |
self._mouse_in_window = True | |
buttons = 0 | |
if ev.xmotion.state & xlib.Button1MotionMask: | |
buttons |= mouse.LEFT | |
if ev.xmotion.state & xlib.Button2MotionMask: | |
buttons |= mouse.MIDDLE | |
if ev.xmotion.state & xlib.Button3MotionMask: | |
buttons |= mouse.RIGHT | |
# TODO: Determine how to implement drag support for mouse 4 and 5 | |
if buttons: | |
# Drag event | |
modifiers = self._translate_modifiers(ev.xmotion.state) | |
self.dispatch_event('on_mouse_drag', x, y, dx, dy, buttons, modifiers) | |
else: | |
# Motion event | |
self.dispatch_event('on_mouse_motion', x, y, dx, dy) | |
def _event_motionnotify(self, ev): | |
# Window motion looks for drags that are outside the view but within | |
# the window. | |
buttons = 0 | |
if ev.xmotion.state & xlib.Button1MotionMask: | |
buttons |= mouse.LEFT | |
if ev.xmotion.state & xlib.Button2MotionMask: | |
buttons |= mouse.MIDDLE | |
if ev.xmotion.state & xlib.Button3MotionMask: | |
buttons |= mouse.RIGHT | |
# TODO: Determine how to implement drag support for mouse 4 and 5 | |
if buttons: | |
# Drag event | |
x = ev.xmotion.x - self._view_x | |
y = self._height - (ev.xmotion.y - self._view_y - 1) | |
if self._mouse_in_window: | |
dx = x - self._mouse_x | |
dy = y - self._mouse_y | |
else: | |
dx = dy = 0 | |
self._mouse_x = x | |
self._mouse_y = y | |
modifiers = self._translate_modifiers(ev.xmotion.state) | |
self.dispatch_event('on_mouse_drag', x, y, dx, dy, buttons, modifiers) | |
def _event_clientmessage(self, ev): | |
atom = ev.xclient.data.l[0] | |
if atom == xlib.XInternAtom(ev.xclient.display, asbytes('WM_DELETE_WINDOW'), False): | |
self.dispatch_event('on_close') | |
elif (self._enable_xsync and | |
atom == xlib.XInternAtom(ev.xclient.display, | |
asbytes('_NET_WM_SYNC_REQUEST'), False)): | |
lo = ev.xclient.data.l[2] | |
hi = ev.xclient.data.l[3] | |
self._current_sync_value = xsync.XSyncValue(hi, lo) | |
elif ev.xclient.message_type == self._xdnd_atoms['XdndPosition']: | |
self._event_drag_position(ev) | |
elif ev.xclient.message_type == self._xdnd_atoms['XdndDrop']: | |
self._event_drag_drop(ev) | |
elif ev.xclient.message_type == self._xdnd_atoms['XdndEnter']: | |
self._event_drag_enter(ev) | |
def _event_drag_drop(self, ev): | |
if self._xdnd_version > XDND_VERSION: | |
return | |
time = xlib.CurrentTime | |
if self._xdnd_format: | |
if self._xdnd_version >= 1: | |
time = ev.xclient.data.l[2] | |
# Convert to selection notification. | |
xlib.XConvertSelection(self._x_display, | |
self._xdnd_atoms['XdndSelection'], | |
self._xdnd_format, | |
self._xdnd_atoms['XdndSelection'], | |
self._window, | |
time) | |
xlib.XFlush(self._x_display) | |
elif self._xdnd_version >= 2: | |
# If no format send finished with no data. | |
e = xlib.XEvent() | |
e.xclient.type = xlib.ClientMessage | |
e.xclient.message_type = self._xdnd_atoms['XdndFinished'] | |
e.xclient.display = cast(self._x_display, POINTER(xlib.Display)) | |
e.xclient.window = self._window | |
e.xclient.format = 32 | |
e.xclient.data.l[0] = self._window | |
e.xclient.data.l[1] = 0 | |
e.xclient.data.l[2] = None | |
xlib.XSendEvent(self._x_display, self._xdnd_source, | |
False, xlib.NoEventMask, byref(e)) | |
xlib.XFlush(self._x_display) | |
def _event_drag_position(self, ev): | |
if self._xdnd_version > XDND_VERSION: | |
return | |
xoff = (ev.xclient.data.l[2] >> 16) & 0xffff | |
yoff = (ev.xclient.data.l[2]) & 0xffff | |
# Need to convert the position to actual window coordinates with the screen offset | |
child = xlib.Window() | |
x = c_int() | |
y = c_int() | |
xlib.XTranslateCoordinates(self._x_display, | |
self._get_root(), | |
self._window, | |
xoff, yoff, | |
byref(x), | |
byref(y), | |
byref(child)) | |
self._xdnd_position = (x.value, y.value) | |
e = xlib.XEvent() | |
e.xclient.type = xlib.ClientMessage | |
e.xclient.message_type = self._xdnd_atoms['XdndStatus'] | |
e.xclient.display = cast(self._x_display, POINTER(xlib.Display)) | |
e.xclient.window = ev.xclient.data.l[0] | |
e.xclient.format = 32 | |
e.xclient.data.l[0] = self._window | |
e.xclient.data.l[2] = 0 | |
e.xclient.data.l[3] = 0 | |
if self._xdnd_format: | |
e.xclient.data.l[1] = 1 | |
if self._xdnd_version >= 2: | |
e.xclient.data.l[4] = self._xdnd_atoms['XdndActionCopy'] | |
xlib.XSendEvent(self._x_display, self._xdnd_source, | |
False, xlib.NoEventMask, byref(e)) | |
xlib.XFlush(self._x_display) | |
def _event_drag_enter(self, ev): | |
self._xdnd_source = ev.xclient.data.l[0] | |
self._xdnd_version = ev.xclient.data.l[1] >> 24 | |
self._xdnd_format = None | |
if self._xdnd_version > XDND_VERSION: | |
return | |
three_or_more = ev.xclient.data.l[1] & 1 | |
# Search all of them (usually 8) | |
if three_or_more: | |
data, count = self.get_single_property(self._xdnd_source, self._xdnd_atoms['XdndTypeList'], XA_ATOM) | |
data = cast(data, POINTER(xlib.Atom)) | |
else: | |
# Some old versions may only have 3? Needs testing. | |
count = 3 | |
data = ev.xclient.data.l + 2 | |
# Check all of the properties we received from the dropped item and verify it support URI. | |
for i in range(count): | |
if data[i] == self._xdnd_atoms['text/uri-list']: | |
self._xdnd_format = self._xdnd_atoms['text/uri-list'] | |
break | |
if data: | |
xlib.XFree(data) | |
def get_single_property(self, window, atom_property, atom_type): | |
""" Returns the length and data of a window property. """ | |
actualAtom = xlib.Atom() | |
actualFormat = c_int() | |
itemCount = c_ulong() | |
bytesAfter = c_ulong() | |
data = POINTER(c_ubyte)() | |
xlib.XGetWindowProperty(self._x_display, window, | |
atom_property, 0, 2147483647, False, atom_type, | |
byref(actualAtom), | |
byref(actualFormat), | |
byref(itemCount), | |
byref(bytesAfter), | |
data) | |
return data, itemCount.value | |
def _event_selection_notification(self, ev): | |
if ev.xselection.property != 0 and ev.xselection.selection == self._xdnd_atoms['XdndSelection']: | |
if self._xdnd_format: | |
# This will get the data | |
data, count = self.get_single_property(ev.xselection.requestor, | |
ev.xselection.property, | |
ev.xselection.target) | |
buffer = create_string_buffer(count) | |
memmove(buffer, data, count) | |
formatted_paths = self.parse_filenames(buffer.value.decode()) | |
e = xlib.XEvent() | |
e.xclient.type = xlib.ClientMessage | |
e.xclient.message_type = self._xdnd_atoms['XdndFinished'] | |
e.xclient.display = cast(self._x_display, POINTER(xlib.Display)) | |
e.xclient.window = self._window | |
e.xclient.format = 32 | |
e.xclient.data.l[0] = self._xdnd_source | |
e.xclient.data.l[1] = 1 | |
e.xclient.data.l[2] = self._xdnd_atoms['XdndActionCopy'] | |
xlib.XSendEvent(self._x_display, self._get_root(), | |
False, xlib.NoEventMask, byref(e)) | |
xlib.XFlush(self._x_display) | |
xlib.XFree(data) | |
self.dispatch_event('on_file_drop', self._xdnd_position[0], self._height - self._xdnd_position[1], formatted_paths) | |
def parse_filenames(decoded_string): | |
"""All of the filenames from file drops come as one big string with | |
some special characters (%20), this will parse them out. | |
""" | |
import sys | |
different_files = decoded_string.splitlines() | |
parsed = [] | |
for filename in different_files: | |
if filename: | |
filename = urllib.parse.urlsplit(filename).path | |
encoding = sys.getfilesystemencoding() | |
parsed.append(urllib.parse.unquote(filename, encoding)) | |
return parsed | |
def _sync_resize(self): | |
if self._enable_xsync and self._current_sync_valid: | |
if xsync.XSyncValueIsZero(self._current_sync_value): | |
self._current_sync_valid = False | |
return | |
xsync.XSyncSetCounter(self._x_display, | |
self._sync_counter, | |
self._current_sync_value) | |
self._current_sync_value = None | |
self._current_sync_valid = False | |
def _event_button(self, ev): | |
x = ev.xbutton.x | |
y = self.height - ev.xbutton.y | |
button = ev.xbutton.button - 1 | |
if button == 7 or button == 8: | |
button -= 4 | |
modifiers = self._translate_modifiers(ev.xbutton.state) | |
if ev.type == xlib.ButtonPress: | |
# override_redirect issue: manually activate this window if | |
# fullscreen. | |
if self._override_redirect and not self._active: | |
self.activate() | |
if ev.xbutton.button == 4: | |
self.dispatch_event('on_mouse_scroll', x, y, 0, 1) | |
elif ev.xbutton.button == 5: | |
self.dispatch_event('on_mouse_scroll', x, y, 0, -1) | |
elif ev.xbutton.button == 6: | |
self.dispatch_event('on_mouse_scroll', x, y, -1, 0) | |
elif ev.xbutton.button == 7: | |
self.dispatch_event('on_mouse_scroll', x, y, 1, 0) | |
elif button < 5: | |
self.dispatch_event('on_mouse_press', x, y, 1 << button, modifiers) | |
elif button < 5: | |
self.dispatch_event('on_mouse_release', x, y, 1 << button, modifiers) | |
def _event_expose(self, ev): | |
# Ignore all expose events except the last one. We could be told | |
# about exposure rects - but I don't see the point since we're | |
# working with OpenGL and we'll just redraw the whole scene. | |
if ev.xexpose.count > 0: | |
return | |
self.dispatch_event('on_expose') | |
def _event_enternotify(self, ev): | |
# mouse position | |
x = self._mouse_x = ev.xcrossing.x | |
y = self._mouse_y = self.height - ev.xcrossing.y | |
self._mouse_in_window = True | |
# XXX there may be more we could do here | |
self.dispatch_event('on_mouse_enter', x, y) | |
def _event_leavenotify(self, ev): | |
x = self._mouse_x = ev.xcrossing.x | |
y = self._mouse_y = self.height - ev.xcrossing.y | |
self._mouse_in_window = False | |
self.dispatch_event('on_mouse_leave', x, y) | |
def _event_configurenotify(self, ev): | |
if self._enable_xsync and self._current_sync_value: | |
self._current_sync_valid = True | |
if self._fullscreen: | |
return | |
self.switch_to() | |
w, h = ev.xconfigure.width, ev.xconfigure.height | |
x, y = ev.xconfigure.x, ev.xconfigure.y | |
if self._width != w or self._height != h: | |
self._width = w | |
self._height = h | |
self._update_view_size() | |
self.dispatch_event('on_resize', self._width, self._height) | |
if self._x != x or self._y != y: | |
self.dispatch_event('on_move', x, y) | |
self._x = x | |
self._y = y | |
def _event_focusin(self, ev): | |
self._active = True | |
self._update_exclusivity() | |
self.dispatch_event('on_activate') | |
xlib.XSetICFocus(self._x_ic) | |
def _event_focusout(self, ev): | |
self._active = False | |
self._update_exclusivity() | |
self.dispatch_event('on_deactivate') | |
xlib.XUnsetICFocus(self._x_ic) | |
def _event_mapnotify(self, ev): | |
self._mapped = True | |
self.dispatch_event('on_show') | |
self._update_exclusivity() | |
def _event_unmapnotify(self, ev): | |
self._mapped = False | |
self.dispatch_event('on_hide') | |
__all__ = ["XlibEventHandler", "XlibWindow"] | |