Beispiel #1
0
 def on_unknown_message(self, daemon, message):
     if not message.startswith("OSD:"):
         return
     if message.startswith("OSD: message"):
         args = split(message)[1:]
         m = Message()
         m.parse_argumets(args)
         m.show()
     elif message.startswith("OSD: keyboard"):
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show keyboard"
             )
         else:
             args = split(message)[1:]
             self._window = Keyboard()
             self._window.connect('destroy', self.on_keyboard_closed)
             # self._window.parse_argumets(args) # TODO: No arguments so far
             self._window.show()
             self._window.use_daemon(self.daemon)
     elif message.startswith("OSD: menu") or message.startswith(
             "OSD: gridmenu"):
         args = split(message)[1:]
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show menu")
         else:
             self._window = GridMenu() if "gridmenu" in message else Menu()
             self._window.connect('destroy', self.on_menu_closed)
             self._window.use_config(self.config)
             if self._window.parse_argumets(args):
                 self._window.show()
                 self._window.use_daemon(self.daemon)
             else:
                 log.error("Failed to show menu")
                 self._window = None
     elif message.startswith("OSD: area"):
         args = split(message)[1:]
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show area")
         else:
             args = split(message)[1:]
             self._window = Area()
             self._window.connect('destroy', self.on_keyboard_closed)
             if self._window.parse_argumets(args):
                 self._window.show()
             else:
                 self._window.quit()
                 self._window = None
     elif message.startswith("OSD: clear"):
         # Clears active OSD window (if any)
         if self._window:
             self._window.quit()
             self._window = None
     else:
         log.warning("Unknown command from daemon: '%s'", message)
Beispiel #2
0
	def on_unknown_message(self, daemon, message):
		if not message.startswith("OSD:"):
			return
		if message.startswith("OSD: message"):
			args = shsplit(message)[1:]
			m = Message()
			m.parse_argumets(args)
			m.show()
		elif message.startswith("OSD: keyboard"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = Keyboard(self.config)
				self._window.connect('destroy', self.on_keyboard_closed)
				self._window.parse_argumets(args)
				self._window.show()
				self._window.use_daemon(self.daemon)
		elif self._is_menu_message(message):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show menu")
			else:
				if message.startswith("OSD: gridmenu"):
					self._window = GridMenu()
				elif message.startswith("OSD: radialmenu"):
					self._window = RadialMenu()
				else:
					self._window = Menu()
				self._window.connect('destroy', self.on_menu_closed)
				self._window.use_config(self.config)
				if self._window.parse_argumets(args):
					self._window.show()
					self._window.use_daemon(self.daemon)
				else:
					log.error("Failed to show menu")
					self._window = None
		elif message.startswith("OSD: area"):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show area")
			else:
				args = shsplit(message)[1:]
				self._window = Area()
				self._window.connect('destroy', self.on_keyboard_closed)
				if self._window.parse_argumets(args):
					self._window.show()
				else:
					self._window.quit()
					self._window = None
		elif message.startswith("OSD: clear"):
			# Clears active OSD window (if any)
			if self._window:
				self._window.quit()
				self._window = None
		else:
			log.warning("Unknown command from daemon: '%s'", message)
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
Beispiel #4
0
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				if self.on_wayland:
					# Cannot display preview with non-X11 backends
					return
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
Beispiel #5
0
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
Beispiel #6
0
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				if self.on_wayland:
					# Cannot display preview with non-X11 backends
					return
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
Beispiel #7
0
class OSDDaemon(object):
    def __init__(self):
        self.exit_code = -1
        self.mainloop = GLib.MainLoop()
        self.config = None
        # hash_of_colors is used to determine if css needs to be reapplied
        # after configuration change
        self._hash_of_colors = -1
        self._window = None
        self._registered = False
        self._last_profile_change = 0
        self._recent_profiles_undo = None

    def quit(self, code=-1):
        self.exit_code = code
        self.mainloop.quit()

    def get_exit_code(self):
        return self.exit_code

    def on_daemon_reconfigured(self, *a):
        log.debug("Reloading config...")
        self.config.reload()
        self._check_colorconfig_change()

    def on_profile_changed(self, daemon, profile):
        name = os.path.split(profile)[-1]
        if name.endswith(".sccprofile") and not name.startswith("."):
            # Ignore .mod and hidden files
            name = name[0:-11]
            recents = self.config['recent_profiles']
            if len(recents) and recents[0] == name:
                # Already first in recent list
                return

            if time.time() - self._last_profile_change < 2.0:
                # Profiles are changing too fast, probably because user
                # is using scroll wheel over profile combobox
                if self._recent_profiles_undo:
                    recents = [] + self._recent_profiles_undo
            self._last_profile_change = time.time()
            self._recent_profiles_undo = [] + recents

            while name in recents:
                recents.remove(name)
            recents.insert(0, name)
            if len(recents) > self.config['recent_max']:
                recents = recents[0:self.config['recent_max']]
            self.config['recent_profiles'] = recents
            self.config.save()
            log.debug("Updated recent profile list")

    def on_daemon_died(self, *a):
        log.error("Connection to daemon lost")
        self.quit(2)

    def on_daemon_connected(self, *a):
        def success(*a):
            log.info("Sucessfully registered as scc-osd-daemon")
            self._registered = True

        def failure(why):
            log.error("Failed to registered as scc-osd-daemon: %s", why)
            self.quit(1)

        if not self._registered:
            self.daemon.request('Register: osd', success, failure)

    def on_menu_closed(self, m):
        """ Called after OSD menu is hidden from screen """
        self._window = None
        if m.get_exit_code() == 0:
            # 0 means that user selected item and confirmed selection
            self.daemon.request(
                'Selected: %s' % (shjoin(
                    [m.get_menuid(), m.get_selected_item_id()])),
                lambda *a: False, lambda *a: False)

    def on_keyboard_closed(self, *a):
        """ Called after on-screen keyboard is hidden from the screen """
        self._window = None

    def on_gesture_recognized(self, gd):
        """ Called after on-screen keyboard is hidden from the screen """
        self._window = None
        if gd.get_exit_code() == 0:
            self.daemon.request('Gestured: %s' % (gd.get_gesture(), ),
                                lambda *a: False, lambda *a: False)
        else:
            self.daemon.request('Gestured: x', lambda *a: False,
                                lambda *a: False)

    @staticmethod
    def _is_menu_message(m):
        """
		Returns True if m starts with 'OSD: [grid|radial]menu'
		or "OSD: dialog"
		"""
        return (m.startswith("OSD: menu") or m.startswith("OSD: radialmenu")
                or m.startswith("OSD: quickmenu")
                or m.startswith("OSD: gridmenu") or m.startswith("OSD: dialog")
                or m.startswith("OSD: hmenu"))

    def on_unknown_message(self, daemon, message):
        if not message.startswith("OSD:"):
            return
        if message.startswith("OSD: message"):
            args = shsplit(message)[1:]
            m = Message()
            m.parse_argumets(args)
            m.show()
        elif message.startswith("OSD: keyboard"):
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show keyboard"
                )
            else:
                args = shsplit(message)[1:]
                self._window = Keyboard(self.config)
                self._window.connect('destroy', self.on_keyboard_closed)
                self._window.parse_argumets(args)
                self._window.show()
                self._window.use_daemon(self.daemon)
        elif message.startswith("OSD: gesture"):
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show keyboard"
                )
            else:
                args = shsplit(message)[1:]
                self._window = GestureDisplay(self.config)
                self._window.parse_argumets(args)
                self._window.use_daemon(self.daemon)
                self._window.show()
                self._window.connect('destroy', self.on_gesture_recognized)
        elif self._is_menu_message(message):
            args = shsplit(message)[1:]
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show menu")
            else:
                if message.startswith("OSD: hmenu"):
                    self._window = HorizontalMenu()
                elif message.startswith("OSD: radialmenu"):
                    self._window = RadialMenu()
                elif message.startswith("OSD: quickmenu"):
                    self._window = QuickMenu()
                elif message.startswith("OSD: gridmenu"):
                    self._window = GridMenu()
                elif message.startswith("OSD: dialog"):
                    self._window = Dialog()
                else:
                    self._window = Menu()
                self._window.connect('destroy', self.on_menu_closed)
                self._window.use_config(self.config)
                if self._window.parse_argumets(args):
                    self._window.show()
                    self._window.use_daemon(self.daemon)
                else:
                    log.error("Failed to show menu")
                    self._window = None
        elif message.startswith("OSD: area"):
            args = shsplit(message)[1:]
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show area")
            else:
                args = shsplit(message)[1:]
                self._window = Area()
                self._window.connect('destroy', self.on_keyboard_closed)
                if self._window.parse_argumets(args):
                    self._window.show()
                else:
                    self._window.quit()
                    self._window = None
        elif message.startswith("OSD: clear"):
            # Clears active OSD window (if any)
            if self._window:
                self._window.quit()
                self._window = None
        else:
            log.warning("Unknown command from daemon: '%s'", message)

    def _check_colorconfig_change(self):
        """
		Checks if OSD color configuration is changed and re-applies CSS
		if needed.
		"""
        h = sum([
            hash(self.config['osd_colors'][x])
            for x in self.config['osd_colors']
        ])
        h += sum([
            hash(self.config['osk_colors'][x])
            for x in self.config['osk_colors']
        ])
        if self._hash_of_colors != h:
            self._hash_of_colors = h
            OSDWindow._apply_css(self.config)
            if self._window and isinstance(self._window, Keyboard):
                self._window.recolor()
                self._window.update_labels()
                self._window.redraw_background()

    def run(self):
        on_wayland = "WAYLAND_DISPLAY" in os.environ or not isinstance(
            Gdk.Display.get_default(), GdkX11.X11Display)
        if on_wayland:
            log.error("Cannot run on Wayland")
            self.exit_code = 8
            return
        self.daemon = DaemonManager()
        self.config = Config()
        self._check_colorconfig_change()
        self.daemon.connect('alive', self.on_daemon_connected)
        self.daemon.connect('dead', self.on_daemon_died)
        self.daemon.connect('profile-changed', self.on_profile_changed)
        self.daemon.connect('reconfigured', self.on_daemon_reconfigured)
        self.daemon.connect('unknown-msg', self.on_unknown_message)
        self.mainloop.run()
Beispiel #8
0
 def on_unknown_message(self, daemon, message):
     if not message.startswith("OSD:"):
         return
     if message.startswith("OSD: message"):
         args = shsplit(message)[1:]
         m = Message()
         m.parse_argumets(args)
         m.show()
     elif message.startswith("OSD: keyboard"):
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show keyboard"
             )
         else:
             args = shsplit(message)[1:]
             self._window = Keyboard(self.config)
             self._window.connect('destroy', self.on_keyboard_closed)
             self._window.parse_argumets(args)
             self._window.show()
             self._window.use_daemon(self.daemon)
     elif message.startswith("OSD: gesture"):
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show keyboard"
             )
         else:
             args = shsplit(message)[1:]
             self._window = GestureDisplay(self.config)
             self._window.parse_argumets(args)
             self._window.use_daemon(self.daemon)
             self._window.show()
             self._window.connect('destroy', self.on_gesture_recognized)
     elif self._is_menu_message(message):
         args = shsplit(message)[1:]
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show menu")
         else:
             if message.startswith("OSD: hmenu"):
                 self._window = HorizontalMenu()
             elif message.startswith("OSD: radialmenu"):
                 self._window = RadialMenu()
             elif message.startswith("OSD: quickmenu"):
                 self._window = QuickMenu()
             elif message.startswith("OSD: gridmenu"):
                 self._window = GridMenu()
             elif message.startswith("OSD: dialog"):
                 self._window = Dialog()
             else:
                 self._window = Menu()
             self._window.connect('destroy', self.on_menu_closed)
             self._window.use_config(self.config)
             if self._window.parse_argumets(args):
                 self._window.show()
                 self._window.use_daemon(self.daemon)
             else:
                 log.error("Failed to show menu")
                 self._window = None
     elif message.startswith("OSD: area"):
         args = shsplit(message)[1:]
         if self._window:
             log.warning(
                 "Another OSD is already visible - refusing to show area")
         else:
             args = shsplit(message)[1:]
             self._window = Area()
             self._window.connect('destroy', self.on_keyboard_closed)
             if self._window.parse_argumets(args):
                 self._window.show()
             else:
                 self._window.quit()
                 self._window = None
     elif message.startswith("OSD: clear"):
         # Clears active OSD window (if any)
         if self._window:
             self._window.quit()
             self._window = None
     else:
         log.warning("Unknown command from daemon: '%s'", message)
class OSDDaemon(object):
	def __init__(self):
		self.exit_code = -1
		self.mainloop = GLib.MainLoop()
		self._window = None
		self._registered = False
		OSDWindow._apply_css()
	
	
	def quit(self, code=-1):
		self.exit_code = code
		self.mainloop.quit()
	
	
	def get_exit_code(self):
		return self.exit_code
	
	
	def on_daemon_died(self, *a):
		log.error("Daemon died")
		self.quit(2)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.info("Sucessfully registered as scc-osd-daemon")
			self._registered = True
		def failure(why):
			log.error("Failed to registered as scc-osd-daemon: %s", why)
			self.quit(1)
		
		if not self._registered:
			self.daemon.request('Register: osd', success, failure)
	
	
	def on_menu_closed(self, m):
		""" Called after OSD menu is hidden from screen """
		self._window = None
		if m.get_exit_code() == 0:
			# 0 means that user selected item and confirmed selection
			self.daemon.request(
				'Selected: %s %s' % (m.get_menuid(), m.get_selected_item_id()),
				lambda *a : False, lambda *a : False)
	
	
	def on_keyboard_closed(self, *a):
		""" Called after on-screen keyboard is hidden from the screen """
		self._window = None
	
	
	def on_unknown_message(self, daemon, message):
		if not message.startswith("OSD:"):
			return
		if message.startswith("OSD: message"):
			args = split(message)[1:]
			m = Message()
			m.parse_argumets(args)
			m.show()
		elif message.startswith("OSD: keyboard"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = split(message)[1:]
				self._window = Keyboard()
				self._window.connect('destroy', self.on_keyboard_closed)
				# self._window.parse_argumets(args) # TODO: No arguments so far
				self._window.show()
				self._window.use_daemon(self.daemon)
		elif message.startswith("OSD: menu") or message.startswith("OSD: gridmenu"):
			args = split(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show menu")
			else:
				self._window = GridMenu() if "gridmenu" in message else Menu()
				self._window.connect('destroy', self.on_menu_closed)
				if self._window.parse_argumets(args):
					self._window.show()
					self._window.use_daemon(self.daemon)
				else:
					log.error("Failed to show menu")
					self._window = None
		elif message.startswith("OSD: area"):
			args = split(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show area")
			else:
				args = split(message)[1:]
				self._window = Area()
				self._window.connect('destroy', self.on_keyboard_closed)
				if self._window.parse_argumets(args):
					self._window.show()
				else:
					self._window.quit()
					self._window = None
		elif message.startswith("OSD: clear"):
			# Clears active OSD window (if any)
			if self._window:
				self._window.quit()
				self._window = None
		else:
			log.warning("Unknown command from daemon: '%s'", message)
	
	
	def run(self):
		self.daemon = DaemonManager()
		self.daemon.connect('dead', self.on_daemon_died)
		self.daemon.connect('alive', self.on_daemon_connected)
		self.daemon.connect('unknown-msg', self.on_unknown_message)
		self.mainloop.run()
Beispiel #10
0
class OSDDaemon(object):
	def __init__(self):
		self.exit_code = -1
		self.mainloop = GLib.MainLoop()
		self.config = None
		# hash_of_colors is used to determine if css needs to be reapplied
		# after configuration change
		self._hash_of_colors = -1
		self._window = None
		self._registered = False
		self._last_profile_change = 0
		self._recent_profiles_undo = None
	
	
	def quit(self, code=-1):
		self.exit_code = code
		self.mainloop.quit()
	
	
	def get_exit_code(self):
		return self.exit_code
	
	
	def on_daemon_reconfigured(self, *a):
		log.debug("Reloading config...")
		self.config.reload()
		self._check_colorconfig_change()
	
	
	def on_profile_changed(self, daemon, profile):
		name = os.path.split(profile)[-1]
		if name.endswith(".sccprofile") and not name.startswith("."):
			# Ignore .mod and hidden files
			name = name[0:-11]
			recents = self.config['recent_profiles']
			if len(recents) and recents[0] == name:
				# Already first in recent list
				return
			
			if time.time() - self._last_profile_change < 2.0:
				# Profiles are changing too fast, probably because user
				# is using scroll wheel over profile combobox
				if self._recent_profiles_undo:
					recents = [] + self._recent_profiles_undo
			self._last_profile_change = time.time()
			self._recent_profiles_undo = [] + recents
			
			while name in recents:
				recents.remove(name)
			recents.insert(0, name)
			if len(recents) > self.config['recent_max']:
				recents = recents[0:self.config['recent_max']]
			self.config['recent_profiles'] = recents
			self.config.save()
			log.debug("Updated recent profile list")
	
	
	def on_daemon_died(self, *a):
		log.error("Connection to daemon lost")
		self.quit(2)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.info("Sucessfully registered as scc-osd-daemon")
			self._registered = True
		def failure(why):
			log.error("Failed to registered as scc-osd-daemon: %s", why)
			self.quit(1)
		
		if not self._registered:
			self.daemon.request('Register: osd', success, failure)
	
	
	def on_menu_closed(self, m):
		""" Called after OSD menu is hidden from screen """
		self._window = None
		if m.get_exit_code() == 0:
			# 0 means that user selected item and confirmed selection
			self.daemon.request(
				'Selected: %s' % ( shjoin([
					m.get_menuid(), m.get_selected_item_id() 
				])),
				lambda *a : False, lambda *a : False)
	
	
	def on_keyboard_closed(self, *a):
		""" Called after on-screen keyboard is hidden from the screen """
		self._window = None
	
	
	@staticmethod
	def _is_menu_message(m):
		""" Returns True if m starts with 'OSD: [grid|radial]menu' """
		return (
			m.startswith("OSD: menu")
			or m.startswith("OSD: gridmenu")
			or m.startswith("OSD: radialmenu")
		)
	
	
	def on_unknown_message(self, daemon, message):
		if not message.startswith("OSD:"):
			return
		if message.startswith("OSD: message"):
			args = shsplit(message)[1:]
			m = Message()
			m.parse_argumets(args)
			m.show()
		elif message.startswith("OSD: keyboard"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = Keyboard(self.config)
				self._window.connect('destroy', self.on_keyboard_closed)
				self._window.parse_argumets(args)
				self._window.show()
				self._window.use_daemon(self.daemon)
		elif self._is_menu_message(message):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show menu")
			else:
				if message.startswith("OSD: gridmenu"):
					self._window = GridMenu()
				elif message.startswith("OSD: radialmenu"):
					self._window = RadialMenu()
				else:
					self._window = Menu()
				self._window.connect('destroy', self.on_menu_closed)
				self._window.use_config(self.config)
				if self._window.parse_argumets(args):
					self._window.show()
					self._window.use_daemon(self.daemon)
				else:
					log.error("Failed to show menu")
					self._window = None
		elif message.startswith("OSD: area"):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show area")
			else:
				args = shsplit(message)[1:]
				self._window = Area()
				self._window.connect('destroy', self.on_keyboard_closed)
				if self._window.parse_argumets(args):
					self._window.show()
				else:
					self._window.quit()
					self._window = None
		elif message.startswith("OSD: clear"):
			# Clears active OSD window (if any)
			if self._window:
				self._window.quit()
				self._window = None
		else:
			log.warning("Unknown command from daemon: '%s'", message)
	
	
	def _check_colorconfig_change(self):
		"""
		Checks if OSD color configuration is changed and re-applies CSS
		if needed.
		"""
		h = sum([ hash(self.config['osd_colors'][x]) for x in self.config['osd_colors'] ])
		h += sum([ hash(self.config['osk_colors'][x]) for x in self.config['osk_colors'] ])
		if self._hash_of_colors != h:
			self._hash_of_colors = h
			OSDWindow._apply_css(self.config)
			if self._window and isinstance(self._window, Keyboard):
				self._window.recolor()
				self._window.update_labels()
				self._window.redraw_background()
	
	
	def run(self):
		self.daemon = DaemonManager()
		self.config = Config()
		self._check_colorconfig_change()
		self.daemon.connect('alive', self.on_daemon_connected)
		self.daemon.connect('dead', self.on_daemon_died)
		self.daemon.connect('profile-changed', self.on_profile_changed)
		self.daemon.connect('reconfigured', self.on_daemon_reconfigured)
		self.daemon.connect('unknown-msg', self.on_unknown_message)
		self.mainloop.run()
Beispiel #11
0
class OSDDaemon(object):
	def __init__(self):
		self.exit_code = -1
		self.mainloop = GLib.MainLoop()
		self.config = None
		# hash_of_colors is used to determine if css needs to be reapplied
		# after configuration change
		self._hash_of_colors = -1
		self._visible_messages = {}
		self._window = None
		self._registered = False
		self._last_profile_change = 0
		self._recent_profiles_undo = None
	
	
	def quit(self, code=-1):
		self.exit_code = code
		self.mainloop.quit()
	
	
	def get_exit_code(self):
		return self.exit_code
	
	
	def on_daemon_reconfigured(self, *a):
		log.debug("Reloading config...")
		self.config.reload()
		self._check_colorconfig_change()
	
	
	def on_profile_changed(self, daemon, profile):
		name = os.path.split(profile)[-1]
		if name.endswith(".sccprofile") and not name.startswith("."):
			# Ignore .mod and hidden files
			name = name[0:-11]
			recents = self.config['recent_profiles']
			if len(recents) and recents[0] == name:
				# Already first in recent list
				return
			
			if time.time() - self._last_profile_change < 2.0:
				# Profiles are changing too fast, probably because user
				# is using scroll wheel over profile combobox
				if self._recent_profiles_undo:
					recents = [] + self._recent_profiles_undo
			self._last_profile_change = time.time()
			self._recent_profiles_undo = [] + recents
			
			while name in recents:
				recents.remove(name)
			recents.insert(0, name)
			if len(recents) > self.config['recent_max']:
				recents = recents[0:self.config['recent_max']]
			self.config['recent_profiles'] = recents
			self.config.save()
			log.debug("Updated recent profile list")
			self.clear_messages()
	
	
	def on_daemon_died(self, *a):
		log.error("Connection to daemon lost")
		self.quit(2)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.info("Sucessfully registered as scc-osd-daemon")
			self._registered = True
		def failure(why):
			log.error("Failed to registered as scc-osd-daemon: %s", why)
			self.quit(1)
		
		if not self._registered:
			self.daemon.request('Register: osd', success, failure)
	
	
	def on_menu_closed(self, m):
		""" Called after OSD menu is hidden from screen """
		self._window = None
		if m.get_exit_code() == 0:
			# 0 means that user selected item and confirmed selection
			self.daemon.request(
				'Selected: %s' % ( shjoin([
					m.get_menuid(), m.get_selected_item_id()
				])),
				lambda *a : False, lambda *a : False)
	
	
	def on_message_closed(self, m):
		hsh = m.hash()
		if hsh in self._visible_messages:
			del self._visible_messages[hsh]
	
	
	def on_keyboard_closed(self, *a):
		""" Called after on-screen keyboard is hidden from the screen """
		self._window = None
	
	
	def on_gesture_recognized(self, gd):
		""" Called after on-screen keyboard is hidden from the screen """
		self._window = None
		if gd.get_exit_code() == 0:
			self.daemon.request('Gestured: %s' % ( gd.get_gesture(), ),
				lambda *a : False, lambda *a : False)
		else:
			self.daemon.request('Gestured: x', lambda *a : False, lambda *a : False)
	
	
	@staticmethod
	def _is_menu_message(m):
		"""
		Returns True if m starts with 'OSD: [grid|radial]menu'
		or "OSD: dialog"
		"""
		return (
			m.startswith("OSD: menu")
			or m.startswith("OSD: radialmenu")
			or m.startswith("OSD: quickmenu")
			or m.startswith("OSD: gridmenu")
			or m.startswith("OSD: dialog")
			or m.startswith("OSD: hmenu")
		)
	
	
	def on_unknown_message(self, daemon, message):
		if not message.startswith("OSD:"):
			return
		if message.startswith("OSD: message"):
			args = shsplit(message)[1:]
			m = Message()
			m.parse_argumets(args)
			hsh = m.hash()
			if hsh in self._visible_messages:
				self._visible_messages[hsh].extend()
				m.destroy()
			else:
				# TODO: Do this only for default position once changing
				# TODO: is allowed
				if len(self._visible_messages):
					height = self._visible_messages.values()[0].get_size().height
					x, y = m.position
					while y in [ i.position[1] for i in self._visible_messages.values() ]:
						y -= height + 5
					m.position = x, y
				m.show()
				self._visible_messages[hsh] = m
				m.connect("destroy", self.on_message_closed)
		elif message.startswith("OSD: keyboard"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = Keyboard(self.config)
				self._window.connect('destroy', self.on_keyboard_closed)
				self._window.parse_argumets(args)
				self._window.show()
				self._window.use_daemon(self.daemon)
		elif message.startswith("OSD: gesture"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = GestureDisplay(self.config)
				self._window.parse_argumets(args)
				self._window.use_daemon(self.daemon)
				self._window.show()
				self._window.connect('destroy', self.on_gesture_recognized)
		elif self._is_menu_message(message):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show menu")
			else:
				if message.startswith("OSD: hmenu"):
					self._window = HorizontalMenu()
				elif message.startswith("OSD: radialmenu"):
					self._window = RadialMenu()
				elif message.startswith("OSD: quickmenu"):
					self._window = QuickMenu()
				elif message.startswith("OSD: gridmenu"):
					self._window = GridMenu()
				elif message.startswith("OSD: dialog"):
					self._window = Dialog()
				else:
					self._window = Menu()
				self._window.connect('destroy', self.on_menu_closed)
				self._window.use_config(self.config)
				try:
					if self._window.parse_argumets(args):
						self._window.show()
						self._window.use_daemon(self.daemon)
					else:
						log.error("Failed to show menu")
						self._window = None
				except:
					log.error(traceback.format_exc())
					log.error("Failed to show menu")
					self._window = None
		elif message.startswith("OSD: area"):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show area")
			else:
				args = shsplit(message)[1:]
				self._window = Area()
				self._window.connect('destroy', self.on_keyboard_closed)
				if self._window.parse_argumets(args):
					self._window.show()
				else:
					self._window.quit()
					self._window = None
		elif message.startswith("OSD: clear"):
			# Clears active OSD windows
			self.clear_windows()
		else:
			log.warning("Unknown command from daemon: '%s'", message)
	
	
	def clear_windows(self):
		if self._window:
			self._window.quit()
			self._window = None
		self.clear_messages(only_long_lasting=False)
	
	
	def clear_messages(self, only_long_lasting=True):
		"""
		Clears all OSD messages from screen.
		If only_long_lasting is True, which is default behaviour on profile
		change, only messages set to last more than 10s are hidden.
		"""
		to_destroy = [] + self._visible_messages.values()
		for m in to_destroy:
			if not only_long_lasting or m.timeout <= 0 or m.timeout > OSDAction.DEFAULT_TIMEOUT * 2:
				m.destroy()
	
	
	def _check_colorconfig_change(self):
		"""
		Checks if OSD color configuration is changed and re-applies CSS
		if needed.
		"""
		h = sum([ hash(self.config['osd_colors'][x]) for x in self.config['osd_colors'] ])
		h += sum([ hash(self.config['osk_colors'][x]) for x in self.config['osk_colors'] ])
		h += hash(self.config['osd_style'])
		if self._hash_of_colors != h:
			self._hash_of_colors = h
			OSDWindow._apply_css(self.config)
			if self._window and isinstance(self._window, Keyboard):
				self._window.recolor()
				self._window.update_labels()
				self._window.redraw_background()
	
	
	def run(self):
		on_wayland = "WAYLAND_DISPLAY" in os.environ or not isinstance(Gdk.Display.get_default(), GdkX11.X11Display)
		if on_wayland:
			log.error("Cannot run on Wayland")
			self.exit_code = 8
			return
		self.daemon = DaemonManager()
		self.config = Config()
		self._check_colorconfig_change()
		self.daemon.connect('alive', self.on_daemon_connected)
		self.daemon.connect('dead', self.on_daemon_died)
		self.daemon.connect('profile-changed', self.on_profile_changed)
		self.daemon.connect('reconfigured', self.on_daemon_reconfigured)
		self.daemon.connect('unknown-msg', self.on_unknown_message)
		self.mainloop.run()
Beispiel #12
0
class OSDDaemon(object):
    def __init__(self):
        self.exit_code = -1
        self.mainloop = GLib.MainLoop()
        self.config = None
        self._window = None
        self._registered = False
        self._last_profile_change = 0
        self._recent_profiles_undo = None
        OSDWindow._apply_css()

    def quit(self, code=-1):
        self.exit_code = code
        self.mainloop.quit()

    def get_exit_code(self):
        return self.exit_code

    def on_daemon_reconfigured(self, *a):
        log.debug("Reloading config...")
        self.config.reload()

    def on_profile_changed(self, daemon, profile):
        name = os.path.split(profile)[-1]
        if name.endswith(".sccprofile") and not name.startswith("."):
            # Ignore .mod and hidden files
            name = name[0:-11]
            recents = self.config['recent_profiles']
            if len(recents) and recents[0] == name:
                # Already first in recent list
                return

            if time.time() - self._last_profile_change < 2.0:
                # Profiles are changing too fast, probably because user
                # is using scroll wheel over profile combobox
                if self._recent_profiles_undo:
                    recents = [] + self._recent_profiles_undo
            self._last_profile_change = time.time()
            self._recent_profiles_undo = [] + recents

            while name in recents:
                recents.remove(name)
            recents.insert(0, name)
            if len(recents) > self.config['recent_max']:
                recents = recents[0:self.config['recent_max']]
            self.config['recent_profiles'] = recents
            self.config.save()
            log.debug("Updated recent profile list")

    def on_daemon_died(self, *a):
        log.error("Connection to daemon lost")
        self.quit(2)

    def on_daemon_connected(self, *a):
        def success(*a):
            log.info("Sucessfully registered as scc-osd-daemon")
            self._registered = True

        def failure(why):
            log.error("Failed to registered as scc-osd-daemon: %s", why)
            self.quit(1)

        if not self._registered:
            self.daemon.request('Register: osd', success, failure)

    def on_menu_closed(self, m):
        """ Called after OSD menu is hidden from screen """
        self._window = None
        if m.get_exit_code() == 0:
            # 0 means that user selected item and confirmed selection
            self.daemon.request(
                'Selected: %s %s' % (m.get_menuid(), m.get_selected_item_id()),
                lambda *a: False, lambda *a: False)

    def on_keyboard_closed(self, *a):
        """ Called after on-screen keyboard is hidden from the screen """
        self._window = None

    def on_unknown_message(self, daemon, message):
        if not message.startswith("OSD:"):
            return
        if message.startswith("OSD: message"):
            args = split(message)[1:]
            m = Message()
            m.parse_argumets(args)
            m.show()
        elif message.startswith("OSD: keyboard"):
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show keyboard"
                )
            else:
                args = split(message)[1:]
                self._window = Keyboard()
                self._window.connect('destroy', self.on_keyboard_closed)
                # self._window.parse_argumets(args) # TODO: No arguments so far
                self._window.show()
                self._window.use_daemon(self.daemon)
        elif message.startswith("OSD: menu") or message.startswith(
                "OSD: gridmenu"):
            args = split(message)[1:]
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show menu")
            else:
                self._window = GridMenu() if "gridmenu" in message else Menu()
                self._window.connect('destroy', self.on_menu_closed)
                self._window.use_config(self.config)
                if self._window.parse_argumets(args):
                    self._window.show()
                    self._window.use_daemon(self.daemon)
                else:
                    log.error("Failed to show menu")
                    self._window = None
        elif message.startswith("OSD: area"):
            args = split(message)[1:]
            if self._window:
                log.warning(
                    "Another OSD is already visible - refusing to show area")
            else:
                args = split(message)[1:]
                self._window = Area()
                self._window.connect('destroy', self.on_keyboard_closed)
                if self._window.parse_argumets(args):
                    self._window.show()
                else:
                    self._window.quit()
                    self._window = None
        elif message.startswith("OSD: clear"):
            # Clears active OSD window (if any)
            if self._window:
                self._window.quit()
                self._window = None
        else:
            log.warning("Unknown command from daemon: '%s'", message)

    def run(self):
        self.daemon = DaemonManager()
        self.config = Config()
        self.daemon.connect('alive', self.on_daemon_connected)
        self.daemon.connect('dead', self.on_daemon_died)
        self.daemon.connect('profile-changed', self.on_profile_changed)
        self.daemon.connect('reconfigured', self.on_daemon_reconfigured)
        self.daemon.connect('unknown-msg', self.on_unknown_message)
        self.mainloop.run()
Beispiel #13
0
class AxisActionComponent(AEComponent, TimerManager):
	GLADE = "ae/axis_action.glade"
	NAME = "axis_action"
	CTXS = Action.AC_STICK, Action.AC_PAD,
	PRIORITY = 3
	
	def __init__(self, app, editor):
		AEComponent.__init__(self, app, editor)
		TimerManager.__init__(self)
		self._recursing = False
		self.relative_area = False
		self.osd_area_instance = None
		self.parser = GuiActionParser()
	
	
	def load(self):
		if self.loaded : return
		AEComponent.load(self)
		cbAreaType = self.builder.get_object("cbAreaType")
		cbAreaType.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == "-" )
	
	
	def hidden(self):
		self.update_osd_area(None)
	
	
	def set_action(self, mode, action):
		if self.handles(mode, action):
			cb = self.builder.get_object("cbAxisOutput")
			if isinstance(action, AreaAction):
				self.load_area_action(action)
				self.set_cb(cb, "area", 2)
				self.update_osd_area(action)
				return
			self.update_osd_area(None)
			if isinstance(action, TrackpadAction):
				self.set_cb(cb, "trackpad", 2)
			elif isinstance(action, TrackballAction):
				self.set_cb(cb, "trackball", 2)
			elif isinstance(action, CircularAction):
				self.set_cb(cb, "circular", 2)
			elif isinstance(action, XYAction):
				p = [ None, None ]
				for x in (0, 1):
					if len(action.actions[0].strip().parameters) >= x:
						if len(action.actions[x].strip().parameters) > 0:
							p[x] = action.actions[x].strip().parameters[0]
				if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
					self.set_cb(cb, "lstick", 2)
				elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
					self.set_cb(cb, "rstick", 2)
				elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
					self.set_cb(cb, "dpad", 2)
				elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
					self.set_cb(cb, "wheel", 2)
			else:
				self.set_cb(cb, "none", 2)
	
	
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
	
	
	def load_area_action(self, action):
		"""
		Load AreaAction values into UI.
		"""
		cbAreaType = self.builder.get_object("cbAreaType")
		
		x1, y1, x2, y2 = action.coords
		self.relative_area = False
		if isinstance(action, RelAreaAction):
			key = "screensize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		elif isinstance(action, RelWinAreaAction):
			key = "windowsize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		else:
			t1 = "1" if x1 < 0 and x2 < 0 else "0"
			t2 = "1" if y1 < 0 and y2 < 0 else "0"
			x1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2)
			if x2 < x1 : x1, x2 = x2, x1
			if y2 < y1 : y1, y2 = y2, y1
			if isinstance(action, WinAreaAction):
				key = "window-%s%s" % (t1, t2)
			else:
				key = "screen-%s%s" % (t1, t2)
		
		self._recursing = True
		self.builder.get_object("sbAreaX1").set_value(x1)
		self.builder.get_object("sbAreaY1").set_value(y1)
		self.builder.get_object("sbAreaX2").set_value(x2)
		self.builder.get_object("sbAreaY2").set_value(y2)
		self.builder.get_object("cbAreaOSDEnabled").set_active(self.editor.osd)
		self.builder.get_object("cbAreaClickEnabled").set_active(self.pressing_pad_clicks())
		for row in cbAreaType.get_model():
			if key == row[1]:
				cbAreaType.set_active_iter(row.iter)
				break
		self._recursing = False
	
	
	def on_cbAreaOSDEnabled_toggled(self, *a):
		self.editor.builder.get_object("cbOSD").set_active(
			self.builder.get_object("cbAreaOSDEnabled").get_active())
	
	
	def pressing_pad_clicks(self):
		"""
		Returns True if currently edited pad is set to press left mouse
		button when pressed.
		(yes, this is used somewhere)
		"""
		side = getattr(SCButtons, self.editor.get_id())
		c_action = self.app.current.buttons[side]
		if isinstance(c_action, ButtonAction):
			return c_action.button == Keys.BTN_LEFT
		return False
	
	
	def on_ok(self, action):
		if isinstance(action.strip(), AreaAction):
			# Kinda hacky way to set action on LPAD press or RPAD press
			# when user selects Mouse Area as ouput and checks
			# 'Pressing the Pad Clicks' checkbox
			side = getattr(SCButtons, self.editor.get_id())
			clicks = self.pressing_pad_clicks()
			
			if self.builder.get_object("cbAreaClickEnabled").get_active():
				if not clicks:
					# Turn pad press into mouse clicks
					self.app.set_action(side, ButtonAction(Keys.BTN_LEFT))
			else:
				if clicks:
					# Clear action created above if checkbox is uncheck
					self.app.set_action(side, NoAction())
	
	
	def make_area_action(self):
		"""
		Loads values from UI into new AreaAction or subclass.
		"""
		# Prepare
		cbAreaType = self.builder.get_object("cbAreaType")
		# Read numbers
		x1 = self.builder.get_object("sbAreaX1").get_value()
		y1 = self.builder.get_object("sbAreaY1").get_value()
		x2 = self.builder.get_object("sbAreaX2").get_value()
		y2 = self.builder.get_object("sbAreaY2").get_value()
		# Determine exact action type by looking into Area Type checkbox
		# (this part may seem little crazy)
		# ... numbers
		key = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1)
		if "-" in key:
			if key[-2] == "1":
				# Before-last character ius "1", that means that X coords are
				# counted from other side and has to be negated
				x1, x2 = -x1, -x2
			if key[-1] == "1":
				# Key ends with "1". Same thing as above but for Y coordinate
				y1, y2 = -y1, -y2
		if "size" in key:
			x1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0
		# ... class 
		if "window-" in key:
			cls = WinAreaAction
			self.relative_area = False
		elif "screensize" == key:
			cls = RelAreaAction
			self.relative_area = True
		elif "windowsize" == key:
			cls = RelWinAreaAction
			self.relative_area = True
		else: # "screen" in key
			cls = AreaAction
			self.relative_area = False
		if not self.relative_area:
			x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
			
		return cls(x1, y1, x2, y2)
	
	
	def get_button_title(self):
		return _("Joystick or Mouse")
	
	
	def handles(self, mode, action):
		if isinstance(action, (NoAction, TrackballAction, CircularAction,
					InvalidAction, AreaAction)):
			return True
		if isinstance(action, XYAction):
			p = [ None, None ]
			for x in (0, 1):
				if len(action.actions[0].strip().parameters) >= x:
					if len(action.actions[x].strip().parameters) > 0:
						p[x] = action.actions[x].strip().parameters[0]
			if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
				return True
			elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
				return True
			elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
				return True
			elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
				return True
		return False
	
	
	def on_area_options_changed(self, *a):
		if self._recursing : return
		action = self.make_area_action()
		self.editor.set_action(action)
		self.update_osd_area(action)
		for x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'):
			spin = self.builder.get_object(x)
			if self.relative_area:
				spin.get_adjustment().set_upper(100)
			else:
				spin.get_adjustment().set_upper(1000)
			self.on_sbArea_output(spin)
	
	
	def on_sbArea_output(self, button, *a):
		if self.relative_area:
			button.set_text("%s %%" % (button.get_value()))
		else:
			button.set_text("%s px" % (int(button.get_value())))
	
	
	def on_sbArea_focus_out_event(self, button, *a):
		GLib.idle_add(self.on_sbArea_output, button)
	
	
	def on_sbArea_changed(self, button, *a):
		self.on_sbArea_output(button)
		self.on_area_options_changed(button)
	
	
	def on_cbAxisOutput_changed(self, *a):
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		stActionData = self.builder.get_object("stActionData")
		key = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 2)
		if key == 'area':
			stActionData.set_visible_child(self.builder.get_object("grArea"))
			action = self.make_area_action()
			self.update_osd_area(action)
		else:
			stActionData.set_visible_child(self.builder.get_object("nothing"))
			action = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 0)
			action = self.parser.restart(action).parse()
			self.update_osd_area(None)
		
		self.editor.set_action(action)
Beispiel #14
0
class AxisActionComponent(AEComponent, TimerManager):
    GLADE = "ae/axis_action.glade"
    NAME = "axis_action"
    CTXS = Action.AC_STICK, Action.AC_PAD,
    PRIORITY = 3

    def __init__(self, app, editor):
        AEComponent.__init__(self, app, editor)
        TimerManager.__init__(self)
        self._recursing = False
        self.relative_area = False
        self.osd_area_instance = None
        self.parser = GuiActionParser()

    def load(self):
        if self.loaded: return
        AEComponent.load(self)
        cbAreaType = self.builder.get_object("cbAreaType")
        cbAreaType.set_row_separator_func(
            lambda model, iter: model.get_value(iter, 0) == "-")

    def hidden(self):
        self.update_osd_area(None)

    def set_action(self, mode, action):
        if self.handles(mode, action):
            cb = self.builder.get_object("cbAxisOutput")
            if isinstance(action, AreaAction):
                self.load_area_action(action)
                self.set_cb(cb, "area", 2)
                self.update_osd_area(action)
                return
            self.update_osd_area(None)
            if isinstance(action, TrackpadAction):
                self.set_cb(cb, "trackpad", 2)
            elif isinstance(action, TrackballAction):
                self.set_cb(cb, "trackball", 2)
            elif isinstance(action, CircularAction):
                self.set_cb(cb, "circular", 2)
            elif isinstance(action, XYAction):
                p = [None, None]
                for x in (0, 1):
                    if len(action.actions[0].strip().parameters) >= x:
                        if len(action.actions[x].strip().parameters) > 0:
                            p[x] = action.actions[x].strip().parameters[0]
                if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
                    self.set_cb(cb, "lstick", 2)
                elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
                    self.set_cb(cb, "rstick", 2)
                elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
                    self.set_cb(cb, "dpad", 2)
                elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
                    self.set_cb(cb, "wheel", 2)
            else:
                self.set_cb(cb, "none", 2)

    def update_osd_area(self, action):
        """ Updates preview area displayed on screen """
        if action:
            if self.osd_area_instance is None:
                self.osd_area_instance = Area()
                self.osd_area_instance.show()
            action.update_osd_area(self.osd_area_instance,
                                   FakeMapper(self.editor))
            self.timer("area", 0.5, self.update_osd_area, action)
        elif self.osd_area_instance:
            self.osd_area_instance.quit()
            self.osd_area_instance = None
            self.cancel_timer("area")

    def load_area_action(self, action):
        """
		Load AreaAction values into UI.
		"""
        cbAreaType = self.builder.get_object("cbAreaType")

        x1, y1, x2, y2 = action.coords
        self.relative_area = False
        if isinstance(action, RelAreaAction):
            key = "screensize"
            self.relative_area = True
            x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
        elif isinstance(action, RelWinAreaAction):
            key = "windowsize"
            self.relative_area = True
            x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
        else:
            t1 = "1" if x1 < 0 and x2 < 0 else "0"
            t2 = "1" if y1 < 0 and y2 < 0 else "0"
            x1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2)
            if x2 < x1: x1, x2 = x2, x1
            if y2 < y1: y1, y2 = y2, y1
            if isinstance(action, WinAreaAction):
                key = "window-%s%s" % (t1, t2)
            else:
                key = "screen-%s%s" % (t1, t2)

        self._recursing = True
        self.builder.get_object("sbAreaX1").set_value(x1)
        self.builder.get_object("sbAreaY1").set_value(y1)
        self.builder.get_object("sbAreaX2").set_value(x2)
        self.builder.get_object("sbAreaY2").set_value(y2)
        self.builder.get_object("cbAreaOSDEnabled").set_active(self.editor.osd)
        self.builder.get_object("cbAreaClickEnabled").set_active(
            self.pressing_pad_clicks())
        for row in cbAreaType.get_model():
            if key == row[1]:
                cbAreaType.set_active_iter(row.iter)
                break
        self._recursing = False

    def on_cbAreaOSDEnabled_toggled(self, *a):
        self.editor.builder.get_object("cbOSD").set_active(
            self.builder.get_object("cbAreaOSDEnabled").get_active())

    def pressing_pad_clicks(self):
        """
		Returns True if currently edited pad is set to press left mouse
		button when pressed.
		(yes, this is used somewhere)
		"""
        side = getattr(SCButtons, self.editor.get_id())
        c_action = self.app.current.buttons[side]
        if isinstance(c_action, ButtonAction):
            return c_action.button == Keys.BTN_LEFT
        return False

    def on_ok(self, action):
        if isinstance(action.strip(), AreaAction):
            # Kinda hacky way to set action on LPAD press or RPAD press
            # when user selects Mouse Area as ouput and checks
            # 'Pressing the Pad Clicks' checkbox
            side = getattr(SCButtons, self.editor.get_id())
            clicks = self.pressing_pad_clicks()

            if self.builder.get_object("cbAreaClickEnabled").get_active():
                if not clicks:
                    # Turn pad press into mouse clicks
                    self.app.set_action(side, ButtonAction(Keys.BTN_LEFT))
            else:
                if clicks:
                    # Clear action created above if checkbox is uncheck
                    self.app.set_action(side, NoAction())

    def make_area_action(self):
        """
		Loads values from UI into new AreaAction or subclass.
		"""
        # Prepare
        cbAreaType = self.builder.get_object("cbAreaType")
        # Read numbers
        x1 = self.builder.get_object("sbAreaX1").get_value()
        y1 = self.builder.get_object("sbAreaY1").get_value()
        x2 = self.builder.get_object("sbAreaX2").get_value()
        y2 = self.builder.get_object("sbAreaY2").get_value()
        # Determine exact action type by looking into Area Type checkbox
        # (this part may seem little crazy)
        # ... numbers
        key = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1)
        if "-" in key:
            if key[-2] == "1":
                # Before-last character ius "1", that means that X coords are
                # counted from other side and has to be negated
                x1, x2 = -x1, -x2
            if key[-1] == "1":
                # Key ends with "1". Same thing as above but for Y coordinate
                y1, y2 = -y1, -y2
        if "size" in key:
            x1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0
        # ... class
        if "window-" in key:
            cls = WinAreaAction
            self.relative_area = False
        elif "screensize" == key:
            cls = RelAreaAction
            self.relative_area = True
        elif "windowsize" == key:
            cls = RelWinAreaAction
            self.relative_area = True
        else:  # "screen" in key
            cls = AreaAction
            self.relative_area = False
        if not self.relative_area:
            x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)

        return cls(x1, y1, x2, y2)

    def get_button_title(self):
        return _("Joystick or Mouse")

    def handles(self, mode, action):
        if isinstance(action, (NoAction, TrackballAction, CircularAction,
                               InvalidAction, AreaAction)):
            return True
        if isinstance(action, XYAction):
            p = [None, None]
            for x in (0, 1):
                if len(action.actions[0].strip().parameters) >= x:
                    if len(action.actions[x].strip().parameters) > 0:
                        p[x] = action.actions[x].strip().parameters[0]
            if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
                return True
            elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
                return True
            elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
                return True
            elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
                return True
        return False

    def on_area_options_changed(self, *a):
        if self._recursing: return
        action = self.make_area_action()
        self.editor.set_action(action)
        self.update_osd_area(action)
        for x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'):
            spin = self.builder.get_object(x)
            if self.relative_area:
                spin.get_adjustment().set_upper(100)
            else:
                spin.get_adjustment().set_upper(1000)
            self.on_sbArea_output(spin)

    def on_sbArea_output(self, button, *a):
        if self.relative_area:
            button.set_text("%s %%" % (button.get_value()))
        else:
            button.set_text("%s px" % (int(button.get_value())))

    def on_sbArea_focus_out_event(self, button, *a):
        GLib.idle_add(self.on_sbArea_output, button)

    def on_sbArea_changed(self, button, *a):
        self.on_sbArea_output(button)
        self.on_area_options_changed(button)

    def on_cbAxisOutput_changed(self, *a):
        cbAxisOutput = self.builder.get_object("cbAxisOutput")
        stActionData = self.builder.get_object("stActionData")
        key = cbAxisOutput.get_model().get_value(
            cbAxisOutput.get_active_iter(), 2)
        if key == 'area':
            stActionData.set_visible_child(self.builder.get_object("grArea"))
            action = self.make_area_action()
            self.update_osd_area(action)
        else:
            stActionData.set_visible_child(self.builder.get_object("nothing"))
            action = cbAxisOutput.get_model().get_value(
                cbAxisOutput.get_active_iter(), 0)
            action = self.parser.restart(action).parse()
            self.update_osd_area(None)

        self.editor.set_action(action)
Beispiel #15
0
class AxisActionComponent(AEComponent, TimerManager):
	GLADE = "ae/axis_action.glade"
	NAME = "axis_action"
	CTXS = Action.AC_STICK | Action.AC_PAD
	PRIORITY = 3
	
	def __init__(self, app, editor):
		AEComponent.__init__(self, app, editor)
		TimerManager.__init__(self)
		self._recursing = False
		self.relative_area = False
		self.osd_area_instance = None
		self.on_wayland = False
		self.circular_axis = MouseAction(Rels.REL_WHEEL)
		self.circular_buttons = [ None, None ]
		self.button = None
		self.parser = GuiActionParser()
	
	
	def load(self):
		if self.loaded : return
		AEComponent.load(self)
		cbAreaType = self.builder.get_object("cbAreaType")
		cbAreaType.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == "-" )
		self.on_wayland = "WAYLAND_DISPLAY" in os.environ or not isinstance(Gdk.Display.get_default(), GdkX11.X11Display)
		if self.on_wayland:
			self.builder.get_object("lblArea").set_text(_("Note: Mouse Region option is not available with Wayland-based display server"))
			self.builder.get_object("grArea").set_sensitive(False)
		
		# Remove options that are not applicable to currently editted input
		if self.editor.get_id() in STICKS:
			# Remove "Mouse Region", "Mouse" and "Mouse (Emulate Stick)" options
			# when editing stick bindings
			cb = self.builder.get_object("cbAxisOutput")
			for row in cb.get_model():
				if row[2] in ("wheel_pad", "area", "mouse", "mouse_pad"):
					cb.get_model().remove(row.iter)
		else:
			# Remove "Mouse" option when editing pads
			# (it's effectivelly same as Trackpad)
			cb = self.builder.get_object("cbAxisOutput")
			for row in cb.get_model():
				if row[2] in ("wheel_stick", "mouse_stick"):
					cb.get_model().remove(row.iter)
	
	
	def hidden(self):
		self.update_osd_area(None)
	
	
	def set_action(self, mode, action):
		if self.handles(mode, action):
			cb = self.builder.get_object("cbAxisOutput")
			if isinstance(action, AreaAction):
				self.load_area_action(action)
				self.set_cb(cb, "area", 2)
				self.update_osd_area(action)
				return
			self.update_osd_area(None)
			if isinstance(action, MouseAction):
				self.load_mouse_action(action)
			elif isinstance(action, CircularModifier):
				self.load_circular_action(action)
			elif isinstance(action, ButtonAction):
				self.load_button_action(action)
			elif isinstance(action, XYAction):
				if self.editor.friction > 0:
					self.load_mouse_action(action)
				else:
					p = [ None, None ]
					for x in (0, 1):
						if len(action.actions[0].strip().parameters) >= x:
							if len(action.actions[x].strip().parameters) > 0:
								p[x] = action.actions[x].strip().parameters[0]
					if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
						self.set_cb(cb, "lstick", 2)
					elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
						if isinstance(action, RelXYAction):
							self.set_cb(cb, "rstick_rel", 2)
						else:
							self.set_cb(cb, "rstick", 2)
					elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
						self.set_cb(cb, "wheel_pad", 2)
						self.set_cb(cb, "wheel_stick", 2)
			else:
				self.set_cb(cb, "none", 2)
	
	
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				if self.on_wayland:
					# Cannot display preview with non-X11 backends
					return
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
	
	
	def load_circular_action(self, action):
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		btCircularAxis = self.builder.get_object("btCircularAxis")
		btCircularButton0 = self.builder.get_object("btCircularButton0")
		btCircularButton1 = self.builder.get_object("btCircularButton1")
		
		# Turn action into list of subactions (even if it's just single action)
		if isinstance(action.action, MultiAction):
			actions = action.action.actions
		else:
			actions = [ action.action ]
		
		# Parse that list
		self.circular_axis, self.circular_buttons = NoAction(), [ None, None ]
		for action in actions:
			if isinstance(action, ButtonAction):
				self.circular_buttons = [ action.button, action.button2 ]
			else:
				self.circular_axis = action
		
		# Set labels
		b0, b1 = self.circular_buttons
		btCircularButton0.set_label(ButtonAction.describe_button(b0))
		btCircularButton1.set_label(ButtonAction.describe_button(b1))
		btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD))
		
		self.set_cb(cbAxisOutput, "circular", 2)
	
	
	def load_button_action(self, action):
		self.button = action
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		btSingleButton = self.builder.get_object("btSingleButton")
		btSingleButton.set_label(self.button.describe(Action.AC_PAD))
		self.set_cb(cbAxisOutput, "button", 2)
	
	
	def load_mouse_action(self, action):
		cbMouseOutput = self.builder.get_object("cbMouseOutput")
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		self._recursing = True
		if isinstance(action, MouseAction):
			self.set_cb(cbMouseOutput, "mouse", 1)
			self.set_cb(cbAxisOutput, "mouse", 2)
		elif isinstance(action, XYAction):
			if isinstance(action.x, AxisAction):
				if action.x.parameters[0] == Axes.ABS_X:
					self.set_cb(cbMouseOutput, "left", 1)
				else:
					self.set_cb(cbMouseOutput, "right", 1)
				self.set_cb(cbAxisOutput, "mouse", 2)
			elif isinstance(action.x, MouseAction):
				if self.editor.get_id() in STICKS:
					self.set_cb(cbAxisOutput, "wheel_stick", 2)
				else:
					self.set_cb(cbAxisOutput, "wheel_pad", 2)
		self._recursing = False
	
	
	def load_area_action(self, action):
		"""
		Load AreaAction values into UI.
		"""
		cbAreaType = self.builder.get_object("cbAreaType")
		
		x1, y1, x2, y2 = action.coords
		self.relative_area = False
		if isinstance(action, RelAreaAction):
			key = "screensize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		elif isinstance(action, RelWinAreaAction):
			key = "windowsize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		else:
			t1 = "1" if x1 < 0 and x2 < 0 else "0"
			t2 = "1" if y1 < 0 and y2 < 0 else "0"
			x1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2)
			if x2 < x1 : x1, x2 = x2, x1
			if y2 < y1 : y1, y2 = y2, y1
			if isinstance(action, WinAreaAction):
				key = "window-%s%s" % (t1, t2)
			else:
				key = "screen-%s%s" % (t1, t2)
		
		self._recursing = True
		self.builder.get_object("sbAreaX1").set_value(x1)
		self.builder.get_object("sbAreaY1").set_value(y1)
		self.builder.get_object("sbAreaX2").set_value(x2)
		self.builder.get_object("sbAreaY2").set_value(y2)
		self.builder.get_object("cbAreaOSDEnabled").set_active(self.editor.osd)
		self.builder.get_object("cbAreaClickEnabled").set_active(self.pressing_pad_clicks())
		for row in cbAreaType.get_model():
			if key == row[1]:
				cbAreaType.set_active_iter(row.iter)
				break
		self._recursing = False
	
	
	def on_btCircularAxis_clicked(self, *a):
		def cb(action):
			self.circular_axis = action
			btCircularAxis = self.builder.get_object("btCircularAxis")
			btCircularAxis.set_label(action.describe(Action.AC_PAD))
			self.editor.set_action(self.make_circular_action())
		
		b = SimpleChooser(self.app, "axis", cb)
		b.set_title(_("Select Axis"))
		b.display_action(Action.AC_STICK, self.circular_axis)
		b.show(self.editor.window)
	
	
	def on_btCircularButton_clicked(self, button, *a):
		index = 0 if button == self.builder.get_object("btCircularButton0") else 1
		def cb(action):
			self.circular_buttons[index] = action.button
			btCircularButton = self.builder.get_object("btCircularButton%s" % (index, ))
			btCircularButton.set_label(action.describe(Action.AC_PAD))
			self.editor.set_action(self.make_circular_action())
		
		b = SimpleChooser(self.app, "buttons", cb)
		b.set_title(_("Select Button"))
		b.display_action(Action.AC_STICK, self.circular_axis)
		b.show(self.editor.window)
	
	
	def on_btClearCircularAxis_clicked(self, *a):
		btCircularAxis = self.builder.get_object("btCircularAxis")
		self.circular_axis = NoAction()
		btCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD))
		self.editor.set_action(self.make_circular_action())
	
	
	def on_btClearCircularButtons_clicked(self, *a):
		btCircularButton0 = self.builder.get_object("btCircularButton0")
		btCircularButton1 = self.builder.get_object("btCircularButton1")
		self.circular_buttons = [ None, None ]
		btCircularButton0.set_label(NoAction().describe(Action.AC_PAD))
		btCircularButton1.set_label(NoAction().describe(Action.AC_PAD))
		self.editor.set_action(self.make_circular_action())
	
	
	def on_btSingleButton_clicked(self, *a):
		def cb(action):
			self.button = action
			btSingleButton = self.builder.get_object("btSingleButton")
			btSingleButton.set_label(self.button.describe(Action.AC_PAD))
			self.editor.set_action(self.button)
		
		b = SimpleChooser(self.app, "buttons", cb)
		b.set_title(_("Select Button"))
		b.display_action(Action.AC_STICK, self.circular_axis)
		b.show(self.editor.window)
	
	
	def on_cbAreaOSDEnabled_toggled(self, *a):
		self.editor.builder.get_object("cbOSD").set_active(
			self.builder.get_object("cbAreaOSDEnabled").get_active())
	
	
	def pressing_pad_clicks(self):
		"""
		Returns True if currently edited pad is set to press left mouse
		button when pressed.
		(yes, this is used somewhere)
		"""
		side = getattr(SCButtons, self.editor.get_id())
		c_action = self.app.current.buttons[side]
		if isinstance(c_action, ButtonAction):
			return c_action.button == Keys.BTN_LEFT
		return False
	
	
	def on_ok(self, action):
		if isinstance(action.strip(), AreaAction):
			# Kinda hacky way to set action on LPAD press or RPAD press
			# when user selects Mouse Area as ouput and checks
			# 'Pressing the Pad Clicks' checkbox
			side = getattr(SCButtons, self.editor.get_id())
			clicks = self.pressing_pad_clicks()
			
			if self.builder.get_object("cbAreaClickEnabled").get_active():
				if not clicks:
					# Turn pad press into mouse clicks
					self.app.set_action(self.app.current, side, ButtonAction(Keys.BTN_LEFT))
			else:
				if clicks:
					# Clear action created above if checkbox is uncheck
					self.app.set_action(self.app.current, side, NoAction())
	
	
	def on_mouse_options_changed(self, *a):
		if self._recursing : return
		action = self.make_mouse_action()
		self.editor.set_action(action)
	
	
	def make_mouse_action(self):
		"""
		Loads values from UI into trackball-related action
		"""
		cbMouseOutput = self.builder.get_object("cbMouseOutput")
		a_str = cbMouseOutput.get_model().get_value(cbMouseOutput.get_active_iter(), 2)
		return self.parser.restart(a_str).parse()
	
	
	def make_circular_action(self):
		"""
		Constructs Circular Modifier
		"""
		if self.circular_axis and any(self.circular_buttons):
			return CircularModifier(MultiAction(
				self.circular_axis, ButtonAction(*self.circular_buttons)))
		elif any(self.circular_buttons):
			return CircularModifier(ButtonAction(*self.circular_buttons))
		else:
			return CircularModifier(self.circular_axis)
	
	
	def make_area_action(self):
		"""
		Loads values from UI into new AreaAction or subclass.
		"""
		# Prepare
		cbAreaType = self.builder.get_object("cbAreaType")
		# Read numbers
		x1 = self.builder.get_object("sbAreaX1").get_value()
		y1 = self.builder.get_object("sbAreaY1").get_value()
		x2 = self.builder.get_object("sbAreaX2").get_value()
		y2 = self.builder.get_object("sbAreaY2").get_value()
		# Determine exact action type by looking into Area Type checkbox
		# (this part may seem little crazy)
		# ... numbers
		key = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1)
		if "-" in key:
			if key[-2] == "1":
				# Before-last character ius "1", that means that X coords are
				# counted from other side and has to be negated
				x1, x2 = -x1, -x2
			if key[-1] == "1":
				# Key ends with "1". Same thing as above but for Y coordinate
				y1, y2 = -y1, -y2
		if "size" in key:
			x1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0
		# ... class 
		if "window-" in key:
			cls = WinAreaAction
			self.relative_area = False
		elif "screensize" == key:
			cls = RelAreaAction
			self.relative_area = True
		elif "windowsize" == key:
			cls = RelWinAreaAction
			self.relative_area = True
		else: # "screen" in key
			cls = AreaAction
			self.relative_area = False
		if not self.relative_area:
			x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
			
		return cls(x1, y1, x2, y2)
	
	
	def get_button_title(self):
		return _("Joystick / Mouse")
	
	
	def handles(self, mode, action):
		if isinstance(action, (NoAction, MouseAction, CircularModifier,
					InvalidAction, AreaAction, ButtonAction)):
			return True
		if isinstance(action, BallModifier):
			if isinstance(action.action, XYAction):
				return (
					isinstance(action.action.x, (AxisAction, MouseAction))
					and isinstance(action.action.x, (AxisAction, MouseAction))
				)
			return isinstance(action.action, MouseAction)
		if isinstance(action, XYAction):
			p = [ None, None ]
			for x in (0, 1):
				if len(action.actions[0].strip().parameters) >= x:
					if len(action.actions[x].strip().parameters) > 0:
						p[x] = action.actions[x].strip().parameters[0]
			if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
				return True
			elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
				return True
			elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
				return True
			elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
				return True
		return False
	
	
	def on_area_options_changed(self, *a):
		if self._recursing : return
		action = self.make_area_action()
		self.editor.set_action(action)
		self.update_osd_area(action)
		for x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'):
			spin = self.builder.get_object(x)
			if self.relative_area:
				spin.get_adjustment().set_upper(100)
			else:
				spin.get_adjustment().set_upper(1000)
			self.on_sbArea_output(spin)
	
	
	def on_sbArea_output(self, button, *a):
		if self.relative_area:
			button.set_text("%s %%" % (button.get_value()))
		else:
			button.set_text("%s px" % (int(button.get_value())))
	
	
	def on_sbArea_focus_out_event(self, button, *a):
		GLib.idle_add(self.on_sbArea_output, button)
	
	
	def on_sbArea_changed(self, button, *a):
		self.on_sbArea_output(button)
		self.on_area_options_changed(button)
	
	
	def on_lblSetupTrackball_activate_link(self, trash, link):
		self.editor.on_link(link)
	
	
	def on_cbAxisOutput_changed(self, *a):
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		stActionData = self.builder.get_object("stActionData")
		key = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 2)
		if key == 'area':
			stActionData.set_visible_child(self.builder.get_object("grArea"))
			action = self.make_area_action()
			self.update_osd_area(action)
		elif key == "button":
			stActionData.set_visible_child(self.builder.get_object("vbButton"))
			self.button = self.button or ButtonAction(Keys.BTN_GAMEPAD)
			action = self.button
		elif key == "circular":
			stActionData.set_visible_child(self.builder.get_object("grCircular"))
			action = self.make_circular_action()
		elif key == 'mouse':
			stActionData.set_visible_child(self.builder.get_object("vbMose"))
			if self.editor.friction == 0:
				# When switching to mouse, enable trackball by default
				self.editor.friction = 10
			action = self.make_mouse_action()
		else:
			stActionData.set_visible_child(self.builder.get_object("nothing"))
			action = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 0)
			action = self.parser.restart(action).parse()
			self.update_osd_area(None)
		
		self.editor.set_action(action)
Beispiel #16
0
class AxisActionComponent(AEComponent, TimerManager):
	GLADE = "ae/axis_action.glade"
	NAME = "axis_action"
	CTXS = Action.AC_STICK | Action.AC_PAD
	PRIORITY = 3
	
	def __init__(self, app, editor):
		AEComponent.__init__(self, app, editor)
		TimerManager.__init__(self)
		self._recursing = False
		self.relative_area = False
		self.osd_area_instance = None
		self.on_wayland = False
		self.circular = MouseAction(Rels.REL_WHEEL)
		self.parser = GuiActionParser()
	
	
	def load(self):
		if self.loaded : return
		AEComponent.load(self)
		cbAreaType = self.builder.get_object("cbAreaType")
		cbAreaType.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == "-" )
		self.on_wayland = "WAYLAND_DISPLAY" in os.environ or not isinstance(Gdk.Display.get_default(), GdkX11.X11Display)
		if self.on_wayland:
			self.builder.get_object("lblArea").set_text(_("Note: Mouse Region option is not available with Wayland-based display server"))
			self.builder.get_object("grArea").set_sensitive(False)
		
		# Remove options that are not applicable to currently editted input
		if self.editor.get_id() in STICKS:
			# Remove "Mouse Region", "Trackball", "Trackpad"
			# and "Mouse (Emulate Stick)" options when editing stick bindings
			cb = self.builder.get_object("cbAxisOutput")
			for row in cb.get_model():
				if row[2] in ("wheel_pad", "area", "mouse_pad", "trackpad", "trackball"):
					cb.get_model().remove(row.iter)
		else:
			# Remove "Mouse" option when editing pads
			# (it's effectivelly same as Trackpad)
			cb = self.builder.get_object("cbAxisOutput")
			for row in cb.get_model():
				if row[2] in ("wheel_stick", "mouse_stick", ):
					cb.get_model().remove(row.iter)
	
	
	def hidden(self):
		self.update_osd_area(None)
	
	
	def set_action(self, mode, action):
		if self.handles(mode, action):
			cb = self.builder.get_object("cbAxisOutput")
			if isinstance(action, AreaAction):
				self.load_area_action(action)
				self.set_cb(cb, "area", 2)
				self.update_osd_area(action)
				return
			self.update_osd_area(None)
			if isinstance(action, MouseAction):
				self.set_cb(cb, "trackpad", 2)
			elif isinstance(action, BallModifier):
				self.load_trackball_action(action)
			elif isinstance(action, CircularModifier):
				self.load_circular_action(action)
			elif isinstance(action, XYAction):
				p = [ None, None ]
				for x in (0, 1):
					if len(action.actions[0].strip().parameters) >= x:
						if len(action.actions[x].strip().parameters) > 0:
							p[x] = action.actions[x].strip().parameters[0]
				if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
					self.set_cb(cb, "lstick", 2)
				elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
					self.set_cb(cb, "rstick", 2)
				elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
					self.set_cb(cb, "wheel", 2)
			else:
				self.set_cb(cb, "none", 2)
	
	
	def update_osd_area(self, action):
		""" Updates preview area displayed on screen """
		if action:
			if self.osd_area_instance is None:
				if self.on_wayland:
					# Cannot display preview with non-X11 backends
					return
				self.osd_area_instance = Area()
				self.osd_area_instance.show()
			action.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))
			self.timer("area", 0.5, self.update_osd_area, action)
		elif self.osd_area_instance:
			self.osd_area_instance.quit()
			self.osd_area_instance = None
			self.cancel_timer("area")
	
	
	def load_circular_action(self, action):
		self.circular = action.action
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		btCircularAxis = self.builder.get_object("btCircularAxis")
		btCircularAxis.set_label(self.circular.describe(Action.AC_PAD))
		self.set_cb(cbAxisOutput, "circular", 2)
	
	
	def load_trackball_action(self, action):
		cbTracballOutput = self.builder.get_object("cbTracballOutput")
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		sclFriction = self.builder.get_object("sclFriction")
		self._recursing = True
		if isinstance(action.action, MouseAction):
			self.set_cb(cbTracballOutput, "mouse", 1)
			self.set_cb(cbAxisOutput, "trackball", 2)
		elif isinstance(action.action, XYAction):
			if isinstance(action.action.x, AxisAction):
				if action.action.x.parameters[0] == Axes.ABS_X:
					self.set_cb(cbTracballOutput, "left", 1)
				else:
					self.set_cb(cbTracballOutput, "right", 1)
				self.set_cb(cbAxisOutput, "trackball", 2)
			elif isinstance(action.action.x, MouseAction):
				self.set_cb(cbAxisOutput, "wheel", 2)
		if action.friction <= 0:
			sclFriction.set_value(0)
		else:
			sclFriction.set_value(math.log(action.friction * 1000.0, 10))
		self._recursing = False
	
	
	def load_area_action(self, action):
		"""
		Load AreaAction values into UI.
		"""
		cbAreaType = self.builder.get_object("cbAreaType")
		
		x1, y1, x2, y2 = action.coords
		self.relative_area = False
		if isinstance(action, RelAreaAction):
			key = "screensize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		elif isinstance(action, RelWinAreaAction):
			key = "windowsize"
			self.relative_area = True
			x1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0
		else:
			t1 = "1" if x1 < 0 and x2 < 0 else "0"
			t2 = "1" if y1 < 0 and y2 < 0 else "0"
			x1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2)
			if x2 < x1 : x1, x2 = x2, x1
			if y2 < y1 : y1, y2 = y2, y1
			if isinstance(action, WinAreaAction):
				key = "window-%s%s" % (t1, t2)
			else:
				key = "screen-%s%s" % (t1, t2)
		
		self._recursing = True
		self.builder.get_object("sbAreaX1").set_value(x1)
		self.builder.get_object("sbAreaY1").set_value(y1)
		self.builder.get_object("sbAreaX2").set_value(x2)
		self.builder.get_object("sbAreaY2").set_value(y2)
		self.builder.get_object("cbAreaOSDEnabled").set_active(self.editor.osd)
		self.builder.get_object("cbAreaClickEnabled").set_active(self.pressing_pad_clicks())
		for row in cbAreaType.get_model():
			if key == row[1]:
				cbAreaType.set_active_iter(row.iter)
				break
		self._recursing = False
	
	
	def on_btCircularAxis_clicked(self, *a):
		def cb(action):
			self.circular = action
			btCircularAxis = self.builder.get_object("btCircularAxis")
			btCircularAxis.set_label(action.describe(Action.AC_PAD))
			self.editor.set_action(self.make_circular_action())
		
		b = SimpleChooser(self.app, "axis", cb)
		b.set_title(_("Select Axis"))
		b.display_action(Action.AC_STICK, self.circular)
		b.show(self.editor.window)
	
	
	def on_cbAreaOSDEnabled_toggled(self, *a):
		self.editor.builder.get_object("cbOSD").set_active(
			self.builder.get_object("cbAreaOSDEnabled").get_active())
	
	
	def pressing_pad_clicks(self):
		"""
		Returns True if currently edited pad is set to press left mouse
		button when pressed.
		(yes, this is used somewhere)
		"""
		side = getattr(SCButtons, self.editor.get_id())
		c_action = self.app.current.buttons[side]
		if isinstance(c_action, ButtonAction):
			return c_action.button == Keys.BTN_LEFT
		return False
	
	
	def on_ok(self, action):
		if isinstance(action.strip(), AreaAction):
			# Kinda hacky way to set action on LPAD press or RPAD press
			# when user selects Mouse Area as ouput and checks
			# 'Pressing the Pad Clicks' checkbox
			side = getattr(SCButtons, self.editor.get_id())
			clicks = self.pressing_pad_clicks()
			
			if self.builder.get_object("cbAreaClickEnabled").get_active():
				if not clicks:
					# Turn pad press into mouse clicks
					self.app.set_action(self.app.current, side, ButtonAction(Keys.BTN_LEFT))
			else:
				if clicks:
					# Clear action created above if checkbox is uncheck
					self.app.set_action(self.app.current, side, NoAction())
	
	
	def make_trackball_action(self):
		"""
		Loads values from UI into trackball-related action
		"""
		sclFriction = self.builder.get_object("sclFriction")
		
		cbTracballOutput = self.builder.get_object("cbTracballOutput")
		a_str = cbTracballOutput.get_model().get_value(cbTracballOutput.get_active_iter(), 2)
		a = self.parser.restart(a_str).parse()
		if sclFriction.get_value() <= 0:
			friction = 0
		else:
			friction = ((10.0**sclFriction.get_value())/1000.0)
		return BallModifier(round(friction, 3), a)
	
	
	def make_circular_action(self):
		"""
		Constructs Circular Modifier
		"""
		return CircularModifier(self.circular)
	
	
	def make_area_action(self):
		"""
		Loads values from UI into new AreaAction or subclass.
		"""
		# Prepare
		cbAreaType = self.builder.get_object("cbAreaType")
		# Read numbers
		x1 = self.builder.get_object("sbAreaX1").get_value()
		y1 = self.builder.get_object("sbAreaY1").get_value()
		x2 = self.builder.get_object("sbAreaX2").get_value()
		y2 = self.builder.get_object("sbAreaY2").get_value()
		# Determine exact action type by looking into Area Type checkbox
		# (this part may seem little crazy)
		# ... numbers
		key = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1)
		if "-" in key:
			if key[-2] == "1":
				# Before-last character ius "1", that means that X coords are
				# counted from other side and has to be negated
				x1, x2 = -x1, -x2
			if key[-1] == "1":
				# Key ends with "1". Same thing as above but for Y coordinate
				y1, y2 = -y1, -y2
		if "size" in key:
			x1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0
		# ... class 
		if "window-" in key:
			cls = WinAreaAction
			self.relative_area = False
		elif "screensize" == key:
			cls = RelAreaAction
			self.relative_area = True
		elif "windowsize" == key:
			cls = RelWinAreaAction
			self.relative_area = True
		else: # "screen" in key
			cls = AreaAction
			self.relative_area = False
		if not self.relative_area:
			x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
			
		return cls(x1, y1, x2, y2)
	
	
	def get_button_title(self):
		return _("Joystick / Mouse")
	
	
	def handles(self, mode, action):
		if isinstance(action, (NoAction, MouseAction, CircularModifier,
					InvalidAction, AreaAction)):
			return True
		if isinstance(action, BallModifier):
			if isinstance(action.action, XYAction):
				return (
					isinstance(action.action.x, (AxisAction, MouseAction))
					and isinstance(action.action.x, (AxisAction, MouseAction))
				)
			return isinstance(action.action, MouseAction)
		if isinstance(action, XYAction):
			p = [ None, None ]
			for x in (0, 1):
				if len(action.actions[0].strip().parameters) >= x:
					if len(action.actions[x].strip().parameters) > 0:
						p[x] = action.actions[x].strip().parameters[0]
			if p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:
				return True
			elif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:
				return True
			elif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:
				return True
			elif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:
				return True
		return False
	
	
	def on_area_options_changed(self, *a):
		if self._recursing : return
		action = self.make_area_action()
		self.editor.set_action(action)
		self.update_osd_area(action)
		for x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'):
			spin = self.builder.get_object(x)
			if self.relative_area:
				spin.get_adjustment().set_upper(100)
			else:
				spin.get_adjustment().set_upper(1000)
			self.on_sbArea_output(spin)
	
	
	def on_trackball_options_changed(self, *a):
		if self._recursing : return
		action = self.make_trackball_action()
		self.editor.set_action(action)
	
	
	def on_sbArea_output(self, button, *a):
		if self.relative_area:
			button.set_text("%s %%" % (button.get_value()))
		else:
			button.set_text("%s px" % (int(button.get_value())))
	
	
	def on_sbArea_focus_out_event(self, button, *a):
		GLib.idle_add(self.on_sbArea_output, button)
	
	
	def on_sbArea_changed(self, button, *a):
		self.on_sbArea_output(button)
		self.on_area_options_changed(button)
	
	
	def on_btClearFriction_clicked(self, *a):
		sclFriction = self.builder.get_object("sclFriction")
		sclFriction.set_value(math.log(10 * 1000.0, 10))
	
	
	def on_sclFriction_format_value(self, scale, value):
		if value <= 0:
			return "0.000"
		elif value >= 6:
			return "1000.00"
		else:
			return "%0.3f" % ((10.0**value)/1000.0)
		
	
	def on_cbAxisOutput_changed(self, *a):
		cbAxisOutput = self.builder.get_object("cbAxisOutput")
		stActionData = self.builder.get_object("stActionData")
		key = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 2)
		if key == 'area':
			stActionData.set_visible_child(self.builder.get_object("grArea"))
			action = self.make_area_action()
			self.update_osd_area(action)
		elif key == "circular":
			stActionData.set_visible_child(self.builder.get_object("vbCircular"))
			action = self.make_circular_action()
		elif key == 'trackball':
			stActionData.set_visible_child(self.builder.get_object("vbTrackball"))
			action = self.make_trackball_action()
		else:
			stActionData.set_visible_child(self.builder.get_object("nothing"))
			action = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 0)
			action = self.parser.restart(action).parse()
			self.update_osd_area(None)
		
		self.editor.set_action(action)
Beispiel #17
0
	def on_unknown_message(self, daemon, message):
		if not message.startswith("OSD:"):
			return
		if message.startswith("OSD: message"):
			args = shsplit(message)[1:]
			m = Message()
			m.parse_argumets(args)
			hsh = m.hash()
			if hsh in self._visible_messages:
				self._visible_messages[hsh].extend()
				m.destroy()
			else:
				# TODO: Do this only for default position once changing
				# TODO: is allowed
				if len(self._visible_messages):
					height = self._visible_messages.values()[0].get_size().height
					x, y = m.position
					while y in [ i.position[1] for i in self._visible_messages.values() ]:
						y -= height + 5
					m.position = x, y
				m.show()
				self._visible_messages[hsh] = m
				m.connect("destroy", self.on_message_closed)
		elif message.startswith("OSD: keyboard"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = Keyboard(self.config)
				self._window.connect('destroy', self.on_keyboard_closed)
				self._window.parse_argumets(args)
				self._window.show()
				self._window.use_daemon(self.daemon)
		elif message.startswith("OSD: gesture"):
			if self._window:
				log.warning("Another OSD is already visible - refusing to show keyboard")
			else:
				args = shsplit(message)[1:]
				self._window = GestureDisplay(self.config)
				self._window.parse_argumets(args)
				self._window.use_daemon(self.daemon)
				self._window.show()
				self._window.connect('destroy', self.on_gesture_recognized)
		elif self._is_menu_message(message):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show menu")
			else:
				if message.startswith("OSD: hmenu"):
					self._window = HorizontalMenu()
				elif message.startswith("OSD: radialmenu"):
					self._window = RadialMenu()
				elif message.startswith("OSD: quickmenu"):
					self._window = QuickMenu()
				elif message.startswith("OSD: gridmenu"):
					self._window = GridMenu()
				elif message.startswith("OSD: dialog"):
					self._window = Dialog()
				else:
					self._window = Menu()
				self._window.connect('destroy', self.on_menu_closed)
				self._window.use_config(self.config)
				try:
					if self._window.parse_argumets(args):
						self._window.show()
						self._window.use_daemon(self.daemon)
					else:
						log.error("Failed to show menu")
						self._window = None
				except:
					log.error(traceback.format_exc())
					log.error("Failed to show menu")
					self._window = None
		elif message.startswith("OSD: area"):
			args = shsplit(message)[1:]
			if self._window:
				log.warning("Another OSD is already visible - refusing to show area")
			else:
				args = shsplit(message)[1:]
				self._window = Area()
				self._window.connect('destroy', self.on_keyboard_closed)
				if self._window.parse_argumets(args):
					self._window.show()
				else:
					self._window.quit()
					self._window = None
		elif message.startswith("OSD: clear"):
			# Clears active OSD windows
			self.clear_windows()
		else:
			log.warning("Unknown command from daemon: '%s'", message)