Ajout du GUI

This commit is contained in:
thatscringebro
2022-08-08 16:31:52 -04:00
parent db362ccdca
commit abd15f28b6
851 changed files with 99957 additions and 1 deletions

2460
kivy/core/window/__init__.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,30 @@
include "../../include/config.pxi"
IF USE_WAYLAND:
cdef extern from "wayland-client-protocol.h":
cdef struct wl_display:
pass
cdef struct wl_surface:
pass
cdef struct wl_shell_surface:
pass
IF USE_X11:
cdef extern from "X11/Xlib.h":
cdef struct _XDisplay:
pass
ctypedef _XDisplay Display
ctypedef int XID
ctypedef XID Window
IF UNAME_SYSNAME == 'Windows':
cdef extern from "windows.h":
ctypedef void *HANDLE
ctypedef HANDLE HWND
ctypedef HANDLE HDC
ctypedef HANDLE HINSTANCE

View File

@@ -0,0 +1,86 @@
'''
EGL Rpi Window: EGL Window provider, specialized for the Pi
Inspired by: rpi_vid_core + JF002 rpi kivy repo
'''
__all__ = ('WindowEglRpi', )
from kivy.logger import Logger
from kivy.core.window import WindowBase
from kivy.base import EventLoop, ExceptionManager, stopTouchApp
from kivy.lib.vidcore_lite import bcm, egl
from os import environ
# Default display IDs.
(DISPMANX_ID_MAIN_LCD,
DISPMANX_ID_AUX_LCD,
DISPMANX_ID_HDMI,
DISPMANX_ID_SDTV,
DISPMANX_ID_FORCE_LCD,
DISPMANX_ID_FORCE_TV,
DISPMANX_ID_FORCE_OTHER) = range(7)
class WindowEglRpi(WindowBase):
_rpi_dispmanx_id = int(environ.get("KIVY_BCM_DISPMANX_ID", "0"))
_rpi_dispmanx_layer = int(environ.get("KIVY_BCM_DISPMANX_LAYER", "0"))
gl_backends_ignored = ['sdl2']
def create_window(self):
bcm.host_init()
w, h = bcm.graphics_get_display_size(self._rpi_dispmanx_id)
Logger.debug('Window: Actual display size: {}x{}'.format(
w, h))
self._size = w, h
self._create_window(w, h)
self._create_egl_context(self.win, 0)
super(WindowEglRpi, self).create_window()
def _create_window(self, w, h):
dst = bcm.Rect(0, 0, w, h)
src = bcm.Rect(0, 0, w << 16, h << 16)
display = egl.bcm_display_open(self._rpi_dispmanx_id)
update = egl.bcm_update_start(0)
element = egl.bcm_element_add(
update, display, self._rpi_dispmanx_layer, dst, src)
self.win = egl.NativeWindow(element, w, h)
egl.bcm_update_submit_sync(update)
def _create_egl_context(self, win, flags):
api = egl._constants.EGL_OPENGL_ES_API
c = egl._constants
attribs = [
c.EGL_RED_SIZE, 8,
c.EGL_GREEN_SIZE, 8,
c.EGL_BLUE_SIZE, 8,
c.EGL_ALPHA_SIZE, 8,
c.EGL_DEPTH_SIZE, 16,
c.EGL_STENCIL_SIZE, 8,
c.EGL_SURFACE_TYPE, c.EGL_WINDOW_BIT,
c.EGL_NONE]
attribs_context = [c.EGL_CONTEXT_CLIENT_VERSION, 2, c.EGL_NONE]
display = egl.GetDisplay(c.EGL_DEFAULT_DISPLAY)
egl.Initialise(display)
egl.BindAPI(c.EGL_OPENGL_ES_API)
egl.GetConfigs(display)
config = egl.ChooseConfig(display, attribs, 1)[0]
surface = egl.CreateWindowSurface(display, config, win)
context = egl.CreateContext(display, config, None, attribs_context)
egl.MakeCurrent(display, surface, surface, context)
self.egl_info = (display, surface, context)
egl.MakeCurrent(display, surface, surface, context)
def close(self):
egl.Terminate(self.egl_info[0])
def flip(self):
if not EventLoop.quit:
egl.SwapBuffers(self.egl_info[0], self.egl_info[1])

Binary file not shown.

View File

@@ -0,0 +1,19 @@
include "window_attrs.pxi"
from libc.stdint cimport uintptr_t
IF USE_WAYLAND:
cdef class WindowInfoWayland:
cdef wl_display *display
cdef wl_surface *surface
cdef wl_shell_surface *shell_surface
IF USE_X11:
cdef class WindowInfoX11:
cdef Display *display
cdef Window window
IF UNAME_SYSNAME == 'Windows':
cdef class WindowInfoWindows:
cdef HWND window
cdef HDC hdc

View File

@@ -0,0 +1,449 @@
'''
Window Pygame: windowing provider based on Pygame
.. warning::
Pygame has been deprecated and will be removed in the release after Kivy
1.11.0.
'''
__all__ = ('WindowPygame', )
# fail early if possible
import pygame
from kivy.compat import PY2
from kivy.core.window import WindowBase
from kivy.core import CoreCriticalException
from os import environ
from os.path import exists, join
from kivy.config import Config
from kivy import kivy_data_dir
from kivy.base import ExceptionManager
from kivy.logger import Logger
from kivy.base import stopTouchApp, EventLoop
from kivy.utils import platform, deprecated
from kivy.resources import resource_find
try:
android = None
if platform == 'android':
import android
except ImportError:
pass
# late binding
glReadPixels = GL_RGBA = GL_UNSIGNED_BYTE = None
class WindowPygame(WindowBase):
@deprecated(
msg='Pygame has been deprecated and will be removed after 1.11.0')
def __init__(self, *largs, **kwargs):
super(WindowPygame, self).__init__(*largs, **kwargs)
def create_window(self, *largs):
# ensure the mouse is still not up after window creation, otherwise, we
# have some weird bugs
self.dispatch('on_mouse_up', 0, 0, 'all', [])
# force display to show (available only for fullscreen)
displayidx = Config.getint('graphics', 'display')
if 'SDL_VIDEO_FULLSCREEN_HEAD' not in environ and displayidx != -1:
environ['SDL_VIDEO_FULLSCREEN_HEAD'] = '%d' % displayidx
# init some opengl, same as before.
self.flags = pygame.HWSURFACE | pygame.OPENGL | pygame.DOUBLEBUF
# right now, activate resizable window only on linux.
# on window / macosx, the opengl context is lost, and we need to
# reconstruct everything. Check #168 for a state of the work.
if platform in ('linux', 'macosx', 'win') and \
Config.getboolean('graphics', 'resizable'):
self.flags |= pygame.RESIZABLE
try:
pygame.display.init()
except pygame.error as e:
raise CoreCriticalException(e.message)
multisamples = Config.getint('graphics', 'multisamples')
if multisamples > 0:
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 1)
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES,
multisamples)
pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 16)
pygame.display.gl_set_attribute(pygame.GL_STENCIL_SIZE, 1)
pygame.display.set_caption(self.title)
if self.position == 'auto':
self._pos = None
elif self.position == 'custom':
self._pos = self.left, self.top
else:
raise ValueError('position token in configuration accept only '
'"auto" or "custom"')
if self._fake_fullscreen:
if not self.borderless:
self.fullscreen = self._fake_fullscreen = False
elif not self.fullscreen or self.fullscreen == 'auto':
self.borderless = self._fake_fullscreen = 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 self.fullscreen == 'fake' or self.borderless:
Logger.debug('WinPygame: Set window to borderless mode.')
self.flags |= pygame.NOFRAME
# If no position set in borderless mode, we always need
# to set the position. So use 0, 0.
if self._pos is None:
self._pos = (0, 0)
environ['SDL_VIDEO_WINDOW_POS'] = '%d,%d' % self._pos
elif self.fullscreen in ('auto', True):
Logger.debug('WinPygame: Set window to fullscreen mode')
self.flags |= pygame.FULLSCREEN
elif self._pos is not None:
environ['SDL_VIDEO_WINDOW_POS'] = '%d,%d' % self._pos
# never stay with a None pos, application using w.center will be fired.
self._pos = (0, 0)
# prepare keyboard
repeat_delay = int(Config.get('kivy', 'keyboard_repeat_delay'))
repeat_rate = float(Config.get('kivy', 'keyboard_repeat_rate'))
pygame.key.set_repeat(repeat_delay, int(1000. / repeat_rate))
# 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')
# try to use mode with multisamples
try:
self._pygame_set_mode()
except pygame.error as e:
if multisamples:
Logger.warning('WinPygame: Video: failed (multisamples=%d)' %
multisamples)
Logger.warning('WinPygame: trying without antialiasing')
pygame.display.gl_set_attribute(
pygame.GL_MULTISAMPLEBUFFERS, 0)
pygame.display.gl_set_attribute(
pygame.GL_MULTISAMPLESAMPLES, 0)
multisamples = 0
try:
self._pygame_set_mode()
except pygame.error as e:
raise CoreCriticalException(e.message)
else:
raise CoreCriticalException(e.message)
if pygame.RESIZABLE & self.flags:
self._pygame_set_mode()
info = pygame.display.Info()
self._size = (info.current_w, info.current_h)
# self.dispatch('on_resize', *self._size)
# in order to debug futur issue with pygame/display, let's show
# more debug output.
Logger.debug('Window: Display driver ' + pygame.display.get_driver())
Logger.debug('Window: Actual window size: %dx%d',
info.current_w, info.current_h)
if platform != 'android':
# unsupported platform, such as android that doesn't support
# gl_get_attribute.
Logger.debug(
'Window: Actual color bits r%d g%d b%d a%d',
pygame.display.gl_get_attribute(pygame.GL_RED_SIZE),
pygame.display.gl_get_attribute(pygame.GL_GREEN_SIZE),
pygame.display.gl_get_attribute(pygame.GL_BLUE_SIZE),
pygame.display.gl_get_attribute(pygame.GL_ALPHA_SIZE))
Logger.debug(
'Window: Actual depth bits: %d',
pygame.display.gl_get_attribute(pygame.GL_DEPTH_SIZE))
Logger.debug(
'Window: Actual stencil bits: %d',
pygame.display.gl_get_attribute(pygame.GL_STENCIL_SIZE))
Logger.debug(
'Window: Actual multisampling samples: %d',
pygame.display.gl_get_attribute(pygame.GL_MULTISAMPLESAMPLES))
super(WindowPygame, self).create_window()
# set mouse visibility
self._set_cursor_state(self.show_cursor)
# if we are on android platform, automatically create hooks
if android:
from kivy.support import install_android
install_android()
def close(self):
pygame.display.quit()
super(WindowPygame, self).close()
def on_title(self, instance, value):
if self.initialized:
pygame.display.set_caption(self.title)
def set_icon(self, filename):
if not exists(filename):
return False
try:
if platform == 'win':
try:
if self._set_icon_win(filename):
return True
except:
# fallback on standard loading then.
pass
# for all others platform, or if the ico is not available, use the
# default way to set it.
self._set_icon_standard(filename)
super(WindowPygame, self).set_icon(filename)
except:
Logger.exception('WinPygame: unable to set icon')
def _set_icon_standard(self, filename):
if PY2:
try:
im = pygame.image.load(filename)
except UnicodeEncodeError:
im = pygame.image.load(filename.encode('utf8'))
else:
im = pygame.image.load(filename)
if im is None:
raise Exception('Unable to load window icon (not found)')
pygame.display.set_icon(im)
def _set_icon_win(self, filename):
# ensure the window ico is ended by ico
if not filename.endswith('.ico'):
filename = '{}.ico'.format(filename.rsplit('.', 1)[0])
if not exists(filename):
return False
import win32api
import win32gui
import win32con
hwnd = pygame.display.get_wm_info()['window']
icon_big = win32gui.LoadImage(
None, filename, win32con.IMAGE_ICON,
48, 48, win32con.LR_LOADFROMFILE)
icon_small = win32gui.LoadImage(
None, filename, win32con.IMAGE_ICON,
16, 16, win32con.LR_LOADFROMFILE)
win32api.SendMessage(
hwnd, win32con.WM_SETICON, win32con.ICON_SMALL, icon_small)
win32api.SendMessage(
hwnd, win32con.WM_SETICON, win32con.ICON_BIG, icon_big)
return True
def _set_cursor_state(self, value):
pygame.mouse.set_visible(value)
def screenshot(self, *largs, **kwargs):
global glReadPixels, GL_RGBA, GL_UNSIGNED_BYTE
filename = super(WindowPygame, self).screenshot(*largs, **kwargs)
if filename is None:
return None
if glReadPixels is None:
from kivy.graphics.opengl import (glReadPixels, GL_RGBA,
GL_UNSIGNED_BYTE)
width, height = self.system_size
data = glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE)
data = bytes(bytearray(data))
surface = pygame.image.fromstring(data, (width, height), 'RGBA', True)
pygame.image.save(surface, filename)
Logger.debug('Window: Screenshot saved at <%s>' % filename)
return filename
def flip(self):
pygame.display.flip()
super(WindowPygame, self).flip()
@deprecated
def toggle_fullscreen(self):
if self.flags & pygame.FULLSCREEN:
self.flags &= ~pygame.FULLSCREEN
else:
self.flags |= pygame.FULLSCREEN
self._pygame_set_mode()
def mainloop(self):
for event in pygame.event.get():
# kill application (SIG_TERM)
if event.type == pygame.QUIT:
if self.dispatch('on_request_close'):
continue
EventLoop.quit = True
self.close()
# mouse move
elif event.type == pygame.MOUSEMOTION:
x, y = event.pos
self.mouse_pos = x, self.system_size[1] - y
# don't dispatch motion if no button are pressed
if event.buttons == (0, 0, 0):
continue
self._mouse_x = x
self._mouse_y = y
self._mouse_meta = self.modifiers
self.dispatch('on_mouse_move', x, y, self.modifiers)
# mouse action
elif event.type in (pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP):
self._pygame_update_modifiers()
x, y = event.pos
btn = 'left'
if event.button == 3:
btn = 'right'
elif event.button == 2:
btn = 'middle'
elif event.button == 4:
btn = 'scrolldown'
elif event.button == 5:
btn = 'scrollup'
elif event.button == 6:
btn = 'scrollright'
elif event.button == 7:
btn = 'scrollleft'
eventname = 'on_mouse_down'
if event.type == pygame.MOUSEBUTTONUP:
eventname = 'on_mouse_up'
self._mouse_x = x
self._mouse_y = y
self._mouse_meta = self.modifiers
self._mouse_btn = btn
self._mouse_down = eventname == 'on_mouse_down'
self.dispatch(eventname, x, y, btn, self.modifiers)
# joystick action
elif event.type == pygame.JOYAXISMOTION:
self.dispatch('on_joy_axis', event.joy, event.axis,
event.value)
elif event.type == pygame.JOYHATMOTION:
self.dispatch('on_joy_hat', event.joy, event.hat, event.value)
elif event.type == pygame.JOYBALLMOTION:
self.dispatch('on_joy_ball', event.joy, event.ballid,
event.rel[0], event.rel[1])
elif event.type == pygame.JOYBUTTONDOWN:
self.dispatch('on_joy_button_down', event.joy, event.button)
elif event.type == pygame.JOYBUTTONUP:
self.dispatch('on_joy_button_up', event.joy, event.button)
# keyboard action
elif event.type in (pygame.KEYDOWN, pygame.KEYUP):
self._pygame_update_modifiers(event.mod)
# atm, don't handle keyup
if event.type == pygame.KEYUP:
self.dispatch('on_key_up', event.key,
event.scancode)
continue
# don't dispatch more key if down event is accepted
if self.dispatch('on_key_down', event.key,
event.scancode, event.unicode,
self.modifiers):
continue
self.dispatch('on_keyboard', event.key,
event.scancode, event.unicode,
self.modifiers)
# video resize
elif event.type == pygame.VIDEORESIZE:
self._size = event.size
self.update_viewport()
elif event.type == pygame.VIDEOEXPOSE:
self.canvas.ask_update()
# ignored event
elif event.type == pygame.ACTIVEEVENT:
pass
# drop file (pygame patch needed)
elif event.type == pygame.USEREVENT and \
hasattr(pygame, 'USEREVENT_DROPFILE') and \
event.code == pygame.USEREVENT_DROPFILE:
drop_x, drop_y = pygame.mouse.get_pos()
self.dispatch('on_drop_file', event.filename, drop_x, drop_y)
'''
# unhandled event !
else:
Logger.debug('WinPygame: Unhandled event %s' % str(event))
'''
if not pygame.display.get_active():
pygame.time.wait(100)
#
# Pygame wrapper
#
def _pygame_set_mode(self, size=None):
if size is None:
size = self.size
if self.fullscreen == 'auto':
pygame.display.set_mode((0, 0), self.flags)
else:
pygame.display.set_mode(size, self.flags)
def _pygame_update_modifiers(self, mods=None):
# Available mod, from dir(pygame)
# 'KMOD_ALT', 'KMOD_CAPS', 'KMOD_CTRL', 'KMOD_LALT',
# 'KMOD_LCTRL', 'KMOD_LMETA', 'KMOD_LSHIFT', 'KMOD_META',
# 'KMOD_MODE', 'KMOD_NONE'
if mods is None:
mods = pygame.key.get_mods()
self._modifiers = []
if mods & (pygame.KMOD_SHIFT | pygame.KMOD_LSHIFT):
self._modifiers.append('shift')
if mods & (pygame.KMOD_ALT | pygame.KMOD_LALT):
self._modifiers.append('alt')
if mods & (pygame.KMOD_CTRL | pygame.KMOD_LCTRL):
self._modifiers.append('ctrl')
if mods & (pygame.KMOD_META | pygame.KMOD_LMETA):
self._modifiers.append('meta')
def request_keyboard(
self, callback, target, input_type='text', keyboard_suggestions=True
):
keyboard = super(WindowPygame, self).request_keyboard(
callback, target, input_type, keyboard_suggestions)
if android and not self.allow_vkeyboard:
android.show_keyboard(target, input_type)
return keyboard
def release_keyboard(self, *largs):
super(WindowPygame, self).release_keyboard(*largs)
if android:
android.hide_keyboard()
return True

View File

@@ -0,0 +1,986 @@
# 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)

Binary file not shown.