''' 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