Difficult-Rocket/libs/pyglet/window/xlib/__init__.py
shenjack d84b490b99
with more logger
Add | more formatter and some more

Fix | type mis match

sync pyglet

Enhance | logger with Template

add lib-not-dr as requirement

sync pyglet

sync pyglet

Add | add lto=yes to nuitka_build

just incase

sync pyglet

sync lib_not_dr

Remove | external requirement lib-not-dr

some logger

sync lib-not-dr

sync pyglet

sync lib-not-dr

sync lib-not-dr

sync pyglet

sync pyglet

Fix | console thread been block

Update DR rs and DR sdk

sync lib not dr

sync lib-not-dr

sync lib-not-dr

sync pyglet and lib-not-dr

sync pyglet 0.1.8

sync lib not dr

logger almost done?

almost!

sync pyglet (clicpboard support!)

sync lib not dr

sync lib not dr

color code and sync pyglet

do not show memory and progress building localy

sync pyglet

synclibs
2023-11-20 20:12:59 +08:00

1654 lines
66 KiB
Python

import locale
import unicodedata
import urllib.parse
from ctypes import *
from functools import lru_cache
from typing import Optional
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
_debug = pyglet.options['debug_x11']
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
XA_STRING = 31
XDND_VERSION = 5
_have_utf8 = locale.getlocale()[1] == 'UTF-8'
# 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()
# Store clipboard string to not query as much for pasting a lot.
self._clipboard_str: Optional[str] = None
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)
# Clipboard related atoms
self._clipboard_atom = xlib.XInternAtom(self._x_display, asbytes('CLIPBOARD'), False)
self._utf8_atom = xlib.XInternAtom(self._x_display, asbytes('UTF8_STRING'), False)
self._target_atom = xlib.XInternAtom(self._x_display, asbytes('TARGETS'), False)
self._incr_atom = xlib.XInternAtom(self._x_display, asbytes('INCR'), False)
# 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')
@staticmethod
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)
@lru_cache()
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))
def set_clipboard_text(self, text: str):
xlib.XSetSelectionOwner(self._x_display,
self._clipboard_atom,
self._window,
xlib.CurrentTime)
if xlib.XGetSelectionOwner(self._x_display, self._clipboard_atom) == self._window:
self._clipboard_str = text
str_bytes = text.encode('utf-8')
size = len(str_bytes)
xlib.XChangeProperty(self._x_display, self._window,
self._clipboard_atom, self._utf8_atom, 8, xlib.PropModeReplace,
(c_ubyte * size).from_buffer_copy(str_bytes), size)
else:
if _debug:
print("X11: Couldn't become owner of clipboard.")
def get_clipboard_text(self) -> str:
if self._clipboard_str is not None:
return self._clipboard_str
owner = xlib.XGetSelectionOwner(self._x_display, self._clipboard_atom)
if not owner:
return ''
text = ''
if owner == self._window:
data, size, actual_atom = self.get_single_property(self._window, self._clipboard_atom,
self._utf8_atom)
else:
notification = xlib.XEvent()
# Convert to selection notification.
xlib.XConvertSelection(self._x_display,
self._clipboard_atom,
self._utf8_atom,
self._clipboard_atom,
self._window,
xlib.CurrentTime)
while not xlib.XCheckTypedWindowEvent(self._x_display, self._window, xlib.SelectionNotify, byref(notification)):
self.dispatch_platform_event(notification)
if not notification.xselection.property:
return ''
data, size, actual_atom = self.get_single_property(notification.xselection.requestor, notification.xselection.property,
self._utf8_atom)
if actual_atom == self._incr_atom:
# Not implemented.
if _debug:
print("X11: Clipboard data is too large, not implemented.")
elif actual_atom == self._utf8_atom:
if data:
text_bytes = string_at(data, size)
text = text_bytes.decode('utf-8')
self._clipboard_str = text
xlib.XFree(data)
return text
# 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)
@staticmethod
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
@staticmethod
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)
@ViewEventHandler
@XlibEventHandler(xlib.KeyPress)
@XlibEventHandler(xlib.KeyRelease)
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)
@XlibEventHandler(xlib.KeyPress)
@XlibEventHandler(xlib.KeyRelease)
def _event_key(self, ev):
return self._event_key_view(ev)
@ViewEventHandler
@XlibEventHandler(xlib.MotionNotify)
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)
@XlibEventHandler(xlib.MotionNotify)
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)
@XlibEventHandler(xlib.ClientMessage)
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, data, and actual atom 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, actualAtom.value
@XlibEventHandler(xlib.SelectionNotify)
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)
@staticmethod
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
@ViewEventHandler
@XlibEventHandler(xlib.ButtonPress)
@XlibEventHandler(xlib.ButtonRelease)
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)
@ViewEventHandler
@XlibEventHandler(xlib.Expose)
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')
@ViewEventHandler
@XlibEventHandler(xlib.EnterNotify)
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)
@ViewEventHandler
@XlibEventHandler(xlib.LeaveNotify)
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)
@XlibEventHandler(xlib.ConfigureNotify)
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
@XlibEventHandler(xlib.FocusIn)
def _event_focusin(self, ev):
self._active = True
self._update_exclusivity()
self.dispatch_event('on_activate')
xlib.XSetICFocus(self._x_ic)
@XlibEventHandler(xlib.FocusOut)
def _event_focusout(self, ev):
self._active = False
self._update_exclusivity()
self.dispatch_event('on_deactivate')
xlib.XUnsetICFocus(self._x_ic)
@XlibEventHandler(xlib.MapNotify)
def _event_mapnotify(self, ev):
self._mapped = True
self.dispatch_event('on_show')
self._update_exclusivity()
@XlibEventHandler(xlib.UnmapNotify)
def _event_unmapnotify(self, ev):
self._mapped = False
self.dispatch_event('on_hide')
@XlibEventHandler(xlib.SelectionClear)
def _event_selection_clear(self, ev):
if ev.xselectionclear.selection == self._clipboard_atom:
# Another application cleared the clipboard.
self._clipboard_str = None
@XlibEventHandler(xlib.SelectionRequest)
def _event_selection_request(self, ev):
request = ev.xselectionrequest
if _debug:
rt = xlib.XGetAtomName(self._x_display, request.target)
rp = xlib.XGetAtomName(self._x_display, request.property)
print(f"X11 debug: request target {rt}")
print(f"X11 debug: request property {rp}")
out_event = xlib.XEvent()
out_event.xany.type = xlib.SelectionNotify
out_event.xselection.selection = request.selection
out_event.xselection.display = request.display
out_event.xselection.target = 0
out_event.xselection.property = 0
out_event.xselection.requestor = request.requestor
out_event.xselection.time = request.time
if (xlib.XGetSelectionOwner(self._x_display, self._clipboard_atom) == self._window and
ev.xselection.target == self._clipboard_atom):
if request.target == self._target_atom:
atoms_ar = (xlib.Atom * 1)(self._utf8_atom)
ptr = cast(pointer(atoms_ar), POINTER(c_ubyte))
xlib.XChangeProperty(self._x_display, request.requestor,
request.property, XA_ATOM, 32,
xlib.PropModeReplace,
ptr, sizeof(atoms_ar)//sizeof(c_ulong))
out_event.xselection.property = request.property
out_event.xselection.target = request.target
elif request.target == self._utf8_atom:
# We are being requested for a UTF-8 string.
text = self._clipboard_str.encode('utf-8')
size = len(self._clipboard_str)
xlib.XChangeProperty(self._x_display, request.requestor,
request.property, request.target, 8,
xlib.PropModeReplace,
(c_ubyte * size).from_buffer_copy(text), size)
out_event.xselection.property = request.property
out_event.xselection.target = request.target
# Send request event back to requestor with updated changes.
xlib.XSendEvent(self._x_display, request.requestor, 0, 0, byref(out_event))
# Seems to work find without it. May add later.
#xlib.XSync(self._x_display, False)
__all__ = ["XlibEventHandler", "XlibWindow"]