Ajout du GUI
This commit is contained in:
2460
kivy/core/window/__init__.py
Normal file
2460
kivy/core/window/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
kivy/core/window/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/core/window/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/window/__pycache__/window_egl_rpi.cpython-310.pyc
Normal file
BIN
kivy/core/window/__pycache__/window_egl_rpi.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/window/__pycache__/window_pygame.cpython-310.pyc
Normal file
BIN
kivy/core/window/__pycache__/window_pygame.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/window/__pycache__/window_sdl2.cpython-310.pyc
Normal file
BIN
kivy/core/window/__pycache__/window_sdl2.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/core/window/_window_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/core/window/_window_sdl2.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
30
kivy/core/window/window_attrs.pxi
Normal file
30
kivy/core/window/window_attrs.pxi
Normal 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
|
||||
86
kivy/core/window/window_egl_rpi.py
Normal file
86
kivy/core/window/window_egl_rpi.py
Normal 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])
|
||||
BIN
kivy/core/window/window_info.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/core/window/window_info.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
19
kivy/core/window/window_info.pxd
Normal file
19
kivy/core/window/window_info.pxd
Normal 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
|
||||
449
kivy/core/window/window_pygame.py
Normal file
449
kivy/core/window/window_pygame.py
Normal 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
|
||||
986
kivy/core/window/window_sdl2.py
Normal file
986
kivy/core/window/window_sdl2.py
Normal 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)
|
||||
BIN
kivy/core/window/window_x11.cpython-310-x86_64-linux-gnu.so
Executable file
BIN
kivy/core/window/window_x11.cpython-310-x86_64-linux-gnu.so
Executable file
Binary file not shown.
Reference in New Issue
Block a user