450 lines
17 KiB
Python
450 lines
17 KiB
Python
'''
|
|
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
|