def load_osk(self): cbStickAction = self.builder.get_object("cbStickAction") cbTriggersAction = self.builder.get_object("cbTriggersAction") profile = Profile(GuiActionParser()) profile.load(find_profile(OSDKeyboard.OSK_PROF_NAME)) self._recursing = True # Load triggers triggers = "%s|%s" % ( profile.triggers[LEFT].to_string(), profile.triggers[RIGHT].to_string() ) if not self.set_cb(cbTriggersAction, triggers, keyindex=1): self.add_custom(cbTriggersAction, triggers) # Load stick if not self.set_cb(cbStickAction, profile.stick.to_string(), keyindex=1): self.add_custom(cbStickAction, profile.stick.to_string()) # Load sensitivity s = profile.pads[LEFT].compress().speed self.builder.get_object("sclSensX").set_value(s[0]) self.builder.get_object("sclSensY").set_value(s[1]) self._recursing = False
def enable_osd_mode(self): # TODO: Support for multiple controllers here self.osd_mode_controller = 0 osd_mode_profile = Profile(GuiActionParser()) osd_mode_profile.load(find_profile(App.OSD_MODE_PROF_NAME)) try: c = self.dm.get_controllers()[self.osd_mode_controller] except IndexError: log.error("osd_mode: Controller not connected") self.quit() return def on_lock_failed(*a): log.error("osd_mode: Locking failed") self.quit() def on_lock_success(*a): log.debug("osd_mode: Locked everything") from scc.gui.osd_mode_mapper import OSDModeMapper self.osd_mode_mapper = OSDModeMapper(osd_mode_profile) self.osd_mode_mapper.set_target_window(self.window.get_window()) GLib.timeout_add(10, self.osd_mode_mapper.run_scheduled) # Locks everything but pads. Pads are emulating mouse and this is # better left in daemon - involving socket in mouse controls # adds too much lags. c.lock(on_lock_success, on_lock_failed, 'A', 'B', 'X', 'Y', 'START', 'BACK', 'LB', 'RB', 'C', 'LPAD', 'RPAD', 'STICK', 'LGRIP', 'RGRIP', 'LT', 'RT', 'STICKPRESS') # Ask daemon to temporaly reconfigure pads for mouse emulation c.replace(DaemonManager.nocallback, on_lock_failed, LEFT, osd_mode_profile.pads[LEFT]) c.replace(DaemonManager.nocallback, on_lock_failed, RIGHT, osd_mode_profile.pads[RIGHT])
def _load_osk_profile(self): """ Loads and returns on-screen keyboard profile. Used by methods that are changing it. """ profile = Profile(GuiActionParser()) profile.load(find_profile(OSDKeyboard.OSK_PROF_NAME)) return profile
def load_profile(self, giofile): """ Loads profile from 'giofile' into 'profile' object Calls on_profiles_loaded when done """ # This may get asynchronous later, but that load runs under 1ms... profile = Profile(GuiActionParser()) profile.load(giofile.get_path()) self.on_profile_loaded(profile, giofile)
def export_profile(tar, filename): profile = Profile(TalkingActionParser()) try: out = tempfile.NamedTemporaryFile() profile.load(filename) profile.save(out.name) tar.add(out.name, arcname=os.path.split(filename)[-1], recursive=False) except Exception, e: # Profile that cannot be parsed shouldn't be exported log.error(e) return False
def _export(self, giofile, target_filename): """ Performs actual exporting. This method is used when only profile with no referenced files is to be exported and works pretty simple - load, parse, save in new file. """ profile = Profile(TalkingActionParser()) try: profile.load(giofile.get_path()) except Exception, e: # Profile that cannot be parsed shouldn't be exported log.error(e) return False
class OSKBindingEditor(Editor, BindingEditor): GLADE = "osk_binding_editor.glade" def __init__(self, app): BindingEditor.__init__(self, app) self.app = app self.gladepath = app.gladepath self.imagepath = app.imagepath self.current = Profile(GuiActionParser()) self.current.load(find_profile(OSDKeyboard.OSK_PROF_NAME)) self.setup_widgets() def setup_widgets(self): Editor.setup_widgets(self) self.create_binding_buttons(use_icons=False, enable_press=False) def show_editor(self, id): if id in STICKS: ae = self.choose_editor(self.current.stick, _("Stick")) ae.set_input(STICK, self.current.stick, mode=Action.AC_OSK) ae.show(self.window) elif id in SCButtons: title = _("%s Button") % (id.name,) ae = self.choose_editor(self.current.buttons[id], title) ae.set_input(id, self.current.buttons[id], mode=Action.AC_OSK) ae.show(self.window) elif id in TRIGGERS: ae = self.choose_editor(self.current.triggers[id], _("%s Trigger") % (id,)) ae.set_input(id, self.current.triggers[id], mode=Action.AC_OSK) ae.show(self.window) def on_action_chosen(self, id, action): self.set_action(self.current, id, action) self.save_profile() def save_profile(self, *a): """ Saves osk profile from 'profile' object into 'giofile'. Calls on_profile_saved when done """ self.current.save(os.path.join(get_profiles_path(), OSDKeyboard.OSK_PROF_NAME + ".sccprofile")) # OSK reloads profile when daemon reports configuration change self.app.dm.reconfigure()
class OSKBindingEditor(Editor, BindingEditor): GLADE = "osk_binding_editor.glade" def __init__(self, app): BindingEditor.__init__(self, app) self.app = app self.gladepath = app.gladepath self.imagepath = app.imagepath self.current = Profile(GuiActionParser()) self.current.load(find_profile(OSDKeyboard.OSK_PROF_NAME)) self.setup_widgets() def setup_widgets(self): Editor.setup_widgets(self) self.create_binding_buttons(use_icons=False, enable_press=False) def show_editor(self, id): if id in STICKS: ae = self.choose_editor(self.current.stick, _("Stick")) ae.set_input(STICK, self.current.stick, mode=Action.AC_OSK) ae.show(self.window) elif id in SCButtons: title = _("%s Button") % (id.name,) ae = self.choose_editor(self.current.buttons[id], title) ae.set_input(id, self.current.buttons[id], mode=Action.AC_OSK) ae.show(self.window) elif id in TRIGGERS: ae = self.choose_editor(self.current.triggers[id], _("%s Trigger") % (id,)) ae.set_input(id, self.current.triggers[id], mode=Action.AC_OSK) ae.show(self.window) def on_action_chosen(self, id, action, mark_changed=True): self.set_action(self.current, id, action) self.save_profile() def save_profile(self, *a): """ Saves osk profile from 'profile' object into 'giofile'. Calls on_profile_saved when done """ self.current.save(os.path.join(get_profiles_path(), OSDKeyboard.OSK_PROF_NAME + ".sccprofile")) # OSK reloads profile when daemon reports configuration change self.app.dm.reconfigure()
def _add_refereced_profile(self, model, giofile, used): """ Loads profile file and recursively adds all profiles and menus referenced by it into 'package' list. Returns True on success or False if something cannot be parsed. """ # Load & parse selected profile and check every action in it profile = Profile(ActionParser()) try: profile.load(giofile.get_path()) except Exception, e: # Profile that cannot be parsed shouldn't be exported log.error(e) return False
def import_scc(self, filename): """ Imports simple, single-file scc-profile. Just loads it, checks for shell() actions and asks user to enter name. """ files = self.builder.get_object("lstImportPackage") # Load profile profile = Profile(GuiActionParser()) try: profile.load(filename) except Exception, e: # Profile cannot be parsed. Display error message and let user to quit # Error message reuses page from VDF import, because they are # basically the same log.error(e) self.error(str(e)) return
class Keyboard(OSDWindow, TimerManager): EPILOG="""Exit codes: 0 - clean exit, user closed keyboard 1 - error, invalid arguments 2 - error, failed to access sc-daemon, sc-daemon reported error or died while keyboard is displayed. 3 - erorr, failed to lock input stick, pad or button(s) """ OSK_PROF_NAME = ".scc-osd.keyboard" BUTTON_MAP = { SCButtons.A.name : Keys.KEY_ENTER, SCButtons.B.name : Keys.KEY_ESC, SCButtons.LB.name : Keys.KEY_BACKSPACE, SCButtons.RB.name : Keys.KEY_SPACE, SCButtons.LGRIP.name : Keys.KEY_LEFTSHIFT, SCButtons.RGRIP.name : Keys.KEY_RIGHTALT, } def __init__(self, config=None): self.kbimage = os.path.join(get_config_path(), 'keyboard.svg') if not os.path.exists(self.kbimage): # Prefer image in ~/.config/scc, but load default one as fallback self.kbimage = os.path.join(get_share_path(), "images", 'keyboard.svg') TimerManager.__init__(self) OSDWindow.__init__(self, "osd-keyboard") self.daemon = None self.mapper = None self.keymap = Gdk.Keymap.get_default() self.keymap.connect('state-changed', self.on_keymap_state_changed) Action.register_all(sys.modules['scc.osd.osk_actions'], prefix="OSK") self.profile = Profile(TalkingActionParser()) self.config = config or Config() self.dpy = X.Display(hash(GdkX11.x11_get_default_xdisplay())) self.group = None self.limits = {} self.background = None cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg') self.cursors = {} self.cursors[LEFT] = Gtk.Image.new_from_file(cursor) self.cursors[LEFT].set_name("osd-keyboard-cursor") self.cursors[RIGHT] = Gtk.Image.new_from_file(cursor) self.cursors[RIGHT].set_name("osd-keyboard-cursor") self.cursors[CPAD] = Gtk.Image.new_from_file(cursor) self.cursors[CPAD].set_name("osd-keyboard-cursor") self._eh_ids = [] self._controller = None self._stick = 0, 0 self._hovers = { self.cursors[LEFT]: None, self.cursors[RIGHT]: None } self._pressed = { self.cursors[LEFT]: None, self.cursors[RIGHT]: None } self._pressed_areas = {} self.c = Gtk.Box() self.c.set_name("osd-keyboard-container") self.f = Gtk.Fixed() def _create_background(self): self.background = KeyboardImage(self.args.image) self.recolor() self.limits = {} self.limits[LEFT] = self.background.get_limit("LIMIT_LEFT") self.limits[RIGHT] = self.background.get_limit("LIMIT_RIGHT") self.limits[CPAD] = self.background.get_limit("LIMIT_CPAD") self._pack() def _pack(self): self.f.add(self.background) self.f.add(self.cursors[LEFT]) self.f.add(self.cursors[RIGHT]) self.f.add(self.cursors[CPAD]) self.c.add(self.f) self.add(self.c) def recolor(self): # TODO: keyboard description is probably not needed anymore _get = lambda a: SVGWidget.color_to_float(self.config['osk_colors'].get(a, "")) self.background.color_button1 = _get("button1") self.background.color_button1_border = _get("button1_border") self.background.color_button2 = _get("button2") self.background.color_button2_border = _get("button2_border") self.background.color_hilight = _get("hilight") self.background.color_pressed = _get("pressed") self.background.color_text = _get("text") def use_daemon(self, d): """ Allows (re)using already existing DaemonManager instance in same process """ self.daemon = d self._cononect_handlers() self.on_daemon_connected(self.daemon) def on_keymap_state_changed(self, x11keymap): if not self.timer_active('labels'): self.timer('labels', 0.1, self.update_labels) def set_help(self): """ Updates help shown on keyboard image. Keyboard bindings don't change on the fly, so this is done only right after start or when daemon is reconfigured. """ if self._controller is None: # Not yet connected return gui_config = self._controller.load_gui_config(os.path.join(get_share_path(), "images")) l_lines, r_lines, used = [], [], set() def add_action(side, button, a): if not a: return if isinstance(a, scc.osd.osk_actions.OSKCursorAction): if a.side != CPAD: return if isinstance(a, ModeModifier): for x in a.get_child_actions(): add_action(side, button, x) return desc = a.describe(Action.AC_OSK) if desc in used: if isinstance(a, scc.osd.osk_actions.OSKPressAction): # Special case, both triggers are set to "press a key" pass else: return icon = self._controller.get_button_name(gui_config, button) side.append(( icon, desc )) used.add(desc) def add_button(side, b): add_action(side, b, self.profile.buttons[b]) if self._controller.get_flags() & ControllerFlags.NO_GRIPS == 0: add_button(l_lines, SCButtons.LGRIP) add_button(r_lines, SCButtons.RGRIP) add_action(l_lines, SCButtons.LT, self.profile.triggers[LEFT]) add_action(r_lines, SCButtons.RT, self.profile.triggers[RIGHT]) for b in (SCButtons.LB, SCButtons.Y, SCButtons.X): add_button(l_lines, b) for b in (SCButtons.RB, SCButtons.B, SCButtons.A): add_button(r_lines, b) if self._controller.get_flags() & ControllerFlags.HAS_CPAD != 0: for lst in (l_lines, r_lines): while len(lst) > 3: lst.pop() while len(lst) < 3: lst.append((None, "")) add_action(r_lines, CPAD, self.profile.pads[CPAD]) add_action(l_lines, SCButtons.STICKPRESS, self.profile.stick) self.background.set_help(l_lines, r_lines) def update_labels(self): """ Updates keyboard labels based on active X keymap """ labels = {} # Get current layout group self.group = X.get_xkb_state(self.dpy).group # Get state of shift/alt/ctrl key mt = Gdk.ModifierType(self.keymap.get_modifier_state()) for button in self.background.buttons: if getattr(Keys, button.name, None) in KEY_TO_KEYCODE: keycode = KEY_TO_KEYCODE[getattr(Keys, button.name)] translation = self.keymap.translate_keyboard_state(keycode, mt, self.group) if hasattr(translation, "keyval"): code = Gdk.keyval_to_unicode(translation.keyval) else: code = Gdk.keyval_to_unicode(translation[1]) if code >= 33: # Printable chars, w/out space labels[button] = unichr(code).strip() else: labels[button] = SPECIAL_KEYS.get(code) self.background.set_labels(labels) def _add_arguments(self): OSDWindow._add_arguments(self) self.argparser.add_argument('image', type=str, nargs="?", default = self.kbimage, help="keyboard image to use") def parse_argumets(self, argv): if not OSDWindow.parse_argumets(self, argv): return False return True def _cononect_handlers(self): self._eh_ids += [ ( self.daemon, self.daemon.connect('dead', self.on_daemon_died) ), ( self.daemon, self.daemon.connect('error', self.on_daemon_died) ), ( self.daemon, self.daemon.connect('reconfigured', self.on_reconfigured) ), ( self.daemon, self.daemon.connect('alive', self.on_daemon_connected) ), ] def run(self): self.daemon = DaemonManager() self._cononect_handlers() OSDWindow.run(self) def load_profile(self): self.profile.load(find_profile(Keyboard.OSK_PROF_NAME)).compress() self.set_help() def on_reconfigured(self, *a): self.load_profile() log.debug("Reloaded profile") def on_daemon_connected(self, *a): def success(*a): log.info("Sucessfully locked input") pass c = self.choose_controller(self.daemon) if c is None or not c.is_connected(): # There is no controller connected to daemon self.on_failed_to_lock("Controller not connected") return self._eh_ids += [ (c, c.connect('event', self.on_event)), (c, c.connect('lost', self.on_controller_lost)), ] # TODO: Single-handed mode for PS4 posponed locks = [ LEFT, RIGHT, STICK, "STICKPRESS" ] + [ b.name for b in SCButtons ] if (c.get_flags() & ControllerFlags.HAS_CPAD) == 0: # Two pads, two hands locks = [ LEFT, RIGHT, STICK, "STICKPRESS" ] + [ b.name for b in SCButtons ] self.cursors[CPAD].hide() else: # Single-handed mode locks = [ CPAD, "CPADPRESS", STICK, "STICKPRESS" ] + [ b.name for b in SCButtons ] self._hovers[self.cursors[RIGHT]] = None self._hovers = { self.cursors[CPAD] : None } self._pressed = { self.cursors[CPAD] : None } self.cursors[LEFT].hide() self.cursors[RIGHT].hide() # There is no configurable nor default mapping for CPDAD, # so situable mappings are hardcoded here self.profile.pads[CPAD] = scc.osd.osk_actions.OSKCursorAction(CPAD) self.profile.pads[CPAD].speed = [ 0.85, 1.2 ] self.profile.buttons[SCButtons.CPADPRESS] = scc.osd.osk_actions.OSKPressAction(CPAD) for i in (LEFT, RIGHT): if isinstance(self.profile.triggers[i], scc.osd.osk_actions.OSKPressAction): self.profile.triggers[i] = scc.osd.osk_actions.OSKPressAction(CPAD) self._controller = c c.lock(success, self.on_failed_to_lock, *locks) self.set_help() def quit(self, code=-1): if self.get_controller(): self.get_controller().unlock_all() for source, eid in self._eh_ids: source.disconnect(eid) self._eh_ids = [] del self.mapper OSDWindow.quit(self, code) def show(self, *a): if self.background is None: self._create_background() OSDWindow.show(self, *a) self.load_profile() self.mapper = SlaveMapper(self.profile, None, keyboard=b"SCC OSD Keyboard", mouse=b"SCC OSD Mouse") self.mapper.set_special_actions_handler(self) self.set_cursor_position(0, 0, self.cursors[LEFT], self.limits[LEFT]) self.set_cursor_position(0, 0, self.cursors[RIGHT], self.limits[RIGHT]) self.set_cursor_position(0, 0, self.cursors[CPAD], self.limits[CPAD]) self.timer('labels', 0.1, self.update_labels) def on_event(self, daemon, what, data): """ Called when button press, button release or stick / pad update is send by daemon. """ group = X.get_xkb_state(self.dpy).group if self.group != group: self.group = group self.timer('labels', 0.1, self.update_labels) self.mapper.handle_event(daemon, what, data) def on_sa_close(self, *a): """ Called by CloseOSDKeyboardAction """ self.quit(0) def on_sa_cursor(self, mapper, action, x, y): self.set_cursor_position( x * action.speed[0], y * action.speed[1], self.cursors[action.side], self.limits[action.side]) def on_sa_move(self, mapper, action, x, y): self._stick = x, y if not self.timer_active('stick'): self.timer("stick", 0.05, self._move_window) def on_sa_press(self, mapper, action, pressed): self.key_from_cursor(self.cursors[action.side], pressed) def set_cursor_position(self, x, y, cursor, limit): """ Moves cursor image. """ if cursor not in self._hovers: return w = limit[2] - (cursor.get_allocation().width * 0.5) h = limit[3] - (cursor.get_allocation().height * 0.5) x = x / float(STICK_PAD_MAX) y = y / float(STICK_PAD_MAX) * -1.0 x, y = circle_to_square(x, y) x = clamp( cursor.get_allocation().width * 0.5, (limit[0] + w * 0.5) + x * w * 0.5, self.get_allocation().width - cursor.get_allocation().width ) y = clamp( cursor.get_allocation().height * 0.5, (limit[1] + h * 0.5) + y * h * 0.5, self.get_allocation().height - cursor.get_allocation().height ) cursor.position = int(x), int(y) self.f.move(cursor, x - cursor.get_allocation().width * 0.5, y - cursor.get_allocation().height * 0.5) for button in self.background.buttons: if button.contains(x, y): if button != self._hovers[cursor]: self._hovers[cursor] = button if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self.key_from_cursor(cursor, True) if not self.timer_active('update'): self.timer('update', 0.01, self.update_background) break def update_background(self, *whatever): """ Updates hilighted keys on bacgkround image. """ self.background.hilight( set([ a for a in self._hovers.values() if a ]), set([ a for a in self._pressed_areas.values() if a ]) ) def _move_window(self, *a): """ Called by timer while stick is tilted to move window around the screen. """ x, y = self._stick x = x * 50.0 / STICK_PAD_MAX y = y * -50.0 / STICK_PAD_MAX rx, ry = self.get_position() self.move(rx + x, ry + y) if abs(self._stick[0]) > 100 or abs(self._stick[1]) > 100: self.timer("stick", 0.05, self._move_window) def key_from_cursor(self, cursor, pressed): """ Sends keypress/keyrelease event to emulated keyboard, based on position of cursor on OSD keyboard. """ x, y = cursor.position if pressed: for button in self.background.buttons: if button.contains(x, y): if button.name.startswith("KEY_") and hasattr(Keys, button.name): key = getattr(Keys, button.name) if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self.mapper.keyboard.pressEvent([ key ]) self._pressed[cursor] = key self._pressed_areas[cursor] = button break elif self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self._pressed[cursor] = None del self._pressed_areas[cursor] if not self.timer_active('update'): self.timer('update', 0.01, self.update_background)
class Keyboard(OSDWindow, TimerManager): EPILOG = """Exit codes: 0 - clean exit, user closed keyboard 1 - error, invalid arguments 2 - error, failed to access sc-daemon, sc-daemon reported error or died while keyboard is displayed. 3 - erorr, failed to lock input stick, pad or button(s) """ OSK_PROF_NAME = ".scc-osd.keyboard" BUTTON_MAP = { SCButtons.A.name: Keys.KEY_ENTER, SCButtons.B.name: Keys.KEY_ESC, SCButtons.LB.name: Keys.KEY_BACKSPACE, SCButtons.RB.name: Keys.KEY_SPACE, SCButtons.LGRIP.name: Keys.KEY_LEFTSHIFT, SCButtons.RGRIP.name: Keys.KEY_RIGHTALT, } def __init__(self, config=None): self.kbimage = os.path.join(get_config_path(), 'keyboard.svg') if not os.path.exists(self.kbimage): # Prefer image in ~/.config/scc, but load default one as fallback self.kbimage = os.path.join(get_share_path(), "images", 'keyboard.svg') TimerManager.__init__(self) OSDWindow.__init__(self, "osd-keyboard") self.daemon = None self.mapper = None self.keymap = Gdk.Keymap.get_default() self.keymap.connect('state-changed', self.on_keymap_state_changed) Action.register_all(sys.modules['scc.osd.osk_actions'], prefix="OSK") self.profile = Profile(TalkingActionParser()) self.config = config or Config() self.dpy = X.Display(hash(GdkX11.x11_get_default_xdisplay())) self.group = None self.limits = {} self.background = None cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg') self.cursors = {} self.cursors[LEFT] = Gtk.Image.new_from_file(cursor) self.cursors[LEFT].set_name("osd-keyboard-cursor") self.cursors[RIGHT] = Gtk.Image.new_from_file(cursor) self.cursors[RIGHT].set_name("osd-keyboard-cursor") self.cursors[CPAD] = Gtk.Image.new_from_file(cursor) self.cursors[CPAD].set_name("osd-keyboard-cursor") self._eh_ids = [] self._controller = None self._stick = 0, 0 self._hovers = {self.cursors[LEFT]: None, self.cursors[RIGHT]: None} self._pressed = {self.cursors[LEFT]: None, self.cursors[RIGHT]: None} self._pressed_areas = {} self.c = Gtk.Box() self.c.set_name("osd-keyboard-container") self.f = Gtk.Fixed() def _create_background(self): self.background = KeyboardImage(self.args.image) self.recolor() self.limits = {} self.limits[LEFT] = self.background.get_limit("LIMIT_LEFT") self.limits[RIGHT] = self.background.get_limit("LIMIT_RIGHT") self.limits[CPAD] = self.background.get_limit("LIMIT_CPAD") self._pack() def _pack(self): self.f.add(self.background) self.f.add(self.cursors[LEFT]) self.f.add(self.cursors[RIGHT]) self.f.add(self.cursors[CPAD]) self.c.add(self.f) self.add(self.c) def recolor(self): # TODO: keyboard description is probably not needed anymore _get = lambda a: SVGWidget.color_to_float(self.config['osk_colors']. get(a, "")) self.background.color_button1 = _get("button1") self.background.color_button1_border = _get("button1_border") self.background.color_button2 = _get("button2") self.background.color_button2_border = _get("button2_border") self.background.color_hilight = _get("hilight") self.background.color_pressed = _get("pressed") self.background.color_text = _get("text") def use_daemon(self, d): """ Allows (re)using already existing DaemonManager instance in same process """ self.daemon = d self._cononect_handlers() self.on_daemon_connected(self.daemon) def on_keymap_state_changed(self, x11keymap): if not self.timer_active('labels'): self.timer('labels', 0.1, self.update_labels) def set_help(self): """ Updates help shown on keyboard image. Keyboard bindings don't change on the fly, so this is done only right after start or when daemon is reconfigured. """ if self._controller is None: # Not yet connected return gui_config = self._controller.load_gui_config( os.path.join(get_share_path(), "images")) l_lines, r_lines, used = [], [], set() def add_action(side, button, a): if not a: return if isinstance(a, scc.osd.osk_actions.OSKCursorAction): if a.side != CPAD: return if isinstance(a, ModeModifier): for x in a.get_child_actions(): add_action(side, button, x) return desc = a.describe(Action.AC_OSK) if desc in used: if isinstance(a, scc.osd.osk_actions.OSKPressAction): # Special case, both triggers are set to "press a key" pass else: return icon = self._controller.get_button_name(gui_config, button) side.append((icon, desc)) used.add(desc) def add_button(side, b): add_action(side, b, self.profile.buttons[b]) if self._controller.get_flags() & ControllerFlags.NO_GRIPS == 0: add_button(l_lines, SCButtons.LGRIP) add_button(r_lines, SCButtons.RGRIP) add_action(l_lines, SCButtons.LT, self.profile.triggers[LEFT]) add_action(r_lines, SCButtons.RT, self.profile.triggers[RIGHT]) for b in (SCButtons.LB, SCButtons.Y, SCButtons.X): add_button(l_lines, b) for b in (SCButtons.RB, SCButtons.B, SCButtons.A): add_button(r_lines, b) if self._controller.get_flags() & ControllerFlags.HAS_CPAD != 0: for lst in (l_lines, r_lines): while len(lst) > 3: lst.pop() while len(lst) < 3: lst.append((None, "")) add_action(r_lines, CPAD, self.profile.pads[CPAD]) add_action(l_lines, SCButtons.STICKPRESS, self.profile.stick) self.background.set_help(l_lines, r_lines) def update_labels(self): """ Updates keyboard labels based on active X keymap """ labels = {} # Get current layout group self.group = X.get_xkb_state(self.dpy).group # Get state of shift/alt/ctrl key mt = Gdk.ModifierType(self.keymap.get_modifier_state()) for button in self.background.buttons: if getattr(Keys, button.name, None) in KEY_TO_KEYCODE: keycode = KEY_TO_KEYCODE[getattr(Keys, button.name)] translation = self.keymap.translate_keyboard_state( keycode, mt, self.group) if hasattr(translation, "keyval"): code = Gdk.keyval_to_unicode(translation.keyval) else: code = Gdk.keyval_to_unicode(translation[1]) if code >= 33: # Printable chars, w/out space labels[button] = unichr(code).strip() else: labels[button] = SPECIAL_KEYS.get(code) self.background.set_labels(labels) def _add_arguments(self): OSDWindow._add_arguments(self) self.argparser.add_argument('image', type=str, nargs="?", default=self.kbimage, help="keyboard image to use") def parse_argumets(self, argv): if not OSDWindow.parse_argumets(self, argv): return False return True def _cononect_handlers(self): self._eh_ids += [ (self.daemon, self.daemon.connect('dead', self.on_daemon_died)), (self.daemon, self.daemon.connect('error', self.on_daemon_died)), (self.daemon, self.daemon.connect('reconfigured', self.on_reconfigured)), (self.daemon, self.daemon.connect('alive', self.on_daemon_connected)), ] def run(self): self.daemon = DaemonManager() self._cononect_handlers() OSDWindow.run(self) def load_profile(self): self.profile.load(find_profile(Keyboard.OSK_PROF_NAME)).compress() self.set_help() def on_reconfigured(self, *a): self.load_profile() log.debug("Reloaded profile") def on_daemon_connected(self, *a): def success(*a): log.info("Sucessfully locked input") pass c = self.choose_controller(self.daemon) if c is None or not c.is_connected(): # There is no controller connected to daemon self.on_failed_to_lock("Controller not connected") return self._eh_ids += [ (c, c.connect('event', self.on_event)), (c, c.connect('lost', self.on_controller_lost)), ] # TODO: Single-handed mode for PS4 posponed locks = [LEFT, RIGHT, STICK, "STICKPRESS" ] + [b.name for b in SCButtons] if (c.get_flags() & ControllerFlags.HAS_CPAD) == 0: # Two pads, two hands locks = [LEFT, RIGHT, STICK, "STICKPRESS" ] + [b.name for b in SCButtons] self.cursors[CPAD].hide() else: # Single-handed mode locks = [CPAD, "CPADPRESS", STICK, "STICKPRESS" ] + [b.name for b in SCButtons] self._hovers[self.cursors[RIGHT]] = None self._hovers = {self.cursors[CPAD]: None} self._pressed = {self.cursors[CPAD]: None} self.cursors[LEFT].hide() self.cursors[RIGHT].hide() # There is no configurable nor default mapping for CPDAD, # so situable mappings are hardcoded here self.profile.pads[CPAD] = scc.osd.osk_actions.OSKCursorAction(CPAD) self.profile.pads[CPAD].speed = [0.85, 1.2] self.profile.buttons[ SCButtons.CPADPRESS] = scc.osd.osk_actions.OSKPressAction(CPAD) for i in (LEFT, RIGHT): if isinstance(self.profile.triggers[i], scc.osd.osk_actions.OSKPressAction): self.profile.triggers[ i] = scc.osd.osk_actions.OSKPressAction(CPAD) self._controller = c c.lock(success, self.on_failed_to_lock, *locks) self.set_help() def quit(self, code=-1): if self.get_controller(): self.get_controller().unlock_all() for source, eid in self._eh_ids: source.disconnect(eid) self._eh_ids = [] del self.mapper OSDWindow.quit(self, code) def show(self, *a): if self.background is None: self._create_background() OSDWindow.show(self, *a) self.load_profile() self.mapper = SlaveMapper(self.profile, None, keyboard=b"SCC OSD Keyboard", mouse=b"SCC OSD Mouse") self.mapper.set_special_actions_handler(self) self.set_cursor_position(0, 0, self.cursors[LEFT], self.limits[LEFT]) self.set_cursor_position(0, 0, self.cursors[RIGHT], self.limits[RIGHT]) self.set_cursor_position(0, 0, self.cursors[CPAD], self.limits[CPAD]) self.timer('labels', 0.1, self.update_labels) def on_event(self, daemon, what, data): """ Called when button press, button release or stick / pad update is send by daemon. """ group = X.get_xkb_state(self.dpy).group if self.group != group: self.group = group self.timer('labels', 0.1, self.update_labels) self.mapper.handle_event(daemon, what, data) def on_sa_close(self, *a): """ Called by CloseOSDKeyboardAction """ self.quit(0) def on_sa_cursor(self, mapper, action, x, y): self.set_cursor_position(x * action.speed[0], y * action.speed[1], self.cursors[action.side], self.limits[action.side]) def on_sa_move(self, mapper, action, x, y): self._stick = x, y if not self.timer_active('stick'): self.timer("stick", 0.05, self._move_window) def on_sa_press(self, mapper, action, pressed): self.key_from_cursor(self.cursors[action.side], pressed) def set_cursor_position(self, x, y, cursor, limit): """ Moves cursor image. """ if cursor not in self._hovers: return w = limit[2] - (cursor.get_allocation().width * 0.5) h = limit[3] - (cursor.get_allocation().height * 0.5) x = x / float(STICK_PAD_MAX) y = y / float(STICK_PAD_MAX) * -1.0 x, y = circle_to_square(x, y) x = clamp(cursor.get_allocation().width * 0.5, (limit[0] + w * 0.5) + x * w * 0.5, self.get_allocation().width - cursor.get_allocation().width) y = clamp( cursor.get_allocation().height * 0.5, (limit[1] + h * 0.5) + y * h * 0.5, self.get_allocation().height - cursor.get_allocation().height) cursor.position = int(x), int(y) self.f.move(cursor, x - cursor.get_allocation().width * 0.5, y - cursor.get_allocation().height * 0.5) for button in self.background.buttons: if button.contains(x, y): if button != self._hovers[cursor]: self._hovers[cursor] = button if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent( [self._pressed[cursor]]) self.key_from_cursor(cursor, True) if not self.timer_active('update'): self.timer('update', 0.01, self.update_background) break def update_background(self, *whatever): """ Updates hilighted keys on bacgkround image. """ self.background.hilight( set([a for a in self._hovers.values() if a]), set([a for a in self._pressed_areas.values() if a])) def _move_window(self, *a): """ Called by timer while stick is tilted to move window around the screen. """ x, y = self._stick x = x * 50.0 / STICK_PAD_MAX y = y * -50.0 / STICK_PAD_MAX rx, ry = self.get_position() self.move(rx + x, ry + y) if abs(self._stick[0]) > 100 or abs(self._stick[1]) > 100: self.timer("stick", 0.05, self._move_window) def key_from_cursor(self, cursor, pressed): """ Sends keypress/keyrelease event to emulated keyboard, based on position of cursor on OSD keyboard. """ x, y = cursor.position if pressed: for button in self.background.buttons: if button.contains(x, y): if button.name.startswith("KEY_") and hasattr( Keys, button.name): key = getattr(Keys, button.name) if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent( [self._pressed[cursor]]) self.mapper.keyboard.pressEvent([key]) self._pressed[cursor] = key self._pressed_areas[cursor] = button break elif self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([self._pressed[cursor]]) self._pressed[cursor] = None del self._pressed_areas[cursor] if not self.timer_active('update'): self.timer('update', 0.01, self.update_background)
class Keyboard(OSDWindow, TimerManager): EPILOG="""Exit codes: 0 - clean exit, user closed keyboard 1 - error, invalid arguments 2 - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed. 3 - erorr, failed to lock input stick, pad or button(s) """ HILIGHT_COLOR = "#00688D" BUTTON_MAP = { SCButtons.A.name : Keys.KEY_ENTER, SCButtons.B.name : Keys.KEY_ESC, SCButtons.LB.name : Keys.KEY_BACKSPACE, SCButtons.RB.name : Keys.KEY_SPACE, SCButtons.LGRIP.name : Keys.KEY_LEFTSHIFT, SCButtons.RGRIP.name : Keys.KEY_RIGHTALT, } def __init__(self): OSDWindow.__init__(self, "osd-keyboard") TimerManager.__init__(self) self.daemon = None self.mapper = None self.keymap = Gdk.Keymap.get_default() self.keymap.connect('state-changed', self.on_state_changed) self.profile = Profile(TalkingActionParser()) kbimage = os.path.join(get_config_path(), 'keyboard.svg') if not os.path.exists(kbimage): # Prefer image in ~/.config/scc, but load default one as fallback kbimage = os.path.join(get_share_path(), "images", 'keyboard.svg') self.background = SVGWidget(self, kbimage) self.limits = {} self.limits[LEFT] = self.background.get_rect_area(self.background.get_element("LIMIT_LEFT")) self.limits[RIGHT] = self.background.get_rect_area(self.background.get_element("LIMIT_RIGHT")) cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg') self.cursors = {} self.cursors[LEFT] = Gtk.Image.new_from_file(cursor) self.cursors[LEFT].set_name("osd-keyboard-cursor") self.cursors[RIGHT] = Gtk.Image.new_from_file(cursor) self.cursors[RIGHT].set_name("osd-keyboard-cursor") self._eh_ids = [] self._stick = 0, 0 self._hovers = { self.cursors[LEFT] : None, self.cursors[RIGHT] : None } self._pressed = { self.cursors[LEFT] : None, self.cursors[RIGHT] : None } self.c = Gtk.Box() self.c.set_name("osd-keyboard-container") self.f = Gtk.Fixed() self.f.add(self.background) self.f.add(self.cursors[LEFT]) self.f.add(self.cursors[RIGHT]) self.c.add(self.f) self.add(self.c) self.timer('labels', 0.1, self.update_labels) def use_daemon(self, d): """ Allows (re)using already existing DaemonManager instance in same process """ self.daemon = d self._cononect_handlers() self.on_daemon_connected(self.daemon) def on_state_changed(self, x11keymap): if not self.timer_active('labels'): self.timer('labels', 0.1, self.update_labels) def update_labels(self): """ Updates keyboard labels based on active X keymap """ labels = {} # Get current layout group dpy = X.Display(hash(GdkX11.x11_get_default_xdisplay())) # Still no idea why... group = X.get_xkb_state(dpy).group # Get state of shift/alt/ctrl key mt = Gdk.ModifierType(self.keymap.get_modifier_state()) for a in self.background.areas: # Iterate over all translatable keys... if hasattr(Keys, a.name) and getattr(Keys, a.name) in KEY_TO_GDK: # Try to convert GKD key to keycode gdkkey = KEY_TO_GDK[getattr(Keys, a.name)] found, entries = self.keymap.get_entries_for_keyval(gdkkey) if gdkkey == Gdk.KEY_equal: # Special case, GDK reports nonsense here entries = [ [ e for e in entries if e.level == 0 ][-1] ] if not found: continue for k in sorted(entries, key=lambda a : a.level): # Try to convert keycode to label translation = self.keymap.translate_keyboard_state(k.keycode, mt, group) if hasattr(translation, "keyval"): code = Gdk.keyval_to_unicode(translation.keyval) else: code = Gdk.keyval_to_unicode(translation[1]) if code != 0: labels[a.name] = unichr(code) break self.background.set_labels(labels) def parse_argumets(self, argv): if not OSDWindow.parse_argumets(self, argv): return False return True def _cononect_handlers(self): self._eh_ids += [ self.daemon.connect('dead', self.on_daemon_died), self.daemon.connect('error', self.on_daemon_died), self.daemon.connect('event', self.on_event), self.daemon.connect('alive', self.on_daemon_connected), ] def run(self): self.daemon = DaemonManager() self._cononect_handlers() OSDWindow.run(self) def on_daemon_died(self, *a): log.error("Daemon died") self.quit(2) def on_failed_to_lock(self, error): log.error("Failed to lock input: %s", error) self.quit(3) def on_daemon_connected(self, *a): def success(*a): log.info("Sucessfully locked input") pass # Lock everything locks = [ LEFT, RIGHT, STICK ] + [ b.name for b in SCButtons ] self.daemon.lock(success, self.on_failed_to_lock, *locks) def quit(self, code=-1): self.daemon.unlock_all() for x in self._eh_ids: self.daemon.disconnect(x) self._eh_ids = [] del self.mapper OSDWindow.quit(self, code) def show(self, *a): OSDWindow.show(self, *a) self.profile.load(find_profile(".scc-osd.keyboard")).compress() self.mapper = SlaveMapper(self.profile, keyboard=b"SCC OSD Keyboard") self.mapper.set_special_actions_handler(self) self.set_cursor_position(0, 0, self.cursors[LEFT], self.limits[LEFT]) self.set_cursor_position(0, 0, self.cursors[RIGHT], self.limits[RIGHT]) def on_event(self, daemon, what, data): """ Called when button press, button release or stick / pad update is send by daemon. """ self.mapper.handle_event(daemon, what, data) def on_sa_close(self, *a): """ Called by CloseOSDKeyboardAction """ self.quit(0) def on_sa_cursor(self, mapper, action, x, y): self.set_cursor_position( x * action.speed[0], y * action.speed[1], self.cursors[action.side], self.limits[action.side]) def on_sa_move(self, mapper, action, x, y): self._stick = x, y if not self.timer_active('stick'): self.timer("stick", 0.05, self._move_window) def on_sa_press(self, mapper, action, pressed): self.key_from_cursor(self.cursors[action.side], pressed) def set_cursor_position(self, x, y, cursor, limit): """ Moves cursor image. """ w = limit[2] - (cursor.get_allocation().width * 0.5) h = limit[3] - (cursor.get_allocation().height * 0.5) x = x / float(STICK_PAD_MAX) y = y / float(STICK_PAD_MAX) * -1.0 x, y = circle_to_square(x, y) x = clamp( cursor.get_allocation().width * 0.5, (limit[0] + w * 0.5) + x * w * 0.5, self.get_allocation().width - cursor.get_allocation().width ) - cursor.get_allocation().width * 0.5 y = clamp( cursor.get_allocation().height * 0.5, (limit[1] + h * 0.5) + y * h * 0.5, self.get_allocation().height - cursor.get_allocation().height ) - cursor.get_allocation().height * 0.5 cursor.position = int(x), int(y) self.f.move(cursor, *cursor.position) for a in self.background.areas: if a.contains(x, y): if a != self._hovers[cursor]: self._hovers[cursor] = a if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self.key_from_cursor(cursor, True) if not self.timer_active('redraw'): self.timer('redraw', 0.01, self.redraw_background) break def redraw_background(self, *a): """ Updates hilighted keys on bacgkround image. """ self.background.hilight({ "AREA_" + a.name : Keyboard.HILIGHT_COLOR for a in [ a for a in self._hovers.values() if a ] }) def _move_window(self, *a): """ Called by timer while stick is tilted to move window around the screen. """ x, y = self._stick x = x * 50.0 / STICK_PAD_MAX y = y * -50.0 / STICK_PAD_MAX rx, ry = self.get_position() self.move(rx + x, ry + y) if abs(self._stick[0]) > 100 or abs(self._stick[1]) > 100: self.timer("stick", 0.05, self._move_window) def key_from_cursor(self, cursor, pressed): """ Sends keypress/keyrelease event to emulated keyboard, based on position of cursor on OSD keyboard. """ x, y = cursor.position if pressed: for a in self.background.areas: if a.contains(x, y): if a.name.startswith("KEY_") and hasattr(Keys, a.name): key = getattr(Keys, a.name) if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self.mapper.keyboard.pressEvent([ key ]) self._pressed[cursor] = key break elif self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([ self._pressed[cursor] ]) self._pressed[cursor] = None
class Keyboard(OSDWindow, TimerManager): EPILOG = """Exit codes: 0 - clean exit, user closed keyboard 1 - error, invalid arguments 2 - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed. 3 - erorr, failed to lock input stick, pad or button(s) """ HILIGHT_COLOR = "#00688D" BUTTON_MAP = { SCButtons.A.name: Keys.KEY_ENTER, SCButtons.B.name: Keys.KEY_ESC, SCButtons.LB.name: Keys.KEY_BACKSPACE, SCButtons.RB.name: Keys.KEY_SPACE, SCButtons.LGRIP.name: Keys.KEY_LEFTSHIFT, SCButtons.RGRIP.name: Keys.KEY_RIGHTALT, } def __init__(self): OSDWindow.__init__(self, "osd-keyboard") TimerManager.__init__(self) self.daemon = None self.mapper = None self.keymap = Gdk.Keymap.get_default() self.keymap.connect('state-changed', self.on_state_changed) self.profile = Profile(TalkingActionParser()) kbimage = os.path.join(get_config_path(), 'keyboard.svg') if not os.path.exists(kbimage): # Prefer image in ~/.config/scc, but load default one as fallback kbimage = os.path.join(get_share_path(), "images", 'keyboard.svg') self.background = SVGWidget(self, kbimage) self.limits = {} self.limits[LEFT] = self.background.get_rect_area( self.background.get_element("LIMIT_LEFT")) self.limits[RIGHT] = self.background.get_rect_area( self.background.get_element("LIMIT_RIGHT")) cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg') self.cursors = {} self.cursors[LEFT] = Gtk.Image.new_from_file(cursor) self.cursors[LEFT].set_name("osd-keyboard-cursor") self.cursors[RIGHT] = Gtk.Image.new_from_file(cursor) self.cursors[RIGHT].set_name("osd-keyboard-cursor") self._eh_ids = [] self._stick = 0, 0 self._hovers = {self.cursors[LEFT]: None, self.cursors[RIGHT]: None} self._pressed = {self.cursors[LEFT]: None, self.cursors[RIGHT]: None} self.c = Gtk.Box() self.c.set_name("osd-keyboard-container") self.f = Gtk.Fixed() self.f.add(self.background) self.f.add(self.cursors[LEFT]) self.f.add(self.cursors[RIGHT]) self.c.add(self.f) self.add(self.c) self.timer('labels', 0.1, self.update_labels) def use_daemon(self, d): """ Allows (re)using already existing DaemonManager instance in same process """ self.daemon = d self._cononect_handlers() self.on_daemon_connected(self.daemon) def on_state_changed(self, x11keymap): if not self.timer_active('labels'): self.timer('labels', 0.1, self.update_labels) def update_labels(self): """ Updates keyboard labels based on active X keymap """ labels = {} # Get current layout group dpy = X.Display(hash( GdkX11.x11_get_default_xdisplay())) # Still no idea why... group = X.get_xkb_state(dpy).group # Get state of shift/alt/ctrl key mt = Gdk.ModifierType(self.keymap.get_modifier_state()) for a in self.background.areas: # Iterate over all translatable keys... if hasattr(Keys, a.name) and getattr(Keys, a.name) in KEY_TO_GDK: # Try to convert GKD key to keycode gdkkey = KEY_TO_GDK[getattr(Keys, a.name)] found, entries = self.keymap.get_entries_for_keyval(gdkkey) if gdkkey == Gdk.KEY_equal: # Special case, GDK reports nonsense here entries = [[e for e in entries if e.level == 0][-1]] if not found: continue for k in sorted(entries, key=lambda a: a.level): # Try to convert keycode to label translation = self.keymap.translate_keyboard_state( k.keycode, mt, group) if hasattr(translation, "keyval"): code = Gdk.keyval_to_unicode(translation.keyval) else: code = Gdk.keyval_to_unicode(translation[1]) if code != 0: labels[a.name] = unichr(code) break self.background.set_labels(labels) def parse_argumets(self, argv): if not OSDWindow.parse_argumets(self, argv): return False return True def _cononect_handlers(self): self._eh_ids += [ self.daemon.connect('dead', self.on_daemon_died), self.daemon.connect('error', self.on_daemon_died), self.daemon.connect('event', self.on_event), self.daemon.connect('alive', self.on_daemon_connected), ] def run(self): self.daemon = DaemonManager() self._cononect_handlers() OSDWindow.run(self) def on_daemon_died(self, *a): log.error("Daemon died") self.quit(2) def on_failed_to_lock(self, error): log.error("Failed to lock input: %s", error) self.quit(3) def on_daemon_connected(self, *a): def success(*a): log.info("Sucessfully locked input") pass # Lock everything locks = [LEFT, RIGHT, STICK] + [b.name for b in SCButtons] self.daemon.lock(success, self.on_failed_to_lock, *locks) def quit(self, code=-1): self.daemon.unlock_all() for x in self._eh_ids: self.daemon.disconnect(x) self._eh_ids = [] del self.mapper OSDWindow.quit(self, code) def show(self, *a): OSDWindow.show(self, *a) self.profile.load(find_profile(".scc-osd.keyboard")).compress() self.mapper = SlaveMapper(self.profile, keyboard=b"SCC OSD Keyboard") self.mapper.set_special_actions_handler(self) self.set_cursor_position(0, 0, self.cursors[LEFT], self.limits[LEFT]) self.set_cursor_position(0, 0, self.cursors[RIGHT], self.limits[RIGHT]) def on_event(self, daemon, what, data): """ Called when button press, button release or stick / pad update is send by daemon. """ self.mapper.handle_event(daemon, what, data) def on_sa_close(self, *a): """ Called by CloseOSDKeyboardAction """ self.quit(0) def on_sa_cursor(self, mapper, action, x, y): self.set_cursor_position(x * action.speed[0], y * action.speed[1], self.cursors[action.side], self.limits[action.side]) def on_sa_move(self, mapper, action, x, y): self._stick = x, y if not self.timer_active('stick'): self.timer("stick", 0.05, self._move_window) def on_sa_press(self, mapper, action, pressed): self.key_from_cursor(self.cursors[action.side], pressed) def set_cursor_position(self, x, y, cursor, limit): """ Moves cursor image. """ w = limit[2] - (cursor.get_allocation().width * 0.5) h = limit[3] - (cursor.get_allocation().height * 0.5) x = x / float(STICK_PAD_MAX) y = y / float(STICK_PAD_MAX) * -1.0 x, y = circle_to_square(x, y) x = clamp(cursor.get_allocation().width * 0.5, (limit[0] + w * 0.5) + x * w * 0.5, self.get_allocation().width - cursor.get_allocation().width ) - cursor.get_allocation().width * 0.5 y = clamp( cursor.get_allocation().height * 0.5, (limit[1] + h * 0.5) + y * h * 0.5, self.get_allocation().height - cursor.get_allocation().height ) - cursor.get_allocation().height * 0.5 cursor.position = int(x), int(y) self.f.move(cursor, *cursor.position) for a in self.background.areas: if a.contains(x, y): if a != self._hovers[cursor]: self._hovers[cursor] = a if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent( [self._pressed[cursor]]) self.key_from_cursor(cursor, True) if not self.timer_active('redraw'): self.timer('redraw', 0.01, self.redraw_background) break def redraw_background(self, *a): """ Updates hilighted keys on bacgkround image. """ self.background.hilight({ "AREA_" + a.name: Keyboard.HILIGHT_COLOR for a in [a for a in self._hovers.values() if a] }) def _move_window(self, *a): """ Called by timer while stick is tilted to move window around the screen. """ x, y = self._stick x = x * 50.0 / STICK_PAD_MAX y = y * -50.0 / STICK_PAD_MAX rx, ry = self.get_position() self.move(rx + x, ry + y) if abs(self._stick[0]) > 100 or abs(self._stick[1]) > 100: self.timer("stick", 0.05, self._move_window) def key_from_cursor(self, cursor, pressed): """ Sends keypress/keyrelease event to emulated keyboard, based on position of cursor on OSD keyboard. """ x, y = cursor.position if pressed: for a in self.background.areas: if a.contains(x, y): if a.name.startswith("KEY_") and hasattr(Keys, a.name): key = getattr(Keys, a.name) if self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent( [self._pressed[cursor]]) self.mapper.keyboard.pressEvent([key]) self._pressed[cursor] = key break elif self._pressed[cursor] is not None: self.mapper.keyboard.releaseEvent([self._pressed[cursor]]) self._pressed[cursor] = None