Files
MementoMori/kivy/core/window/window_sdl2.py
thatscringebro abd15f28b6 Ajout du GUI
2022-08-08 16:31:52 -04:00

987 lines
34 KiB
Python

# found a way to include it more easily.
'''
SDL2 Window
===========
Windowing provider directly based on our own wrapped version of SDL.
TODO:
- fix keys
- support scrolling
- clean code
- manage correctly all sdl events
'''
__all__ = ('WindowSDL', )
from os.path import join
import sys
from typing import Optional
from kivy import kivy_data_dir
from kivy.logger import Logger
from kivy.base import EventLoop
from kivy.clock import Clock
from kivy.config import Config
from kivy.core.window import WindowBase
try:
from kivy.core.window._window_sdl2 import _WindowSDL2Storage
except ImportError:
from kivy.core import handle_win_lib_import_error
handle_win_lib_import_error(
'window', 'sdl2', 'kivy.core.window._window_sdl2')
raise
from kivy.input.provider import MotionEventProvider
from kivy.input.motionevent import MotionEvent
from kivy.resources import resource_find
from kivy.utils import platform, deprecated
from kivy.compat import unichr
from collections import deque
# SDL_keycode.h, https://wiki.libsdl.org/SDL_Keymod
KMOD_NONE = 0x0000
KMOD_LSHIFT = 0x0001
KMOD_RSHIFT = 0x0002
KMOD_LCTRL = 0x0040
KMOD_RCTRL = 0x0080
KMOD_LALT = 0x0100
KMOD_RALT = 0x0200
KMOD_LGUI = 0x0400
KMOD_RGUI = 0x0800
KMOD_NUM = 0x1000
KMOD_CAPS = 0x2000
KMOD_MODE = 0x4000
SDLK_SHIFTL = 1073742049
SDLK_SHIFTR = 1073742053
SDLK_LCTRL = 1073742048
SDLK_RCTRL = 1073742052
SDLK_LALT = 1073742050
SDLK_RALT = 1073742054
SDLK_LEFT = 1073741904
SDLK_RIGHT = 1073741903
SDLK_UP = 1073741906
SDLK_DOWN = 1073741905
SDLK_HOME = 1073741898
SDLK_END = 1073741901
SDLK_PAGEUP = 1073741899
SDLK_PAGEDOWN = 1073741902
SDLK_SUPER = 1073742051
SDLK_CAPS = 1073741881
SDLK_INSERT = 1073741897
SDLK_KEYPADNUM = 1073741907
SDLK_KP_DIVIDE = 1073741908
SDLK_KP_MULTIPLY = 1073741909
SDLK_KP_MINUS = 1073741910
SDLK_KP_PLUS = 1073741911
SDLK_KP_ENTER = 1073741912
SDLK_KP_1 = 1073741913
SDLK_KP_2 = 1073741914
SDLK_KP_3 = 1073741915
SDLK_KP_4 = 1073741916
SDLK_KP_5 = 1073741917
SDLK_KP_6 = 1073741918
SDLK_KP_7 = 1073741919
SDLK_KP_8 = 1073741920
SDLK_KP_9 = 1073741921
SDLK_KP_0 = 1073741922
SDLK_KP_DOT = 1073741923
SDLK_F1 = 1073741882
SDLK_F2 = 1073741883
SDLK_F3 = 1073741884
SDLK_F4 = 1073741885
SDLK_F5 = 1073741886
SDLK_F6 = 1073741887
SDLK_F7 = 1073741888
SDLK_F8 = 1073741889
SDLK_F9 = 1073741890
SDLK_F10 = 1073741891
SDLK_F11 = 1073741892
SDLK_F12 = 1073741893
SDLK_F13 = 1073741894
SDLK_F14 = 1073741895
SDLK_F15 = 1073741896
class SDL2MotionEvent(MotionEvent):
def __init__(self, *args, **kwargs):
kwargs.setdefault('is_touch', True)
kwargs.setdefault('type_id', 'touch')
super().__init__(*args, **kwargs)
self.profile = ('pos', 'pressure')
def depack(self, args):
self.sx, self.sy, self.pressure = args
super().depack(args)
class SDL2MotionEventProvider(MotionEventProvider):
win = None
q = deque()
touchmap = {}
def update(self, dispatch_fn):
touchmap = self.touchmap
while True:
try:
value = self.q.pop()
except IndexError:
return
action, fid, x, y, pressure = value
y = 1 - y
if fid not in touchmap:
touchmap[fid] = me = SDL2MotionEvent(
'sdl', fid, (x, y, pressure)
)
else:
me = touchmap[fid]
me.move((x, y, pressure))
if action == 'fingerdown':
dispatch_fn('begin', me)
elif action == 'fingerup':
me.update_time_end()
dispatch_fn('end', me)
del touchmap[fid]
else:
dispatch_fn('update', me)
class WindowSDL(WindowBase):
_win_dpi_watch: Optional['_WindowsSysDPIWatch'] = None
_do_resize_ev = None
managed_textinput = True
def __init__(self, **kwargs):
self._pause_loop = False
self._cursor_entered = False
self._drop_pos = None
self._win = _WindowSDL2Storage()
super(WindowSDL, self).__init__()
self.titlebar_widget = None
self._mouse_x = self._mouse_y = -1
self._meta_keys = (
KMOD_LCTRL, KMOD_RCTRL, KMOD_RSHIFT,
KMOD_LSHIFT, KMOD_RALT, KMOD_LALT, KMOD_LGUI,
KMOD_RGUI, KMOD_NUM, KMOD_CAPS, KMOD_MODE)
self.command_keys = {
27: 'escape',
9: 'tab',
8: 'backspace',
13: 'enter',
127: 'del',
271: 'enter',
273: 'up',
274: 'down',
275: 'right',
276: 'left',
278: 'home',
279: 'end',
280: 'pgup',
281: 'pgdown'}
self._mouse_buttons_down = set()
self.key_map = {SDLK_LEFT: 276, SDLK_RIGHT: 275, SDLK_UP: 273,
SDLK_DOWN: 274, SDLK_HOME: 278, SDLK_END: 279,
SDLK_PAGEDOWN: 281, SDLK_PAGEUP: 280, SDLK_SHIFTR: 303,
SDLK_SHIFTL: 304, SDLK_SUPER: 309, SDLK_LCTRL: 305,
SDLK_RCTRL: 306, SDLK_LALT: 308, SDLK_RALT: 307,
SDLK_CAPS: 301, SDLK_INSERT: 277, SDLK_F1: 282,
SDLK_F2: 283, SDLK_F3: 284, SDLK_F4: 285, SDLK_F5: 286,
SDLK_F6: 287, SDLK_F7: 288, SDLK_F8: 289, SDLK_F9: 290,
SDLK_F10: 291, SDLK_F11: 292, SDLK_F12: 293,
SDLK_F13: 294, SDLK_F14: 295, SDLK_F15: 296,
SDLK_KEYPADNUM: 300, SDLK_KP_DIVIDE: 267,
SDLK_KP_MULTIPLY: 268, SDLK_KP_MINUS: 269,
SDLK_KP_PLUS: 270, SDLK_KP_ENTER: 271,
SDLK_KP_DOT: 266, SDLK_KP_0: 256, SDLK_KP_1: 257,
SDLK_KP_2: 258, SDLK_KP_3: 259, SDLK_KP_4: 260,
SDLK_KP_5: 261, SDLK_KP_6: 262, SDLK_KP_7: 263,
SDLK_KP_8: 264, SDLK_KP_9: 265}
if platform == 'ios':
# XXX ios keyboard suck, when backspace is hit, the delete
# keycode is sent. fix it.
self.key_map[127] = 8
elif platform == 'android':
# map android back button to escape
self.key_map[1073742094] = 27
self.bind(minimum_width=self._set_minimum_size,
minimum_height=self._set_minimum_size)
self.bind(allow_screensaver=self._set_allow_screensaver)
def get_window_info(self):
return self._win.get_window_info()
def _set_minimum_size(self, *args):
minimum_width = self.minimum_width
minimum_height = self.minimum_height
if minimum_width and minimum_height:
self._win.set_minimum_size(minimum_width, minimum_height)
elif minimum_width or minimum_height:
Logger.warning(
'Both Window.minimum_width and Window.minimum_height must be '
'bigger than 0 for the size restriction to take effect.')
def _set_allow_screensaver(self, *args):
self._win.set_allow_screensaver(self.allow_screensaver)
def _event_filter(self, action, *largs):
from kivy.app import App
if action == 'app_terminating':
EventLoop.quit = True
elif action == 'app_lowmemory':
self.dispatch('on_memorywarning')
elif action == 'app_willenterbackground':
from kivy.base import stopTouchApp
app = App.get_running_app()
if not app:
Logger.info('WindowSDL: No running App found, pause.')
elif not app.dispatch('on_pause'):
Logger.info(
'WindowSDL: App doesn\'t support pause mode, stop.')
stopTouchApp()
return 0
self._pause_loop = True
elif action == 'app_didenterforeground':
# on iOS, the did enter foreground is launched at the start
# of the application. in our case, we want it only when the app
# is resumed
if self._pause_loop:
self._pause_loop = False
app = App.get_running_app()
if app:
app.dispatch('on_resume')
elif action == 'windowresized':
self._size = largs
self._win.resize_window(*self._size)
# Force kivy to render the frame now, so that the canvas is drawn.
EventLoop.idle()
return 0
def create_window(self, *largs):
if self._fake_fullscreen:
if not self.borderless:
self.fullscreen = self._fake_fullscreen = False
elif not self.fullscreen or self.fullscreen == 'auto':
self.custom_titlebar = \
self.borderless = self._fake_fullscreen = False
elif self.custom_titlebar:
if platform == 'win':
# use custom behaviour
# To handle aero snapping and rounded corners
self.borderless = False
if self.fullscreen == 'fake':
self.borderless = self._fake_fullscreen = True
Logger.warning("The 'fake' fullscreen option has been "
"deprecated, use Window.borderless or the "
"borderless Config option instead.")
if not self.initialized:
if self.position == 'auto':
pos = None, None
elif self.position == 'custom':
pos = self.left, self.top
# ensure we have an event filter
self._win.set_event_filter(self._event_filter)
# setup window
w, h = self.system_size
resizable = Config.getboolean('graphics', 'resizable')
state = (Config.get('graphics', 'window_state')
if self._is_desktop else None)
self.system_size = _size = self._win.setup_window(
pos[0], pos[1], w, h, self.borderless,
self.fullscreen, resizable, state,
self.get_gl_backend_name())
# calculate density/dpi
if platform == 'win':
from ctypes import windll
self._density = 1.
try:
hwnd = windll.user32.GetActiveWindow()
self.dpi = float(windll.user32.GetDpiForWindow(hwnd))
except AttributeError:
pass
else:
sz = self._win._get_gl_size()[0]
self._density = density = sz / _size[0]
if self._is_desktop and self.size[0] != _size[0]:
self.dpi = density * 96.
# never stay with a None pos, application using w.center
# will be fired.
self._pos = (0, 0)
self._set_minimum_size()
self._set_allow_screensaver()
if state == 'hidden':
self._focus = False
else:
w, h = self.system_size
self._win.resize_window(w, h)
if platform == 'win':
if self.custom_titlebar:
# check dragging+resize or just dragging
if Config.getboolean('graphics', 'resizable'):
import win32con
import ctypes
self._win.set_border_state(False)
# make windows dispatch,
# WM_NCCALCSIZE explicitly
ctypes.windll.user32.SetWindowPos(
self._win.get_window_info().window,
win32con.HWND_TOP,
*self._win.get_window_pos(),
*self.system_size,
win32con.SWP_FRAMECHANGED
)
else:
self._win.set_border_state(True)
else:
self._win.set_border_state(self.borderless)
else:
self._win.set_border_state(self.borderless
or self.custom_titlebar)
self._win.set_fullscreen_mode(self.fullscreen)
super(WindowSDL, self).create_window()
# set mouse visibility
self._set_cursor_state(self.show_cursor)
if self.initialized:
return
# auto add input provider
Logger.info('Window: auto add sdl2 input provider')
from kivy.base import EventLoop
SDL2MotionEventProvider.win = self
EventLoop.add_input_provider(SDL2MotionEventProvider('sdl', ''))
# set window icon before calling set_mode
try:
filename_icon = self.icon or Config.get('kivy', 'window_icon')
if filename_icon == '':
logo_size = 32
if platform == 'macosx':
logo_size = 512
elif platform == 'win':
logo_size = 64
filename_icon = 'kivy-icon-{}.png'.format(logo_size)
filename_icon = resource_find(
join(kivy_data_dir, 'logo', filename_icon))
self.set_icon(filename_icon)
except:
Logger.exception('Window: cannot set icon')
if platform == 'win' and self._win_dpi_watch is None:
self._win_dpi_watch = _WindowsSysDPIWatch(window=self)
self._win_dpi_watch.start()
def close(self):
self._win.teardown_window()
super(WindowSDL, self).close()
if self._win_dpi_watch is not None:
self._win_dpi_watch.stop()
self._win_dpi_watch = None
self.initialized = False
def maximize(self):
if self._is_desktop:
self._win.maximize_window()
else:
Logger.warning('Window: maximize() is used only on desktop OSes.')
def minimize(self):
if self._is_desktop:
self._win.minimize_window()
else:
Logger.warning('Window: minimize() is used only on desktop OSes.')
def restore(self):
if self._is_desktop:
self._win.restore_window()
else:
Logger.warning('Window: restore() is used only on desktop OSes.')
def hide(self):
if self._is_desktop:
self._win.hide_window()
else:
Logger.warning('Window: hide() is used only on desktop OSes.')
def show(self):
if self._is_desktop:
self._win.show_window()
else:
Logger.warning('Window: show() is used only on desktop OSes.')
def raise_window(self):
if self._is_desktop:
self._win.raise_window()
else:
Logger.warning('Window: show() is used only on desktop OSes.')
@deprecated
def toggle_fullscreen(self):
if self.fullscreen in (True, 'auto'):
self.fullscreen = False
else:
self.fullscreen = 'auto'
def set_title(self, title):
self._win.set_window_title(title)
def set_icon(self, filename):
self._win.set_window_icon(str(filename))
def screenshot(self, *largs, **kwargs):
filename = super(WindowSDL, self).screenshot(*largs, **kwargs)
if filename is None:
return
from kivy.graphics.opengl import glReadPixels, GL_RGB, GL_UNSIGNED_BYTE
width, height = self.size
data = glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE)
self._win.save_bytes_in_png(filename, data, width, height)
Logger.debug('Window: Screenshot saved at <%s>' % filename)
return filename
def flip(self):
self._win.flip()
super(WindowSDL, self).flip()
def set_system_cursor(self, cursor_name):
result = self._win.set_system_cursor(cursor_name)
return result
def _get_window_pos(self):
return self._win.get_window_pos()
def _set_window_pos(self, x, y):
self._win.set_window_pos(x, y)
# Transparent Window background
def _is_shaped(self):
return self._win.is_window_shaped()
def _set_shape(self, shape_image, mode='default',
cutoff=False, color_key=None):
modes = ('default', 'binalpha', 'reversebinalpha', 'colorkey')
color_key = color_key or (0, 0, 0, 1)
if mode not in modes:
Logger.warning(
'Window: shape mode can be only '
'{}'.format(', '.join(modes))
)
return
if not isinstance(color_key, (tuple, list)):
return
if len(color_key) not in (3, 4):
return
if len(color_key) == 3:
color_key = (color_key[0], color_key[1], color_key[2], 1)
Logger.warning(
'Window: Shape color_key must be only tuple or list'
)
return
color_key = (
color_key[0] * 255,
color_key[1] * 255,
color_key[2] * 255,
color_key[3] * 255
)
assert cutoff in (1, 0)
shape_image = shape_image or Config.get('kivy', 'window_shape')
shape_image = resource_find(shape_image) or shape_image
self._win.set_shape(shape_image, mode, cutoff, color_key)
def _get_shaped_mode(self):
return self._win.get_shaped_mode()
def _set_shaped_mode(self, value):
self._set_shape(
shape_image=self.shape_image,
mode=value, cutoff=self.shape_cutoff,
color_key=self.shape_color_key
)
return self._win.get_shaped_mode()
# twb end
def _set_cursor_state(self, value):
self._win._set_cursor_state(value)
def _fix_mouse_pos(self, x, y):
self.mouse_pos = (
x * self._density,
(self.system_size[1] - 1 - y) * self._density
)
return x, y
def mainloop(self):
# for android/iOS, we don't want to have any event nor executing our
# main loop while the pause is going on. This loop wait any event (not
# handled by the event filter), and remove them from the queue.
# Nothing happen during the pause on iOS, except gyroscope value sent
# over joystick. So it's safe.
while self._pause_loop:
self._win.wait_event()
if not self._pause_loop:
break
event = self._win.poll()
if event is None:
continue
# A drop is send while the app is still in pause.loop
# we need to dispatch it
action, args = event[0], event[1:]
if action.startswith('drop'):
self._dispatch_drop_event(action, args)
# app_terminating event might be received while the app is paused
# in this case EventLoop.quit will be set at _event_filter
elif EventLoop.quit:
return
while True:
event = self._win.poll()
if event is False:
break
if event is None:
continue
action, args = event[0], event[1:]
if action == 'quit':
if self.dispatch('on_request_close'):
continue
EventLoop.quit = True
break
elif action in ('fingermotion', 'fingerdown', 'fingerup'):
# for finger, pass the raw event to SDL motion event provider
# XXX this is problematic. On OSX, it generates touches with 0,
# 0 coordinates, at the same times as mouse. But it works.
# We have a conflict of using either the mouse or the finger.
# Right now, we have no mechanism that we could use to know
# which is the preferred one for the application.
if platform in ('ios', 'android'):
SDL2MotionEventProvider.q.appendleft(event)
pass
elif action == 'mousemotion':
x, y = args
x, y = self._fix_mouse_pos(x, y)
self._mouse_x = x
self._mouse_y = y
if not self._cursor_entered:
self._cursor_entered = True
self.dispatch('on_cursor_enter')
# don't dispatch motion if no button are pressed
if len(self._mouse_buttons_down) == 0:
continue
self._mouse_meta = self.modifiers
self.dispatch('on_mouse_move', x, y, self.modifiers)
elif action in ('mousebuttondown', 'mousebuttonup'):
x, y, button = args
x, y = self._fix_mouse_pos(x, y)
self._mouse_x = x
self._mouse_y = y
if not self._cursor_entered:
self._cursor_entered = True
self.dispatch('on_cursor_enter')
btn = 'left'
if button == 3:
btn = 'right'
elif button == 2:
btn = 'middle'
elif button == 4:
btn = "mouse4"
elif button == 5:
btn = "mouse5"
eventname = 'on_mouse_down'
self._mouse_buttons_down.add(button)
if action == 'mousebuttonup':
eventname = 'on_mouse_up'
self._mouse_buttons_down.remove(button)
self.dispatch(eventname, x, y, btn, self.modifiers)
elif action.startswith('mousewheel'):
x, y = self._win.get_relative_mouse_pos()
if not self._collide_and_dispatch_cursor_enter(x, y):
# Ignore if the cursor position is on the window title bar
# or on its edges
continue
self._update_modifiers()
x, y, button = args
btn = 'scrolldown'
if action.endswith('up'):
btn = 'scrollup'
elif action.endswith('right'):
btn = 'scrollright'
elif action.endswith('left'):
btn = 'scrollleft'
self._mouse_meta = self.modifiers
self._mouse_btn = btn
# times = x if y == 0 else y
# times = min(abs(times), 100)
# for k in range(times):
self._mouse_down = True
self.dispatch('on_mouse_down',
self._mouse_x, self._mouse_y, btn, self.modifiers)
self._mouse_down = False
self.dispatch('on_mouse_up',
self._mouse_x, self._mouse_y, btn, self.modifiers)
elif action.startswith('drop'):
self._dispatch_drop_event(action, args)
# video resize
elif action == 'windowresized':
self._size = self._win.window_size
# don't use trigger here, we want to delay the resize event
ev = self._do_resize_ev
if ev is None:
ev = Clock.schedule_once(self._do_resize, .1)
self._do_resize_ev = ev
else:
ev()
elif action == 'windowmoved':
self.dispatch('on_move')
elif action == 'windowrestored':
self.dispatch('on_restore')
self.canvas.ask_update()
elif action == 'windowexposed':
self.canvas.ask_update()
elif action == 'windowminimized':
self.dispatch('on_minimize')
if Config.getboolean('kivy', 'pause_on_minimize'):
self.do_pause()
elif action == 'windowmaximized':
self.dispatch('on_maximize')
elif action == 'windowhidden':
self.dispatch('on_hide')
elif action == 'windowshown':
self.dispatch('on_show')
elif action == 'windowfocusgained':
self._focus = True
elif action == 'windowfocuslost':
self._focus = False
elif action == 'windowenter':
x, y = self._win.get_relative_mouse_pos()
self._collide_and_dispatch_cursor_enter(x, y)
elif action == 'windowleave':
self._cursor_entered = False
self.dispatch('on_cursor_leave')
elif action == 'joyaxismotion':
stickid, axisid, value = args
self.dispatch('on_joy_axis', stickid, axisid, value)
elif action == 'joyhatmotion':
stickid, hatid, value = args
self.dispatch('on_joy_hat', stickid, hatid, value)
elif action == 'joyballmotion':
stickid, ballid, xrel, yrel = args
self.dispatch('on_joy_ball', stickid, ballid, xrel, yrel)
elif action == 'joybuttondown':
stickid, buttonid = args
self.dispatch('on_joy_button_down', stickid, buttonid)
elif action == 'joybuttonup':
stickid, buttonid = args
self.dispatch('on_joy_button_up', stickid, buttonid)
elif action in ('keydown', 'keyup'):
mod, key, scancode, kstr = args
try:
key = self.key_map[key]
except KeyError:
pass
if action == 'keydown':
self._update_modifiers(mod, key)
else:
# ignore the key, it has been released
self._update_modifiers(mod)
# if mod in self._meta_keys:
if (key not in self._modifiers and
key not in self.command_keys.keys()):
try:
kstr_chr = unichr(key)
try:
# On android, there is no 'encoding' attribute.
# On other platforms, if stdout is redirected,
# 'encoding' may be None
encoding = getattr(sys.stdout, 'encoding',
'utf8') or 'utf8'
kstr_chr.encode(encoding)
kstr = kstr_chr
except UnicodeError:
pass
except ValueError:
pass
# if 'shift' in self._modifiers and key\
# not in self.command_keys.keys():
# return
if action == 'keyup':
self.dispatch('on_key_up', key, scancode)
continue
# don't dispatch more key if down event is accepted
if self.dispatch('on_key_down', key,
scancode, kstr,
self.modifiers):
continue
self.dispatch('on_keyboard', key,
scancode, kstr,
self.modifiers)
elif action == 'textinput':
text = args[0]
self.dispatch('on_textinput', text)
elif action == 'textedit':
text = args[0]
self.dispatch('on_textedit', text)
# unhandled event !
else:
Logger.trace('WindowSDL: Unhandled event %s' % str(event))
def _dispatch_drop_event(self, action, args):
x, y = (0, 0) if self._drop_pos is None else self._drop_pos
if action == 'dropfile':
self.dispatch('on_drop_file', args[0], x, y)
elif action == 'droptext':
self.dispatch('on_drop_text', args[0], x, y)
elif action == 'dropbegin':
self._drop_pos = x, y = self._win.get_relative_mouse_pos()
self._collide_and_dispatch_cursor_enter(x, y)
self.dispatch('on_drop_begin', x, y)
elif action == 'dropend':
self._drop_pos = None
self.dispatch('on_drop_end', x, y)
def _collide_and_dispatch_cursor_enter(self, x, y):
# x, y are relative to window left/top position
w, h = self._win.window_size
if 0 <= x < w and 0 <= y < h:
self._mouse_x, self._mouse_y = self._fix_mouse_pos(x, y)
if not self._cursor_entered:
self._cursor_entered = True
self.dispatch('on_cursor_enter')
return True
def _do_resize(self, dt):
Logger.debug('Window: Resize window to %s' % str(self.size))
self._win.resize_window(*self._size)
self.dispatch('on_pre_resize', *self.size)
def do_pause(self):
# should go to app pause mode (desktop style)
from kivy.app import App
from kivy.base import stopTouchApp
app = App.get_running_app()
if not app:
Logger.info('WindowSDL: No running App found, pause.')
elif not app.dispatch('on_pause'):
Logger.info('WindowSDL: App doesn\'t support pause mode, stop.')
stopTouchApp()
return
# XXX FIXME wait for sdl resume
while True:
event = self._win.poll()
if event is False:
continue
if event is None:
continue
action, args = event[0], event[1:]
if action == 'quit':
EventLoop.quit = True
break
elif action == 'app_willenterforeground':
break
elif action == 'windowrestored':
break
if app:
app.dispatch('on_resume')
def _update_modifiers(self, mods=None, key=None):
if mods is None and key is None:
return
modifiers = set()
if mods is not None:
if mods & (KMOD_RSHIFT | KMOD_LSHIFT):
modifiers.add('shift')
if mods & (KMOD_RALT | KMOD_LALT | KMOD_MODE):
modifiers.add('alt')
if mods & (KMOD_RCTRL | KMOD_LCTRL):
modifiers.add('ctrl')
if mods & (KMOD_RGUI | KMOD_LGUI):
modifiers.add('meta')
if mods & KMOD_NUM:
modifiers.add('numlock')
if mods & KMOD_CAPS:
modifiers.add('capslock')
if key is not None:
if key in (KMOD_RSHIFT, KMOD_LSHIFT):
modifiers.add('shift')
if key in (KMOD_RALT, KMOD_LALT, KMOD_MODE):
modifiers.add('alt')
if key in (KMOD_RCTRL, KMOD_LCTRL):
modifiers.add('ctrl')
if key in (KMOD_RGUI, KMOD_LGUI):
modifiers.add('meta')
if key == KMOD_NUM:
modifiers.add('numlock')
if key == KMOD_CAPS:
modifiers.add('capslock')
self._modifiers = list(modifiers)
return
def request_keyboard(
self, callback, target, input_type='text', keyboard_suggestions=True
):
self._sdl_keyboard = super(WindowSDL, self).\
request_keyboard(
callback, target, input_type, keyboard_suggestions
)
self._win.show_keyboard(
self._system_keyboard,
self.softinput_mode,
input_type,
keyboard_suggestions,
)
Clock.schedule_interval(self._check_keyboard_shown, 1 / 5.)
return self._sdl_keyboard
def release_keyboard(self, *largs):
super(WindowSDL, self).release_keyboard(*largs)
self._win.hide_keyboard()
self._sdl_keyboard = None
return True
def _check_keyboard_shown(self, dt):
if self._sdl_keyboard is None:
return False
if not self._win.is_keyboard_shown():
self._sdl_keyboard.release()
def map_key(self, original_key, new_key):
self.key_map[original_key] = new_key
def unmap_key(self, key):
if key in self.key_map:
del self.key_map[key]
def grab_mouse(self):
self._win.grab_mouse(True)
def ungrab_mouse(self):
self._win.grab_mouse(False)
def set_custom_titlebar(self, titlebar_widget):
if not self.custom_titlebar:
Logger.warning("Window: Window.custom_titlebar not set to True… "
"can't set custom titlebar")
return
self.titlebar_widget = titlebar_widget
return self._win.set_custom_titlebar(self.titlebar_widget) == 0
class _WindowsSysDPIWatch:
hwnd = None
new_windProc = None
old_windProc = None
window: WindowBase = None
def __init__(self, window: WindowBase):
self.window = window
def start(self):
from kivy.input.providers.wm_common import WNDPROC, \
SetWindowLong_WndProc_wrapper
from ctypes import windll
self.hwnd = windll.user32.GetActiveWindow()
# inject our own handler to handle messages before window manager
self.new_windProc = WNDPROC(self._wnd_proc)
self.old_windProc = SetWindowLong_WndProc_wrapper(
self.hwnd, self.new_windProc)
def stop(self):
from kivy.input.providers.wm_common import \
SetWindowLong_WndProc_wrapper
if self.hwnd is None:
return
self.new_windProc = SetWindowLong_WndProc_wrapper(
self.hwnd, self.old_windProc)
self.hwnd = self.new_windProc = self.old_windProc = None
def _wnd_proc(self, hwnd, msg, wParam, lParam):
from kivy.input.providers.wm_common import WM_DPICHANGED, WM_NCCALCSIZE
from ctypes import windll
if msg == WM_DPICHANGED:
ow, oh = self.window.size
old_dpi = self.window.dpi
def clock_callback(*args):
if x_dpi != y_dpi:
raise ValueError(
'Can only handle DPI that are same for x and y')
self.window.dpi = x_dpi
# maintain the same window size
ratio = x_dpi / old_dpi
self.window.size = ratio * ow, ratio * oh
x_dpi = wParam & 0xFFFF
y_dpi = wParam >> 16
Clock.schedule_once(clock_callback, -1)
elif Config.getboolean('graphics', 'resizable') \
and msg == WM_NCCALCSIZE and self.window.custom_titlebar:
return 0
return windll.user32.CallWindowProcW(
self.old_windProc, hwnd, msg, wParam, lParam)