예제 #1
0
	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
예제 #2
0
	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
예제 #3
0
	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])
예제 #4
0
	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
예제 #5
0
    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
예제 #6
0
    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 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)
예제 #8
0
		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
예제 #9
0
 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
예제 #10
0
	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
예제 #11
0
    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
예제 #12
0
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()
예제 #13
0
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()
예제 #14
0
    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
예제 #15
0
	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
예제 #16
0
	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
    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
예제 #18
0
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)
예제 #19
0
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)
예제 #20
0
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
예제 #21
0
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