PageRenderTime 68ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/pyglet/window/xlib/__init__.py

https://code.google.com/p/pyglet/
Python | 1309 lines | 979 code | 171 blank | 159 comment | 187 complexity | 90e468b936e8d3fa51fc596265cf1835 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. # ----------------------------------------------------------------------------
  2. # pyglet
  3. # Copyright (c) 2006-2008 Alex Holkner
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # * Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # * Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in
  14. # the documentation and/or other materials provided with the
  15. # distribution.
  16. # * Neither the name of pyglet nor the names of its
  17. # contributors may be used to endorse or promote products
  18. # derived from this software without specific prior written
  19. # permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  24. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  25. # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  26. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  27. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  29. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  30. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  31. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32. # POSSIBILITY OF SUCH DAMAGE.
  33. # ----------------------------------------------------------------------------
  34. __docformat__ = 'restructuredtext'
  35. __version__ = '$Id$'
  36. from ctypes import *
  37. import unicodedata
  38. import warnings
  39. import pyglet
  40. from pyglet.window import WindowException, NoSuchDisplayException, \
  41. MouseCursorException, MouseCursor, \
  42. DefaultMouseCursor, ImageMouseCursor, BaseWindow, _PlatformEventHandler, \
  43. _ViewEventHandler
  44. from pyglet.window import key
  45. from pyglet.window import mouse
  46. from pyglet.event import EventDispatcher
  47. from pyglet.canvas.xlib import XlibCanvas
  48. from pyglet.libs.x11 import xlib
  49. from pyglet.libs.x11 import cursorfont
  50. from pyglet.compat import asbytes
  51. try:
  52. from pyglet.libs.x11 import xsync
  53. _have_xsync = True
  54. except:
  55. _have_xsync = False
  56. class mwmhints_t(Structure):
  57. _fields_ = [
  58. ('flags', c_uint32),
  59. ('functions', c_uint32),
  60. ('decorations', c_uint32),
  61. ('input_mode', c_int32),
  62. ('status', c_uint32)
  63. ]
  64. # XXX: wraptypes can't parse the header this function is in yet
  65. XkbSetDetectableAutoRepeat = xlib._lib.XkbSetDetectableAutoRepeat
  66. XkbSetDetectableAutoRepeat.restype = c_int
  67. XkbSetDetectableAutoRepeat.argtypes = [POINTER(xlib.Display), c_int, POINTER(c_int)]
  68. _can_detect_autorepeat = None
  69. XA_CARDINAL = 6 # Xatom.h:14
  70. # Do we have the November 2000 UTF8 extension?
  71. _have_utf8 = hasattr(xlib._lib, 'Xutf8TextListToTextProperty')
  72. # symbol,ctrl -> motion mapping
  73. _motion_map = {
  74. (key.UP, False): key.MOTION_UP,
  75. (key.RIGHT, False): key.MOTION_RIGHT,
  76. (key.DOWN, False): key.MOTION_DOWN,
  77. (key.LEFT, False): key.MOTION_LEFT,
  78. (key.RIGHT, True): key.MOTION_NEXT_WORD,
  79. (key.LEFT, True): key.MOTION_PREVIOUS_WORD,
  80. (key.HOME, False): key.MOTION_BEGINNING_OF_LINE,
  81. (key.END, False): key.MOTION_END_OF_LINE,
  82. (key.PAGEUP, False): key.MOTION_PREVIOUS_PAGE,
  83. (key.PAGEDOWN, False): key.MOTION_NEXT_PAGE,
  84. (key.HOME, True): key.MOTION_BEGINNING_OF_FILE,
  85. (key.END, True): key.MOTION_END_OF_FILE,
  86. (key.BACKSPACE, False): key.MOTION_BACKSPACE,
  87. (key.DELETE, False): key.MOTION_DELETE,
  88. }
  89. class XlibException(WindowException):
  90. '''An X11-specific exception. This exception is probably a programming
  91. error in pyglet.'''
  92. pass
  93. class XlibMouseCursor(MouseCursor):
  94. drawable = False
  95. def __init__(self, cursor):
  96. self.cursor = cursor
  97. # Platform event data is single item, so use platform event handler directly.
  98. XlibEventHandler = _PlatformEventHandler
  99. ViewEventHandler = _ViewEventHandler
  100. class XlibWindow(BaseWindow):
  101. _x_display = None # X display connection
  102. _x_screen_id = None # X screen index
  103. _x_ic = None # X input context
  104. _window = None # Xlib window handle
  105. _minimum_size = None
  106. _maximum_size = None
  107. _override_redirect = False
  108. _x = 0
  109. _y = 0 # Last known window position
  110. _width = 0
  111. _height = 0 # Last known window size
  112. _mouse_exclusive_client = None # x,y of "real" mouse during exclusive
  113. _mouse_buttons = [False] * 6 # State of each xlib button
  114. _keyboard_exclusive = False
  115. _active = True
  116. _applied_mouse_exclusive = False
  117. _applied_keyboard_exclusive = False
  118. _mapped = False
  119. _lost_context = False
  120. _lost_context_state = False
  121. _enable_xsync = False
  122. _current_sync_value = None
  123. _current_sync_valid = False
  124. _needs_resize = False # True when resize event has been received but not
  125. # dispatched
  126. _default_event_mask = (0x1ffffff
  127. & ~xlib.PointerMotionHintMask
  128. & ~xlib.ResizeRedirectMask
  129. & ~xlib.SubstructureNotifyMask)
  130. def __init__(self, *args, **kwargs):
  131. # Bind event handlers
  132. self._event_handlers = {}
  133. self._view_event_handlers = {}
  134. for name in self._platform_event_names:
  135. if not hasattr(self, name):
  136. continue
  137. func = getattr(self, name)
  138. for message in func._platform_event_data:
  139. if hasattr(func, '_view'):
  140. self._view_event_handlers[message] = func
  141. else:
  142. self._event_handlers[message] = func
  143. super(XlibWindow, self).__init__(*args, **kwargs)
  144. global _can_detect_autorepeat
  145. if _can_detect_autorepeat == None:
  146. supported_rtrn = c_int()
  147. _can_detect_autorepeat = XkbSetDetectableAutoRepeat(self.display._display, c_int(1), byref(supported_rtrn))
  148. if _can_detect_autorepeat:
  149. self.pressed_keys = set()
  150. def _recreate(self, changes):
  151. # If flipping to/from fullscreen, need to recreate the window. (This
  152. # is the case with both override_redirect method and
  153. # _NET_WM_STATE_FULLSCREEN).
  154. #
  155. # A possible improvement could be to just hide the top window,
  156. # destroy the GLX window, and reshow it again when leaving fullscreen.
  157. # This would prevent the floating window from being moved by the
  158. # WM.
  159. if ('fullscreen' in changes or 'resizable' in changes):
  160. # clear out the GLX context
  161. self.context.detach()
  162. xlib.XDestroyWindow(self._x_display, self._window)
  163. del self.display._window_map[self._window]
  164. del self.display._window_map[self._view]
  165. self._window = None
  166. self._mapped = False
  167. # TODO: detect state loss only by examining context share.
  168. if 'context' in changes:
  169. self._lost_context = True
  170. self._lost_context_state = True
  171. self._create()
  172. def _create(self):
  173. # Unmap existing window if necessary while we fiddle with it.
  174. if self._window and self._mapped:
  175. self._unmap()
  176. self._x_display = self.display._display
  177. self._x_screen_id = self.display.x_screen
  178. # Create X window if not already existing.
  179. if not self._window:
  180. root = xlib.XRootWindow(self._x_display, self._x_screen_id)
  181. visual_info = self.config.get_visual_info()
  182. visual = visual_info.visual
  183. visual_id = xlib.XVisualIDFromVisual(visual)
  184. default_visual = xlib.XDefaultVisual(
  185. self._x_display, self._x_screen_id)
  186. default_visual_id = xlib.XVisualIDFromVisual(default_visual)
  187. window_attributes = xlib.XSetWindowAttributes()
  188. if visual_id != default_visual_id:
  189. window_attributes.colormap = xlib.XCreateColormap(
  190. self._x_display, root, visual, xlib.AllocNone)
  191. else:
  192. window_attributes.colormap = xlib.XDefaultColormap(
  193. self._x_display, self._x_screen_id)
  194. window_attributes.bit_gravity = xlib.StaticGravity
  195. # Issue 287: Compiz on Intel/Mesa doesn't draw window decoration
  196. # unless CWBackPixel is given in mask. Should have
  197. # no effect on other systems, so it's set
  198. # unconditionally.
  199. mask = xlib.CWColormap | xlib.CWBitGravity | xlib.CWBackPixel
  200. if self._fullscreen:
  201. width, height = self.screen.width, self.screen.height
  202. self._view_x = (width - self._width) // 2
  203. self._view_y = (height - self._height) // 2
  204. else:
  205. width, height = self._width, self._height
  206. self._view_x = self._view_y = 0
  207. self._window = xlib.XCreateWindow(self._x_display, root,
  208. 0, 0, width, height, 0, visual_info.depth,
  209. xlib.InputOutput, visual, mask,
  210. byref(window_attributes))
  211. self._view = xlib.XCreateWindow(self._x_display,
  212. self._window, self._view_x, self._view_y,
  213. self._width, self._height, 0, visual_info.depth,
  214. xlib.InputOutput, visual, mask,
  215. byref(window_attributes));
  216. xlib.XMapWindow(self._x_display, self._view)
  217. xlib.XSelectInput(
  218. self._x_display, self._view, self._default_event_mask)
  219. self.display._window_map[self._window] = \
  220. self.dispatch_platform_event
  221. self.display._window_map[self._view] = \
  222. self.dispatch_platform_event_view
  223. self.canvas = XlibCanvas(self.display, self._view)
  224. self.context.attach(self.canvas)
  225. self.context.set_vsync(self._vsync) # XXX ?
  226. # Setting null background pixmap disables drawing the background,
  227. # preventing flicker while resizing (in theory).
  228. #
  229. # Issue 287: Compiz on Intel/Mesa doesn't draw window decoration if
  230. # this is called. As it doesn't seem to have any
  231. # effect anyway, it's just commented out.
  232. #xlib.XSetWindowBackgroundPixmap(self._x_display, self._window, 0)
  233. self._enable_xsync = (pyglet.options['xsync'] and
  234. self.display._enable_xsync and
  235. self.config.double_buffer)
  236. # Set supported protocols
  237. protocols = []
  238. protocols.append(xlib.XInternAtom(self._x_display,
  239. asbytes('WM_DELETE_WINDOW'), False))
  240. if self._enable_xsync:
  241. protocols.append(xlib.XInternAtom(self._x_display,
  242. asbytes('_NET_WM_SYNC_REQUEST'),
  243. False))
  244. protocols = (c_ulong * len(protocols))(*protocols)
  245. xlib.XSetWMProtocols(self._x_display, self._window,
  246. protocols, len(protocols))
  247. # Create window resize sync counter
  248. if self._enable_xsync:
  249. value = xsync.XSyncValue()
  250. self._sync_counter = xlib.XID(
  251. xsync.XSyncCreateCounter(self._x_display, value))
  252. atom = xlib.XInternAtom(self._x_display,
  253. asbytes('_NET_WM_SYNC_REQUEST_COUNTER'), False)
  254. ptr = pointer(self._sync_counter)
  255. xlib.XChangeProperty(self._x_display, self._window,
  256. atom, XA_CARDINAL, 32,
  257. xlib.PropModeReplace,
  258. cast(ptr, POINTER(c_ubyte)), 1)
  259. # Set window attributes
  260. attributes = xlib.XSetWindowAttributes()
  261. attributes_mask = 0
  262. self._override_redirect = False
  263. if self._fullscreen:
  264. if pyglet.options['xlib_fullscreen_override_redirect']:
  265. # Try not to use this any more, it causes problems; disabled
  266. # by default in favour of _NET_WM_STATE_FULLSCREEN.
  267. attributes.override_redirect = self._fullscreen
  268. attributes_mask |= xlib.CWOverrideRedirect
  269. self._override_redirect = True
  270. else:
  271. self._set_wm_state('_NET_WM_STATE_FULLSCREEN')
  272. if self._fullscreen:
  273. xlib.XMoveResizeWindow(self._x_display, self._window,
  274. self.screen.x, self.screen.y,
  275. self.screen.width, self.screen.height)
  276. else:
  277. xlib.XResizeWindow(self._x_display, self._window,
  278. self._width, self._height)
  279. xlib.XChangeWindowAttributes(self._x_display, self._window,
  280. attributes_mask, byref(attributes))
  281. # Set style
  282. styles = {
  283. self.WINDOW_STYLE_DEFAULT: '_NET_WM_WINDOW_TYPE_NORMAL',
  284. self.WINDOW_STYLE_DIALOG: '_NET_WM_WINDOW_TYPE_DIALOG',
  285. self.WINDOW_STYLE_TOOL: '_NET_WM_WINDOW_TYPE_UTILITY',
  286. }
  287. if self._style in styles:
  288. self._set_atoms_property('_NET_WM_WINDOW_TYPE',
  289. (styles[self._style],))
  290. elif self._style == self.WINDOW_STYLE_BORDERLESS:
  291. MWM_HINTS_DECORATIONS = 1 << 1
  292. PROP_MWM_HINTS_ELEMENTS = 5
  293. mwmhints = mwmhints_t()
  294. mwmhints.flags = MWM_HINTS_DECORATIONS
  295. mwmhints.decorations = 0
  296. name = xlib.XInternAtom(self._x_display, asbytes('_MOTIF_WM_HINTS'), False)
  297. xlib.XChangeProperty(self._x_display, self._window,
  298. name, name, 32, xlib.PropModeReplace,
  299. cast(pointer(mwmhints), POINTER(c_ubyte)),
  300. PROP_MWM_HINTS_ELEMENTS)
  301. # Set resizeable
  302. if not self._resizable and not self._fullscreen:
  303. self.set_minimum_size(self._width, self._height)
  304. self.set_maximum_size(self._width, self._height)
  305. # Set caption
  306. self.set_caption(self._caption)
  307. # this is supported by some compositors (ie gnome-shell), and more to come
  308. # see: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idp6357888
  309. _NET_WM_BYPASS_COMPOSITOR_HINT_ON = c_ulong(int(self._fullscreen))
  310. name = xlib.XInternAtom(self._x_display,
  311. asbytes('_NET_WM_BYPASS_COMPOSITOR'), False)
  312. ptr = pointer(_NET_WM_BYPASS_COMPOSITOR_HINT_ON)
  313. xlib.XChangeProperty(self._x_display, self._window,
  314. name, XA_CARDINAL, 32,
  315. xlib.PropModeReplace,
  316. cast(ptr, POINTER(c_ubyte)), 1)
  317. # Create input context. A good but very outdated reference for this
  318. # is http://www.sbin.org/doc/Xlib/chapt_11.html
  319. if _have_utf8 and not self._x_ic:
  320. if not self.display._x_im:
  321. xlib.XSetLocaleModifiers(asbytes('@im=none'))
  322. self.display._x_im = \
  323. xlib.XOpenIM(self._x_display, None, None, None)
  324. xlib.XFlush(self._x_display);
  325. # Need to set argtypes on this function because it's vararg,
  326. # and ctypes guesses wrong.
  327. xlib.XCreateIC.argtypes = [xlib.XIM,
  328. c_char_p, c_int,
  329. c_char_p, xlib.Window,
  330. c_char_p, xlib.Window,
  331. c_void_p]
  332. self._x_ic = xlib.XCreateIC(self.display._x_im,
  333. asbytes('inputStyle'), xlib.XIMPreeditNothing|xlib.XIMStatusNothing,
  334. asbytes('clientWindow'), self._window,
  335. asbytes('focusWindow'), self._window,
  336. None)
  337. filter_events = c_ulong()
  338. xlib.XGetICValues(self._x_ic,
  339. 'filterEvents', byref(filter_events),
  340. None)
  341. self._default_event_mask |= filter_events.value
  342. xlib.XSetICFocus(self._x_ic)
  343. self.switch_to()
  344. if self._visible:
  345. self.set_visible(True)
  346. self.set_mouse_platform_visible()
  347. self._applied_mouse_exclusive = None
  348. self._update_exclusivity()
  349. def _map(self):
  350. if self._mapped:
  351. return
  352. # Map the window, wait for map event before continuing.
  353. xlib.XSelectInput(
  354. self._x_display, self._window, xlib.StructureNotifyMask)
  355. xlib.XMapRaised(self._x_display, self._window)
  356. e = xlib.XEvent()
  357. while True:
  358. xlib.XNextEvent(self._x_display, e)
  359. if e.type == xlib.ConfigureNotify:
  360. self._width = e.xconfigure.width
  361. self._height = e.xconfigure.height
  362. elif e.type == xlib.MapNotify:
  363. break
  364. xlib.XSelectInput(
  365. self._x_display, self._window, self._default_event_mask)
  366. self._mapped = True
  367. if self._override_redirect:
  368. # Possibly an override_redirect issue.
  369. self.activate()
  370. self._update_view_size()
  371. self.dispatch_event('on_resize', self._width, self._height)
  372. self.dispatch_event('on_show')
  373. self.dispatch_event('on_expose')
  374. def _unmap(self):
  375. if not self._mapped:
  376. return
  377. xlib.XSelectInput(
  378. self._x_display, self._window, xlib.StructureNotifyMask)
  379. xlib.XUnmapWindow(self._x_display, self._window)
  380. e = xlib.XEvent()
  381. while True:
  382. xlib.XNextEvent(self._x_display, e)
  383. if e.type == xlib.UnmapNotify:
  384. break
  385. xlib.XSelectInput(
  386. self._x_display, self._window, self._default_event_mask)
  387. self._mapped = False
  388. def _get_root(self):
  389. attributes = xlib.XWindowAttributes()
  390. xlib.XGetWindowAttributes(self._x_display, self._window,
  391. byref(attributes))
  392. return attributes.root
  393. def _is_reparented(self):
  394. root = c_ulong()
  395. parent = c_ulong()
  396. children = pointer(c_ulong())
  397. n_children = c_uint()
  398. xlib.XQueryTree(self._x_display, self._window,
  399. byref(root), byref(parent), byref(children),
  400. byref(n_children))
  401. return root.value != parent.value
  402. def close(self):
  403. if not self._window:
  404. return
  405. self.context.destroy()
  406. self._unmap()
  407. if self._window:
  408. xlib.XDestroyWindow(self._x_display, self._window)
  409. del self.display._window_map[self._window]
  410. self._window = None
  411. if _have_utf8:
  412. xlib.XDestroyIC(self._x_ic)
  413. self._x_ic = None
  414. super(XlibWindow, self).close()
  415. def switch_to(self):
  416. if self.context:
  417. self.context.set_current()
  418. def flip(self):
  419. self.draw_mouse_cursor()
  420. # TODO canvas.flip?
  421. if self.context:
  422. self.context.flip()
  423. self._sync_resize()
  424. def set_vsync(self, vsync):
  425. if pyglet.options['vsync'] is not None:
  426. vsync = pyglet.options['vsync']
  427. self._vsync = vsync
  428. self.context.set_vsync(vsync)
  429. def set_caption(self, caption):
  430. if caption is None:
  431. caption = ''
  432. self._caption = caption
  433. self._set_text_property('WM_NAME', caption, allow_utf8=False)
  434. self._set_text_property('WM_ICON_NAME', caption, allow_utf8=False)
  435. self._set_text_property('_NET_WM_NAME', caption)
  436. self._set_text_property('_NET_WM_ICON_NAME', caption)
  437. def get_caption(self):
  438. return self._caption
  439. def set_size(self, width, height):
  440. if self._fullscreen:
  441. raise WindowException('Cannot set size of fullscreen window.')
  442. self._width = width
  443. self._height = height
  444. if not self._resizable:
  445. self.set_minimum_size(width, height)
  446. self.set_maximum_size(width, height)
  447. xlib.XResizeWindow(self._x_display, self._window, width, height)
  448. self._update_view_size()
  449. self.dispatch_event('on_resize', width, height)
  450. def _update_view_size(self):
  451. xlib.XResizeWindow(self._x_display, self._view,
  452. self._width, self._height)
  453. def get_size(self):
  454. # XGetGeometry and XWindowAttributes seem to always return the
  455. # original size of the window, which is wrong after the user
  456. # has resized it.
  457. # XXX this is probably fixed now, with fix of resize.
  458. return self._width, self._height
  459. def set_location(self, x, y):
  460. if self._is_reparented():
  461. # Assume the window manager has reparented our top-level window
  462. # only once, in which case attributes.x/y give the offset from
  463. # the frame to the content window. Better solution would be
  464. # to use _NET_FRAME_EXTENTS, where supported.
  465. attributes = xlib.XWindowAttributes()
  466. xlib.XGetWindowAttributes(self._x_display, self._window,
  467. byref(attributes))
  468. # XXX at least under KDE's WM these attrs are both 0
  469. x -= attributes.x
  470. y -= attributes.y
  471. xlib.XMoveWindow(self._x_display, self._window, x, y)
  472. def get_location(self):
  473. child = xlib.Window()
  474. x = c_int()
  475. y = c_int()
  476. xlib.XTranslateCoordinates(self._x_display,
  477. self._window,
  478. self._get_root(),
  479. 0, 0,
  480. byref(x),
  481. byref(y),
  482. byref(child))
  483. return x.value, y.value
  484. def activate(self):
  485. xlib.XSetInputFocus(self._x_display, self._window,
  486. xlib.RevertToParent, xlib.CurrentTime)
  487. def set_visible(self, visible=True):
  488. if visible:
  489. self._map()
  490. else:
  491. self._unmap()
  492. self._visible = visible
  493. def set_minimum_size(self, width, height):
  494. self._minimum_size = width, height
  495. self._set_wm_normal_hints()
  496. def set_maximum_size(self, width, height):
  497. self._maximum_size = width, height
  498. self._set_wm_normal_hints()
  499. def minimize(self):
  500. xlib.XIconifyWindow(self._x_display, self._window, self._x_screen_id)
  501. def maximize(self):
  502. self._set_wm_state('_NET_WM_STATE_MAXIMIZED_HORZ',
  503. '_NET_WM_STATE_MAXIMIZED_VERT')
  504. def set_mouse_platform_visible(self, platform_visible=None):
  505. if not self._window:
  506. return
  507. if platform_visible is None:
  508. platform_visible = self._mouse_visible and \
  509. not self._mouse_cursor.drawable
  510. if not platform_visible:
  511. # Hide pointer by creating an empty cursor
  512. black = xlib.XBlackPixel(self._x_display, self._x_screen_id)
  513. black = xlib.XColor()
  514. bmp = xlib.XCreateBitmapFromData(self._x_display, self._window,
  515. c_buffer(8), 8, 8)
  516. cursor = xlib.XCreatePixmapCursor(self._x_display, bmp, bmp,
  517. black, black, 0, 0)
  518. xlib.XDefineCursor(self._x_display, self._window, cursor)
  519. xlib.XFreeCursor(self._x_display, cursor)
  520. xlib.XFreePixmap(self._x_display, bmp)
  521. else:
  522. # Restore cursor
  523. if isinstance(self._mouse_cursor, XlibMouseCursor):
  524. xlib.XDefineCursor(self._x_display, self._window,
  525. self._mouse_cursor.cursor)
  526. else:
  527. xlib.XUndefineCursor(self._x_display, self._window)
  528. def set_mouse_position(self, x, y):
  529. xlib.XWarpPointer(self._x_display,
  530. 0, # src window
  531. self._window, # dst window
  532. 0, 0, # src x, y
  533. 0, 0, # src w, h
  534. x, self._height - y,
  535. )
  536. def _update_exclusivity(self):
  537. mouse_exclusive = self._active and self._mouse_exclusive
  538. keyboard_exclusive = self._active and self._keyboard_exclusive
  539. if mouse_exclusive != self._applied_mouse_exclusive:
  540. if mouse_exclusive:
  541. self.set_mouse_platform_visible(False)
  542. # Restrict to client area
  543. xlib.XGrabPointer(self._x_display, self._window,
  544. True,
  545. 0,
  546. xlib.GrabModeAsync,
  547. xlib.GrabModeAsync,
  548. self._window,
  549. 0,
  550. xlib.CurrentTime)
  551. # Move pointer to center of window
  552. x = self._width // 2
  553. y = self._height // 2
  554. self._mouse_exclusive_client = x, y
  555. self.set_mouse_position(x, y)
  556. elif self._fullscreen and not self.screen._xinerama:
  557. # Restrict to fullscreen area (prevent viewport scrolling)
  558. self.set_mouse_position(0, 0)
  559. r = xlib.XGrabPointer(self._x_display, self._view,
  560. True, 0,
  561. xlib.GrabModeAsync,
  562. xlib.GrabModeAsync,
  563. self._view,
  564. 0,
  565. xlib.CurrentTime)
  566. if r:
  567. # Failed to grab, try again later
  568. self._applied_mouse_exclusive = None
  569. return
  570. self.set_mouse_platform_visible()
  571. else:
  572. # Unclip
  573. xlib.XUngrabPointer(self._x_display, xlib.CurrentTime)
  574. self.set_mouse_platform_visible()
  575. self._applied_mouse_exclusive = mouse_exclusive
  576. if keyboard_exclusive != self._applied_keyboard_exclusive:
  577. if keyboard_exclusive:
  578. xlib.XGrabKeyboard(self._x_display,
  579. self._window,
  580. False,
  581. xlib.GrabModeAsync,
  582. xlib.GrabModeAsync,
  583. xlib.CurrentTime)
  584. else:
  585. xlib.XUngrabKeyboard(self._x_display, xlib.CurrentTime)
  586. self._applied_keyboard_exclusive = keyboard_exclusive
  587. def set_exclusive_mouse(self, exclusive=True):
  588. if exclusive == self._mouse_exclusive:
  589. return
  590. self._mouse_exclusive = exclusive
  591. self._update_exclusivity()
  592. def set_exclusive_keyboard(self, exclusive=True):
  593. if exclusive == self._keyboard_exclusive:
  594. return
  595. self._keyboard_exclusive = exclusive
  596. self._update_exclusivity()
  597. def get_system_mouse_cursor(self, name):
  598. if name == self.CURSOR_DEFAULT:
  599. return DefaultMouseCursor()
  600. # NQR means default shape is not pretty... surely there is another
  601. # cursor font?
  602. cursor_shapes = {
  603. self.CURSOR_CROSSHAIR: cursorfont.XC_crosshair,
  604. self.CURSOR_HAND: cursorfont.XC_hand2,
  605. self.CURSOR_HELP: cursorfont.XC_question_arrow, # NQR
  606. self.CURSOR_NO: cursorfont.XC_pirate, # NQR
  607. self.CURSOR_SIZE: cursorfont.XC_fleur,
  608. self.CURSOR_SIZE_UP: cursorfont.XC_top_side,
  609. self.CURSOR_SIZE_UP_RIGHT: cursorfont.XC_top_right_corner,
  610. self.CURSOR_SIZE_RIGHT: cursorfont.XC_right_side,
  611. self.CURSOR_SIZE_DOWN_RIGHT: cursorfont.XC_bottom_right_corner,
  612. self.CURSOR_SIZE_DOWN: cursorfont.XC_bottom_side,
  613. self.CURSOR_SIZE_DOWN_LEFT: cursorfont.XC_bottom_left_corner,
  614. self.CURSOR_SIZE_LEFT: cursorfont.XC_left_side,
  615. self.CURSOR_SIZE_UP_LEFT: cursorfont.XC_top_left_corner,
  616. self.CURSOR_SIZE_UP_DOWN: cursorfont.XC_sb_v_double_arrow,
  617. self.CURSOR_SIZE_LEFT_RIGHT: cursorfont.XC_sb_h_double_arrow,
  618. self.CURSOR_TEXT: cursorfont.XC_xterm,
  619. self.CURSOR_WAIT: cursorfont.XC_watch,
  620. self.CURSOR_WAIT_ARROW: cursorfont.XC_watch, # NQR
  621. }
  622. if name not in cursor_shapes:
  623. raise MouseCursorException('Unknown cursor name "%s"' % name)
  624. cursor = xlib.XCreateFontCursor(self._x_display, cursor_shapes[name])
  625. return XlibMouseCursor(cursor)
  626. def set_icon(self, *images):
  627. # Careful! XChangeProperty takes an array of long when data type
  628. # is 32-bit (but long can be 64 bit!), so pad high bytes of format if
  629. # necessary.
  630. import sys
  631. format = {
  632. ('little', 4): 'BGRA',
  633. ('little', 8): 'BGRAAAAA',
  634. ('big', 4): 'ARGB',
  635. ('big', 8): 'AAAAARGB'
  636. }[(sys.byteorder, sizeof(c_ulong))]
  637. data = asbytes('')
  638. for image in images:
  639. image = image.get_image_data()
  640. pitch = -(image.width * len(format))
  641. s = c_buffer(sizeof(c_ulong) * 2)
  642. memmove(s, cast((c_ulong * 2)(image.width, image.height),
  643. POINTER(c_ubyte)), len(s))
  644. data += s.raw + image.get_data(format, pitch)
  645. buffer = (c_ubyte * len(data))()
  646. memmove(buffer, data, len(data))
  647. atom = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_ICON'), False)
  648. xlib.XChangeProperty(self._x_display, self._window, atom, XA_CARDINAL,
  649. 32, xlib.PropModeReplace, buffer, len(data)//sizeof(c_ulong))
  650. # Private utility
  651. def _set_wm_normal_hints(self):
  652. hints = xlib.XAllocSizeHints().contents
  653. if self._minimum_size:
  654. hints.flags |= xlib.PMinSize
  655. hints.min_width, hints.min_height = self._minimum_size
  656. if self._maximum_size:
  657. hints.flags |= xlib.PMaxSize
  658. hints.max_width, hints.max_height = self._maximum_size
  659. xlib.XSetWMNormalHints(self._x_display, self._window, byref(hints))
  660. def _set_text_property(self, name, value, allow_utf8=True):
  661. atom = xlib.XInternAtom(self._x_display, asbytes(name), False)
  662. if not atom:
  663. raise XlibException('Undefined atom "%s"' % name)
  664. assert type(value) in (str, unicode)
  665. property = xlib.XTextProperty()
  666. if _have_utf8 and allow_utf8:
  667. buf = create_string_buffer(value.encode('utf8'))
  668. result = xlib.Xutf8TextListToTextProperty(self._x_display,
  669. cast(pointer(buf), c_char_p), 1, xlib.XUTF8StringStyle,
  670. byref(property))
  671. if result < 0:
  672. raise XlibException('Could not create UTF8 text property')
  673. else:
  674. buf = create_string_buffer(value.encode('ascii', 'ignore'))
  675. result = xlib.XStringListToTextProperty(
  676. cast(pointer(buf), c_char_p), 1, byref(property))
  677. if result < 0:
  678. raise XlibException('Could not create text property')
  679. xlib.XSetTextProperty(self._x_display,
  680. self._window, byref(property), atom)
  681. # XXX <rj> Xlib doesn't like us freeing this
  682. #xlib.XFree(property.value)
  683. def _set_atoms_property(self, name, values, mode=xlib.PropModeReplace):
  684. name_atom = xlib.XInternAtom(self._x_display, asbytes(name), False)
  685. atoms = []
  686. for value in values:
  687. atoms.append(xlib.XInternAtom(self._x_display, asbytes(value), False))
  688. atom_type = xlib.XInternAtom(self._x_display, asbytes('ATOM'), False)
  689. if len(atoms):
  690. atoms_ar = (xlib.Atom * len(atoms))(*atoms)
  691. xlib.XChangeProperty(self._x_display, self._window,
  692. name_atom, atom_type, 32, mode,
  693. cast(pointer(atoms_ar), POINTER(c_ubyte)), len(atoms))
  694. else:
  695. xlib.XDeleteProperty(self._x_display, self._window, net_wm_state)
  696. def _set_wm_state(self, *states):
  697. # Set property
  698. net_wm_state = xlib.XInternAtom(self._x_display, asbytes('_NET_WM_STATE'), False)
  699. atoms = []
  700. for state in states:
  701. atoms.append(xlib.XInternAtom(self._x_display, asbytes(state), False))
  702. atom_type = xlib.XInternAtom(self._x_display, asbytes('ATOM'), False)
  703. if len(atoms):
  704. atoms_ar = (xlib.Atom * len(atoms))(*atoms)
  705. xlib.XChangeProperty(self._x_display, self._window,
  706. net_wm_state, atom_type, 32, xlib.PropModePrepend,
  707. cast(pointer(atoms_ar), POINTER(c_ubyte)), len(atoms))
  708. else:
  709. xlib.XDeleteProperty(self._x_display, self._window, net_wm_state)
  710. # Nudge the WM
  711. e = xlib.XEvent()
  712. e.xclient.type = xlib.ClientMessage
  713. e.xclient.message_type = net_wm_state
  714. e.xclient.display = cast(self._x_display, POINTER(xlib.Display))
  715. e.xclient.window = self._window
  716. e.xclient.format = 32
  717. e.xclient.data.l[0] = xlib.PropModePrepend
  718. for i, atom in enumerate(atoms):
  719. e.xclient.data.l[i + 1] = atom
  720. xlib.XSendEvent(self._x_display, self._get_root(),
  721. False, xlib.SubstructureRedirectMask, byref(e))
  722. # Event handling
  723. def dispatch_events(self):
  724. self.dispatch_pending_events()
  725. self._allow_dispatch_event = True
  726. e = xlib.XEvent()
  727. # Cache these in case window is closed from an event handler
  728. _x_display = self._x_display
  729. _window = self._window
  730. _view = self._view
  731. # Check for the events specific to this window
  732. while xlib.XCheckWindowEvent(_x_display, _window,
  733. 0x1ffffff, byref(e)):
  734. # Key events are filtered by the xlib window event
  735. # handler so they get a shot at the prefiltered event.
  736. if e.xany.type not in (xlib.KeyPress, xlib.KeyRelease):
  737. if xlib.XFilterEvent(e, 0):
  738. continue
  739. self.dispatch_platform_event(e)
  740. # Check for the events specific to this view
  741. while xlib.XCheckWindowEvent(_x_display, _view,
  742. 0x1ffffff, byref(e)):
  743. # Key events are filtered by the xlib window event
  744. # handler so they get a shot at the prefiltered event.
  745. if e.xany.type not in (xlib.KeyPress, xlib.KeyRelease):
  746. if xlib.XFilterEvent(e, 0):
  747. continue
  748. self.dispatch_platform_event_view(e)
  749. # Generic events for this window (the window close event).
  750. while xlib.XCheckTypedWindowEvent(_x_display, _window,
  751. xlib.ClientMessage, byref(e)):
  752. self.dispatch_platform_event(e)
  753. if self._needs_resize:
  754. self.dispatch_event('on_resize', self._width, self._height)
  755. self.dispatch_event('on_expose')
  756. self._needs_resize = False
  757. self._allow_dispatch_event = False
  758. def dispatch_pending_events(self):
  759. while self._event_queue:
  760. EventDispatcher.dispatch_event(self, *self._event_queue.pop(0))
  761. # Dispatch any context-related events
  762. if self._lost_context:
  763. self._lost_context = False
  764. EventDispatcher.dispatch_event(self, 'on_context_lost')
  765. if self._lost_context_state:
  766. self._lost_context_state = False
  767. EventDispatcher.dispatch_event(self, 'on_context_state_lost')
  768. def dispatch_platform_event(self, e):
  769. if self._applied_mouse_exclusive is None:
  770. self._update_exclusivity()
  771. event_handler = self._event_handlers.get(e.type)
  772. if event_handler:
  773. event_handler(e)
  774. def dispatch_platform_event_view(self, e):
  775. event_handler = self._view_event_handlers.get(e.type)
  776. if event_handler:
  777. event_handler(e)
  778. @staticmethod
  779. def _translate_modifiers(state):
  780. modifiers = 0
  781. if state & xlib.ShiftMask:
  782. modifiers |= key.MOD_SHIFT
  783. if state & xlib.ControlMask:
  784. modifiers |= key.MOD_CTRL
  785. if state & xlib.LockMask:
  786. modifiers |= key.MOD_CAPSLOCK
  787. if state & xlib.Mod1Mask:
  788. modifiers |= key.MOD_ALT
  789. if state & xlib.Mod2Mask:
  790. modifiers |= key.MOD_NUMLOCK
  791. if state & xlib.Mod4Mask:
  792. modifiers |= key.MOD_WINDOWS
  793. if state & xlib.Mod5Mask:
  794. modifiers |= key.MOD_SCROLLLOCK
  795. return modifiers
  796. # Event handlers
  797. '''
  798. def _event_symbol(self, event):
  799. # pyglet.self.key keysymbols are identical to X11 keysymbols, no
  800. # need to map the keysymbol.
  801. symbol = xlib.XKeycodeToKeysym(self._x_display, event.xkey.keycode, 0)
  802. if symbol == 0:
  803. # XIM event
  804. return None
  805. elif symbol not in key._key_names.keys():
  806. symbol = key.user_key(event.xkey.keycode)
  807. return symbol
  808. '''
  809. def _event_text_symbol(self, ev):
  810. text = None
  811. symbol = xlib.KeySym()
  812. buffer = create_string_buffer(128)
  813. # Look up raw keysym before XIM filters it (default for keypress and
  814. # keyrelease)
  815. count = xlib.XLookupString(ev.xkey,
  816. buffer, len(buffer) - 1,
  817. byref(symbol), None)
  818. # Give XIM a shot
  819. filtered = xlib.XFilterEvent(ev, ev.xany.window)
  820. if ev.type == xlib.KeyPress and not filtered:
  821. status = c_int()
  822. if _have_utf8:
  823. encoding = 'utf8'
  824. count = xlib.Xutf8LookupString(self._x_ic,
  825. ev.xkey,
  826. buffer, len(buffer) - 1,
  827. byref(symbol), byref(status))
  828. if status.value == xlib.XBufferOverflow:
  829. raise NotImplementedError('TODO: XIM buffer resize')
  830. else:
  831. encoding = 'ascii'
  832. count = xlib.XLookupString(ev.xkey,
  833. buffer, len(buffer) - 1,
  834. byref(symbol), None)
  835. if count:
  836. status.value = xlib.XLookupBoth
  837. if status.value & (xlib.XLookupChars | xlib.XLookupBoth):
  838. text = buffer.value[:count].decode(encoding)
  839. # Don't treat Unicode command codepoints as text, except Return.
  840. if text and unicodedata.category(text) == 'Cc' and text != '\r':
  841. text = None
  842. symbol = symbol.value
  843. # If the event is a XIM filtered event, the keysym will be virtual
  844. # (e.g., aacute instead of A after a dead key). Drop it, we don't
  845. # want these kind of key events.
  846. if ev.xkey.keycode == 0 and not filtered:
  847. symbol = None
  848. # pyglet.self.key keysymbols are identical to X11 keysymbols, no
  849. # need to map the keysymbol. For keysyms outside the pyglet set, map
  850. # raw key code to a user key.
  851. if symbol and symbol not in key._key_names and ev.xkey.keycode:
  852. # Issue 353: Symbol is uppercase when shift key held down.
  853. try:
  854. symbol = ord(unichr(symbol).lower())
  855. except ValueError:
  856. # Not a valid unichr, use the keycode
  857. symbol = key.user_key(ev.xkey.keycode)
  858. else:
  859. # If still not recognised, use the keycode
  860. if symbol not in key._key_names:
  861. symbol = key.user_key(ev.xkey.keycode)
  862. if filtered:
  863. # The event was filtered, text must be ignored, but the symbol is
  864. # still good.
  865. return None, symbol
  866. return text, symbol
  867. def _event_text_motion(self, symbol, modifiers):
  868. if modifiers & key.MOD_ALT:
  869. return None
  870. ctrl = modifiers & key.MOD_CTRL != 0
  871. return _motion_map.get((symbol, ctrl), None)
  872. @ViewEventHandler
  873. @XlibEventHandler(xlib.KeyPress)
  874. @XlibEventHandler(xlib.KeyRelease)
  875. def _event_key_view(self, ev):
  876. # Try to detect autorepeat ourselves if the server doesn't support it
  877. # XXX: Doesn't always work, better off letting the server do it
  878. global _can_detect_autorepeat
  879. if not _can_detect_autorepeat and ev.type == xlib.KeyRelease:
  880. # Look in the queue for a matching KeyPress with same timestamp,
  881. # indicating an auto-repeat rather than actual key event.
  882. saved = []
  883. while True:
  884. auto_event = xlib.XEvent()
  885. result = xlib.XCheckWindowEvent(self._x_display,
  886. self._window, xlib.KeyPress|xlib.KeyRelease,
  887. byref(auto_event))
  888. if not result:
  889. break
  890. saved.append(auto_event)
  891. if auto_event.type == xlib.KeyRelease:
  892. # just save this off for restoration back to the queue
  893. continue
  894. if ev.xkey.keycode == auto_event.xkey.keycode:
  895. # Found a key repeat: dispatch EVENT_TEXT* event
  896. text, symbol = self._event_text_symbol(auto_event)
  897. modifiers = self._translate_modifiers(ev.xkey.state)
  898. modifiers_ctrl = modifiers & (key.MOD_CTRL | key.MOD_ALT)
  899. motion = self._event_text_motion(symbol, modifiers)
  900. if motion:
  901. if modifiers & key.MOD_SHIFT:
  902. self.dispatch_event(
  903. 'on_text_motion_select', motion)
  904. else:
  905. self.dispatch_event('on_text_motion', motion)
  906. elif text and not modifiers_ctrl:
  907. self.dispatch_event('on_text', text)
  908. ditched = saved.pop()
  909. for auto_event in reversed(saved):
  910. xlib.XPutBackEvent(self._x_display, byref(auto_event))
  911. return
  912. else:
  913. # Key code of press did not match, therefore no repeating
  914. # is going on, stop searching.
  915. break
  916. # Whoops, put the events back, it's for real.
  917. for auto_event in reversed(saved):
  918. xlib.XPutBackEvent(self._x_display, byref(auto_event))
  919. text, symbol = self._event_text_symbol(ev)
  920. modifiers = self._translate_modifiers(ev.xkey.state)
  921. modifiers_ctrl = modifiers & (key.MOD_CTRL | key.MOD_ALT)
  922. motion = self._event_text_motion(symbol, modifiers)
  923. if ev.type == xlib.KeyPress:
  924. if symbol and (not _can_detect_autorepeat or symbol not in self.pressed_keys):
  925. self.dispatch_event('on_key_press', symbol, modifiers)
  926. if _can_detect_autorepeat:
  927. self.pressed_keys.add(symbol)
  928. if motion:
  929. if modifiers & key.MOD_SHIFT:
  930. self.dispatch_event('on_text_motion_select', motion)
  931. else:
  932. self.dispatch_event('on_text_motion', motion)
  933. elif text and not modifiers_ctrl:
  934. self.dispatch_event('on_text', text)
  935. elif ev.type == xlib.KeyRelease:
  936. if symbol:
  937. self.dispatch_event('on_key_release', symbol, modifiers)
  938. if _can_detect_autorepeat and symbol in self.pressed_keys:
  939. self.pressed_keys.remove(symbol)
  940. @XlibEventHandler(xlib.KeyPress)
  941. @XlibEventHandler(xlib.KeyRelease)
  942. def _event_key(self, ev):
  943. return self._event_key_view(ev)
  944. @ViewEventHandler
  945. @XlibEventHandler(xlib.MotionNotify)
  946. def _event_motionnotify_view(self, ev):
  947. x = ev.xmotion.x
  948. y = self.height - ev.xmotion.y
  949. if self._mouse_in_window:
  950. dx = x - self._mouse_x
  951. dy = y - self._mouse_y
  952. else:
  953. dx = dy = 0
  954. if self._applied_mouse_exclusive and \
  955. (ev.xmotion.x, ev.xmotion.y) == self._mouse_exclusive_client:
  956. # Ignore events caused by XWarpPointer
  957. self._mouse_x = x
  958. self._mouse_y = y
  959. return
  960. if self._applied_mouse_exclusive:
  961. # Reset pointer position
  962. ex, ey = self._mouse_exclusive_client
  963. xlib.XWarpPointer(self._x_display,
  964. 0,
  965. self._window,
  966. 0, 0,
  967. 0, 0,
  968. ex, ey)
  969. self._mouse_x = x
  970. self._mouse_y = y
  971. self._mouse_in_window = True
  972. buttons = 0
  973. if ev.xmotion.state & xlib.Button1MotionMask:
  974. buttons |= mouse.LEFT
  975. if ev.xmotion.state & xlib.Button2MotionMask:
  976. buttons |= mouse.MIDDLE
  977. if ev.xmotion.state & xlib.Button3MotionMask:
  978. buttons |= mouse.RIGHT
  979. if buttons:
  980. # Drag event
  981. modifiers = self._translate_modifiers(ev.xmotion.state)
  982. self.dispatch_event('on_mouse_drag',
  983. x, y, dx, dy, buttons, modifiers)
  984. else:
  985. # Motion event
  986. self.dispatch_event('on_mouse_motion', x, y, dx, dy)
  987. @XlibEventHandler(xlib.MotionNotify)
  988. def _event_motionnotify(self, ev):
  989. # Window motion looks for drags that are outside the view but within
  990. # the window.
  991. buttons = 0
  992. if ev.xmotion.state & xlib.Button1MotionMask:
  993. buttons |= mouse.LEFT
  994. if ev.xmotion.state & xlib.Button2MotionMask:
  995. buttons |= mouse.MIDDLE
  996. if ev.xmotion.state & xlib.Button3MotionMask:
  997. buttons |= mouse.RIGHT
  998. if buttons:
  999. # Drag event
  1000. x = ev.xmotion.x - self._view_x
  1001. y = self._height - (ev.xmotion.y - self._view_y)
  1002. if self._mouse_in_window:
  1003. dx = x - self._mouse_x
  1004. dy = y - self._mouse_y
  1005. else:
  1006. dx = dy = 0
  1007. self._mouse_x = x
  1008. self._mouse_y = y
  1009. modifiers = self._translate_modifiers(ev.xmotion.state)
  1010. self.dispatch_event('on_mouse_drag',
  1011. x, y, dx, dy, buttons, modifiers)
  1012. @XlibEventHandler(xlib.ClientMessage)
  1013. def _event_clientmessage(self, ev):
  1014. atom = ev.xclient.data.l[0]
  1015. if atom == xlib.XInternAtom(ev.xclient.display,
  1016. asbytes('WM_DELETE_WINDOW'), False):
  1017. self.dispatch_event('on_close')
  1018. elif (self._enable_xsync and
  1019. atom == xlib.XInternAtom(ev.xclient.display,
  1020. asbytes('_NET_WM_SYNC_REQUEST'), False)):
  1021. lo = ev.xclient.data.l[2]
  1022. hi = ev.xclient.data.l[3]
  1023. self._current_sync_value = xsync.XSyncValue(hi, lo)
  1024. def _sync_resize(self):
  1025. if self._enable_xsync and self._current_sync_valid:
  1026. if xsync.XSyncValueIsZero(self._current_sync_value):
  1027. self._current_sync_valid = False
  1028. return
  1029. xsync.XSyncSetCounter(self._x_display,
  1030. self._sync_counter,
  1031. self._current_sync_value)
  1032. self._current_sync_value = None
  1033. self._current_sync_valid = False
  1034. @ViewEventHandler
  1035. @XlibEventHandler(xlib.ButtonPress)
  1036. @XlibEventHandler(xlib.ButtonRelease)
  1037. def _event_button(self, ev):
  1038. x = ev.xbutton.x
  1039. y = self.height - ev.xbutton.y
  1040. button = 1 << (ev.xbutton.button - 1) # 1, 2, 3 -> 1, 2, 4
  1041. modifiers = self._translate_modifiers(ev.xbutton.state)
  1042. if ev.type == xlib.ButtonPress:
  1043. # override_redirect issue: manually activate this window if
  1044. # fullscreen.
  1045. if self._override_redirect and not self._active:
  1046. self.activate()
  1047. if ev.xbutton.button == 4:
  1048. self.dispatch_event('on_mouse_scroll', x, y, 0, 1)
  1049. elif ev.xbutton.button == 5:
  1050. self.dispatch_event('on_mouse_scroll', x, y, 0, -1)
  1051. elif ev.xbutton.button < len(self._mouse_buttons):
  1052. self._mouse_buttons[ev.xbutton.button] = True
  1053. self.dispatch_event('on_mouse_press',
  1054. x, y, button, modifiers)
  1055. else:
  1056. if ev.xbutton.button < 4:
  1057. self._mouse_buttons[ev.xbutton.button] = False
  1058. self.dispatch_event('on_mouse_release',
  1059. x, y, button, modifiers)
  1060. @ViewEventHandler
  1061. @XlibEventHandler(xlib.Expose)
  1062. def _event_expose(self, ev):
  1063. # Ignore all expose events except the last one. We could be told
  1064. # about exposure rects - but I don't see the point since we're
  1065. # working with OpenGL and we'll just redraw the whole scene.
  1066. if ev.xexpose.count > 0: return
  1067. self.dispatch_event('on_expose')
  1068. @ViewEventHandler
  1069. @XlibEventHandler(xlib.EnterNotify)
  1070. def _event_enternotify(self, ev):
  1071. # figure active mouse buttons
  1072. # XXX ignore modifier state?
  1073. state = ev.xcrossing.state
  1074. self._mouse_buttons[1] = state & xlib.Button1Mask
  1075. self._mouse_buttons[2] = state & xlib.Button2Mask
  1076. self._mouse_buttons[3] = state & xlib.Button3Mask
  1077. self._mouse_buttons[4] = state & xlib.Button4Mask
  1078. self._mouse_buttons[5] = state & xlib.Button5Mask
  1079. # mouse position
  1080. x = self._mouse_x = ev.xcrossing.x
  1081. y = self._mouse_y = self.height - ev.xcrossing.y
  1082. self._mouse_in_window = True
  1083. # XXX there may be more we could do here
  1084. self.dispatch_event('on_mouse_enter', x, y)
  1085. @ViewEventHandler
  1086. @XlibEventHandler(xlib.LeaveNotify)
  1087. def _event_leavenotify(self, ev):
  1088. x = self._mouse_x = ev.xcrossing.x
  1089. y = self._mouse_y = self.height - ev.xcrossing.y
  1090. self._mouse_in_window = False
  1091. self.dispatch_event('on_mouse_leave', x, y)
  1092. @XlibEventHandler(xlib.ConfigureNotify)
  1093. def _event_configurenotify(self, ev):
  1094. if self._enable_xsync and self._current_sync_value:
  1095. self._current_sync_valid = True
  1096. if self._fullscreen:
  1097. return
  1098. self.switch_to()
  1099. w, h = ev.xconfigure.width, ev.xconfigure.height
  1100. x, y = ev.xconfigure.x, ev.xconfigure.y
  1101. if self._width != w or self._height != h:
  1102. self._width = w
  1103. self._height = h
  1104. self._update_view_size()
  1105. self._needs_resize = True
  1106. if self._x != x or self._y != y:
  1107. self.dispatch_event('on_move', x, y)
  1108. self._x = x
  1109. self._y = y
  1110. @XlibEventHandler(xlib.FocusIn)
  1111. def _event_focusin(self, ev):
  1112. self._active = True
  1113. self._update_exclusivity()
  1114. self.dispatch_event('on_activate')
  1115. xlib.XSetICFocus(self._x_ic)
  1116. @XlibEventHandler(xlib.FocusOut)
  1117. def _event_focusout(self, ev):
  1118. self._active = False
  1119. self._update_exclusivity()
  1120. self.dispatch_event('on_deactivate')
  1121. xlib.XUnsetICFocus(self._x_ic)
  1122. @XlibEventHandler(xlib.MapNotify)
  1123. def _event_mapnotify(self, ev):
  1124. self._mapped = True
  1125. self.dispatch_event('on_show')
  1126. self._update_exclusivity()
  1127. @XlibEventHandler(xlib.UnmapNotify)
  1128. def _event_unmapnotify(self, ev):
  1129. self._mapped = False
  1130. self.dispatch_event('on_hide')