Esempio n. 1
0
class Menu(OSDWindow):
    EPILOG = """Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled menu
  -2  - clean exit, menu closed from callback method
   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)
	"""
    SUBMENU_OFFSET = 50

    def __init__(self, cls="osd-menu"):
        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.xdisplay = X.Display(hash(
            GdkX11.x11_get_default_xdisplay()))  # Magic

        cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
        self.cursor = Gtk.Image.new_from_file(cursor)
        self.cursor.set_name("osd-menu-cursor")

        self.parent = self.create_parent()
        self.f = Gtk.Fixed()
        self.f.add(self.parent)
        self.add(self.f)

        self._submenu = None
        self._scon = StickController()
        self._scon.connect("direction", self.on_stick_direction)
        self._is_submenu = False
        self._selected = None
        self._menuid = None
        self._use_cursor = False
        self._eh_ids = []
        self._control_with = STICK
        self._confirm_with = 'A'
        self._cancel_with = 'B'

    def set_is_submenu(self):
        """
		Marks menu as submenu. This changes behaviour of some methods,
		especially disables (un)locking of input stick and buttons.
		"""
        self._is_submenu = True

    def create_parent(self):
        v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        v.set_name("osd-menu")
        return v

    def pack_items(self, parent, items):
        for item in items:
            parent.pack_start(item.widget, True, True, 0)

    def use_daemon(self, d):
        """
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
        self.daemon = d
        if not self._is_submenu:
            self._cononect_handlers()
            self.on_daemon_connected(self.daemon)

    def use_config(self, c):
        """
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
        self.config = c

    def get_menuid(self):
        """
		Returns ID of used menu.
		"""
        return self._menuid

    def get_selected_item_id(self):
        """
		Returns ID of selected item or None if nothing is selected.
		"""
        if self._selected:
            return self._selected.id
        return None

    def _add_arguments(self):
        OSDWindow._add_arguments(self)
        self.argparser.add_argument(
            '--control-with',
            '-c',
            type=str,
            metavar="option",
            default=STICK,
            choices=(LEFT, RIGHT, STICK),
            help=
            "which pad or stick should be used to navigate menu (default: %s)"
            % (STICK, ))
        self.argparser.add_argument(
            '--confirm-with',
            type=str,
            metavar="button",
            default='A',
            help="button used to confirm choice (default: A)")
        self.argparser.add_argument(
            '--cancel-with',
            type=str,
            metavar="button",
            default='B',
            help="button used to cancel menu (default: B)")
        self.argparser.add_argument(
            '--confirm-with-release',
            action='store_true',
            help="confirm choice with button release instead of button press")
        self.argparser.add_argument(
            '--cancel-with-release',
            action='store_true',
            help="cancel menu with button release instead of button press")
        self.argparser.add_argument('--use-cursor',
                                    '-u',
                                    action='store_true',
                                    help="display and use cursor")
        self.argparser.add_argument('--from-profile',
                                    '-p',
                                    type=str,
                                    metavar="profile_file menu_name",
                                    help="load menu items from profile file")
        self.argparser.add_argument('--from-file',
                                    '-f',
                                    type=str,
                                    metavar="filename",
                                    help="load menu items from json file")
        self.argparser.add_argument('--print-items',
                                    action='store_true',
                                    help="prints menu items to stdout")
        self.argparser.add_argument('items',
                                    type=str,
                                    nargs='*',
                                    metavar='id title',
                                    help="Menu items")

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.config:
            self.config = Config()
        if self.args.from_profile:
            try:
                self._menuid = self.args.items[0]
                self.items = MenuData.from_profile(self.args.from_profile,
                                                   self._menuid)
            except IOError:
                print >> sys.stderr, '%s: error: profile file not found' % (
                    sys.argv[0])
                return False
            except ValueError:
                print >> sys.stderr, '%s: error: menu not found' % (
                    sys.argv[0])
                return False
        elif self.args.from_file:
            #try:
            data = json.loads(open(self.args.from_file, "r").read())
            self._menuid = self.args.from_file
            self.items = MenuData.from_json_data(data)
            #except:
            #	print >>sys.stderr, '%s: error: failed to load menu file' % (sys.argv[0])
            #	return False
        else:
            try:
                self.items = MenuData.from_args(self.args.items)
                self._menuid = None
            except ValueError:
                print >> sys.stderr, '%s: error: invalid number of arguments' % (
                    sys.argv[0])
                return False

        # Parse simpler arguments
        self._control_with = self.args.control_with
        self._confirm_with = self.args.confirm_with
        self._cancel_with = self.args.cancel_with

        if self.args.use_cursor:
            self.enable_cursor()

        # Create buttons that are displayed on screen
        self.items = self.items.generate(self)
        for item in self.items:
            item.widget = self.generate_widget(item)
        self.pack_items(self.parent, self.items)
        if len(self.items) == 0:
            print >> sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
            return False

        if self.args.print_items:
            max_id_len = max(*[len(x.id) for x in self.items])
            row_format = "{:>%s}:\t{}" % (max_id_len, )
            for item in self.items:
                print row_format.format(item.id, item.label)
        return True

    def enable_cursor(self):
        if not self._use_cursor:
            self.f.add(self.cursor)
            self.f.show_all()
            self._use_cursor = True

    def generate_widget(self, item):
        """ Generates gtk widget for specified menutitem """
        if isinstance(item, Separator) and item.label:
            widget = Gtk.Button.new_with_label(item.label)
            widget.set_relief(Gtk.ReliefStyle.NONE)
            widget.set_name("osd-menu-separator")
            return widget
        elif isinstance(item, Separator):
            widget = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
            widget.set_name("osd-menu-separator")
            return widget
        else:
            widget = Gtk.Button.new_with_label(item.label)
            widget.set_relief(Gtk.ReliefStyle.NONE)
            if hasattr(widget.get_children()[0], "set_xalign"):
                widget.get_children()[0].set_xalign(0)
            else:
                widget.get_children()[0].set_halign(Gtk.Align.START)
            if isinstance(item, Submenu):
                item.callback = self.show_submenu
                label1 = widget.get_children()[0]
                label2 = Gtk.Label(_(">>"))
                label2.set_property("margin-left", 30)
                box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
                widget.remove(label1)
                box.pack_start(label1, True, True, 1)
                box.pack_start(label2, False, True, 1)
                widget.add(box)
                widget.set_name("osd-menu-item")
            elif item.id is None:
                widget.set_name("osd-menu-dummy")
            else:
                widget.set_name("osd-menu-item")
            return widget

    def select(self, index):
        if self._selected:
            self._selected.widget.set_name("osd-menu-item")
        if self.items[index].id:
            self._selected = self.items[index]
            self._selected.widget.set_name("osd-menu-item-selected")
            return True
        return False

    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 show(self, *a):
        if not self.select(0):
            self.next_item(1)
        OSDWindow.show(self, *a)

    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.error("Sucessfully locked input")
            pass

        if not self.config:
            self.config = Config()
        locks = [self._control_with, self._confirm_with, self._cancel_with]
        self.daemon.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if not self._is_submenu:
            self.daemon.unlock_all()
            for x in self._eh_ids:
                self.daemon.disconnect(x)
            self._eh_ids = []
        OSDWindow.quit(self, code)

    def next_item(self, direction):
        """ Selects next menu item, based on self._direction """
        start, i = -1, 0
        try:
            start = self.items.index(self._selected)
            i = start + direction
        except:
            pass
        while True:
            if i == start:
                # Cannot find valid menu item
                self.select(start)
                break
            if i >= len(self.items):
                i = 0
                continue
            if i < 0:
                i = len(self.items) - 1
                continue
            if self.select(i):
                # Not a separator
                break
            i += direction
            if start < 0: start = 0

    def on_submenu_closed(self, *a):
        if self._submenu.get_exit_code() in (0, -2):
            self.quit(self._submenu.get_exit_code())
            self._selected = self._submenu._selected
        self._submenu = None

    def show_submenu(self, trash, trash2, menuitem):
        """ Called when user chooses menu item pointing to submenu """
        filename = find_menu(menuitem.filename)
        if filename:
            self._submenu = self.__class__()
            sub_pos = list(self.position)
            for i in (0, 1):
                sub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET
                              if sub_pos[i] < 0 else sub_pos[i] +
                              self.SUBMENU_OFFSET)

            self._submenu.use_config(self.config)
            self._submenu.parse_argumets([
                "menu.py", "-x",
                str(sub_pos[0]), "-y",
                str(sub_pos[1]), "--from-file", filename, "--control-with",
                self._control_with, "--confirm-with", self._confirm_with,
                "--cancel-with", self._cancel_with
            ])
            self._submenu.set_is_submenu()
            self._submenu.use_daemon(self.daemon)
            self._submenu.connect('destroy', self.on_submenu_closed)
            self._submenu.show()

    def _control_equals_cancel(self, daemon, x, y):
        """
		Called by on_event in that very special case when both confirm_with
		and cancel_with are set to STICK.
		
		Separated because RadialMenu overrides on_event and still
		needs to call this.
		
		Returns True if menu was canceled.
		"""
        distance = sqrt(x * x + y * y)
        if distance < STICK_PAD_MAX / 8:
            self.quit(-1)
            return True
        return False

    def on_stick_direction(self, trash, x, y):
        if y != 0:
            self.next_item(y)

    def on_event(self, daemon, what, data):
        if self._submenu:
            return self._submenu.on_event(daemon, what, data)
        if what == self._control_with:
            x, y = data
            if self._use_cursor:
                # Special case, both confirm_with and cancel_with
                # can be set to STICK
                if self._cancel_with == STICK and self._control_with == STICK:
                    if self._control_equals_cancel(daemon, x, y):
                        return

                max_w = self.get_allocation().width - (
                    self.cursor.get_allocation().width * 0.8)
                max_h = self.get_allocation().height - (
                    self.cursor.get_allocation().height * 1.0)
                x = ((x / (STICK_PAD_MAX * 2.0)) + 0.5) * max_w
                y = (0.5 - (y / (STICK_PAD_MAX * 2.0))) * max_h

                x -= self.cursor.get_allocation().width * 0.5
                y -= self.cursor.get_allocation().height * 0.5

                self.f.move(self.cursor, int(x), int(y))
                for i in self.items:
                    if point_in_gtkrect(i.widget.get_allocation(), x, y):
                        self.select(self.items.index(i))
            else:
                self._scon.set_stick(x, y)
        elif what == self._cancel_with:
            if data[0] == 0:  # Button released
                self.quit(-1)
        elif what == self._confirm_with:
            if data[0] == 0:  # Button released
                if self._selected and self._selected.callback:
                    self._selected.callback(self, self.daemon, self._selected)
                elif self._selected:
                    self.quit(0)
                else:
                    self.quit(-1)
Esempio n. 2
0
class Menu(OSDWindow, TimerManager):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled menu
   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)
	"""
	REPEAT_DELAY = 0.5
	
	def __init__(self):
		OSDWindow.__init__(self, "osd-menu")
		TimerManager.__init__(self)
		self.daemon = None
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursor = Gtk.Image.new_from_file(cursor)
		self.cursor.set_name("osd-menu-cursor")
		
		self.parent = self.create_parent()
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
		
		self._direction = 0		# Movement direction
		self._selected = None
		self._menuid = None
		self._use_cursor = False
		self._eh_ids = []
		self._control_with = STICK
		self._confirm_with = 'A'
		self._cancel_with = 'B'
	
	
	def create_parent(self):
		v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		v.set_name("osd-menu")
		return v
	
	
	def pack_items(self, parent, items):
		for item in items:
			parent.pack_start(item.widget, True, True, 0)
	
	
	def use_daemon(self, d):
		"""
		Allows (re)using already existin DaemonManager instance in same process
		"""
		self.daemon = d
		self._cononect_handlers()
		self.on_daemon_connected(self.daemon)
	
	
	def get_menuid(self):
		"""
		Returns ID of used menu.
		"""
		return self._menuid
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		if self._selected:
			return self._selected.id
		return None
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--control-with', '-c', type=str,
			metavar="option", default=STICK, choices=(LEFT, RIGHT, STICK),
			help="which pad or stick should be used to navigate menu (default: %s)" % (STICK,))
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default='A',
			help="button used to confirm choice (default: A)")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default='B',
			help="button used to cancel menu (default: B)")
		self.argparser.add_argument('--confirm-with-release', action='store_true',
			help="confirm choice with button release instead of button press")
		self.argparser.add_argument('--cancel-with-release', action='store_true',
			help="cancel menu with button release instead of button press")
		self.argparser.add_argument('--use-cursor', '-u', action='store_true',
			help="display and use cursor")
		self.argparser.add_argument('--from-profile', '-p', type=str,
			metavar="profile_file menu_name",
			help="load menu items from profile file")
		self.argparser.add_argument('--from-file', '-f', type=str,
			metavar="filename",
			help="load menu items from json file")
		self.argparser.add_argument('items', type=str, nargs='*', metavar='id title',
			help="Menu items")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if self.args.from_profile:
			try:
				self._menuid = self.args.items[0]
				self.items = MenuData.from_profile(self.args.from_profile, self._menuid)
			except IOError:
				print >>sys.stderr, '%s: error: profile file not found' % (sys.argv[0])
				return False
			except ValueError:
				print >>sys.stderr, '%s: error: menu not found' % (sys.argv[0])
				return False
		elif self.args.from_file:
			try:
				data = json.loads(open(self.args.from_file, "r").read())
				self._menuid = self.args.from_file
				self.items = MenuData.from_json_data(data)
			except:
				print >>sys.stderr, '%s: error: failed to loade menu file' % (sys.argv[0])
				return False
		else:
			try:
				self.items = MenuData.from_args(self.args.items)
				self._menuid = None
			except ValueError:
				print >>sys.stderr, '%s: error: invalid number of arguments' % (sys.argv[0])
				return False
		
		# Parse simpler arguments
		self._control_with = self.args.control_with
		self._confirm_with = self.args.confirm_with
		self._cancel_with = self.args.cancel_with
		
		if self.args.use_cursor:
			self.f.add(self.cursor)
			self.f.show_all()
			self._use_cursor = True
		
		# Create buttons that are displayed on screen
		for item in self.items:
			item.widget = Gtk.Button.new_with_label(item.label)
			item.widget.set_name("osd-menu-item")
			item.widget.set_relief(Gtk.ReliefStyle.NONE)
		self.pack_items(self.parent, self.items)
		if len(self.items) == 0:
			print >>sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
			return False
		return True
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name("osd-menu-item")
		self._selected = self.items[index]
		self._selected.widget.set_name("osd-menu-item-selected")
	
	
	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 show(self, *a):
		self.select(0)
		OSDWindow.show(self, *a)
	
	
	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.error("Sucessfully locked input")
			pass
		
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		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 = []
		OSDWindow.quit(self, code)
	
	
	def on_move(self):
		i = self.items.index(self._selected) + self._direction
		if i >= len(self.items):
			i = 0
		elif i < 0:
			i = len(self.items) - 1
		self.select(i)
		self.timer("move", self.REPEAT_DELAY, self.on_move)
	
	
	def on_event(self, daemon, what, data):
		if what == self._control_with:
			x, y = data
			if self._use_cursor:
				max_w = self.get_allocation().width - (self.cursor.get_allocation().width * 0.8)
				max_h = self.get_allocation().height - (self.cursor.get_allocation().height * 1.0)
				x = ((x / (STICK_PAD_MAX * 2.0)) + 0.5) * max_w
				y = (0.5 - (y / (STICK_PAD_MAX * 2.0))) * max_h
				
				x -= self.cursor.get_allocation().width * 0.5
				y -= self.cursor.get_allocation().height * 0.5
				
				self.f.move(self.cursor, int(x), int(y))
				for i in self.items:
					if point_in_gtkrect(i.widget.get_allocation(), x, y):
						self.select(self.items.index(i))
			else:
				if y < STICK_PAD_MIN / 3 and self._direction != 1:
					self._direction = 1
					self.on_move()
				if y > STICK_PAD_MAX / 3 and self._direction != -1:
					self._direction = -1
					self.on_move()
				if y < STICK_PAD_MAX / 3 and y > STICK_PAD_MIN / 3 and self._direction != 0:
					self._direction = 0
					self.cancel_timer("move")
		elif what == self._cancel_with:
			if data[0] == 0:	# Button released
				self.quit(-1)
		elif what == self._confirm_with:
			if data[0] == 0:	# Button released
				self.quit(0)
Esempio n. 3
0
class Menu(OSDWindow, TimerManager):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled menu
  -2  - clean exit, menu closed from callback method
   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)
	"""
	REPEAT_DELAY = 0.5
	SUBMENU_OFFSET = 50
	
	def __init__(self, cls="osd-menu"):
		OSDWindow.__init__(self, cls)
		TimerManager.__init__(self)
		self.daemon = None
		self.config = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursor = Gtk.Image.new_from_file(cursor)
		self.cursor.set_name("osd-menu-cursor")
		
		self.parent = self.create_parent()
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
		
		self._submenu = None
		self._is_submenu = False
		self._direction = 0		# Movement direction
		self._selected = None
		self._menuid = None
		self._use_cursor = False
		self._eh_ids = []
		self._control_with = STICK
		self._confirm_with = 'A'
		self._cancel_with = 'B'
	
	
	def set_is_submenu(self):
		"""
		Marks menu as submenu. This changes behaviour of some methods,
		especially disables (un)locking of input stick and buttons.
		"""
		self._is_submenu = True
	
	def create_parent(self):
		v = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		v.set_name("osd-menu")
		return v
	
	
	def pack_items(self, parent, items):
		for item in items:
			parent.pack_start(item.widget, True, True, 0)
	
	
	def use_daemon(self, d):
		"""
		Allows (re)using already existing DaemonManager instance in same process.
		use_config() should be be called before parse_argumets() if this is used.
		"""
		self.daemon = d
		if not self._is_submenu:
			self._cononect_handlers()
			self.on_daemon_connected(self.daemon)
	
	
	def use_config(self, c):
		"""
		Allows reusing already existin Config instance in same process.
		Has to be called before parse_argumets()
		"""
		self.config = c
	
	
	def get_menuid(self):
		"""
		Returns ID of used menu.
		"""
		return self._menuid
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		if self._selected:
			return self._selected.id
		return None
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--control-with', '-c', type=str,
			metavar="option", default=STICK, choices=(LEFT, RIGHT, STICK),
			help="which pad or stick should be used to navigate menu (default: %s)" % (STICK,))
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default='A',
			help="button used to confirm choice (default: A)")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default='B',
			help="button used to cancel menu (default: B)")
		self.argparser.add_argument('--confirm-with-release', action='store_true',
			help="confirm choice with button release instead of button press")
		self.argparser.add_argument('--cancel-with-release', action='store_true',
			help="cancel menu with button release instead of button press")
		self.argparser.add_argument('--use-cursor', '-u', action='store_true',
			help="display and use cursor")
		self.argparser.add_argument('--from-profile', '-p', type=str,
			metavar="profile_file menu_name",
			help="load menu items from profile file")
		self.argparser.add_argument('--from-file', '-f', type=str,
			metavar="filename",
			help="load menu items from json file")
		self.argparser.add_argument('--print-items', action='store_true',
			help="prints menu items to stdout")
		self.argparser.add_argument('items', type=str, nargs='*', metavar='id title',
			help="Menu items")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		if self.args.from_profile:
			try:
				self._menuid = self.args.items[0]
				self.items = MenuData.from_profile(self.args.from_profile, self._menuid)
			except IOError:
				print >>sys.stderr, '%s: error: profile file not found' % (sys.argv[0])
				return False
			except ValueError:
				print >>sys.stderr, '%s: error: menu not found' % (sys.argv[0])
				return False
		elif self.args.from_file:
			#try:
			data = json.loads(open(self.args.from_file, "r").read())
			self._menuid = self.args.from_file
			self.items = MenuData.from_json_data(data)
			#except:
			#	print >>sys.stderr, '%s: error: failed to load menu file' % (sys.argv[0])
			#	return False
		else:
			try:
				self.items = MenuData.from_args(self.args.items)
				self._menuid = None
			except ValueError:
				print >>sys.stderr, '%s: error: invalid number of arguments' % (sys.argv[0])
				return False
		
		# Parse simpler arguments
		self._control_with = self.args.control_with
		self._confirm_with = self.args.confirm_with
		self._cancel_with = self.args.cancel_with
		
		if self.args.use_cursor:
			self.enable_cursor()
		
		# Create buttons that are displayed on screen
		self.items = self.items.generate(self)
		for item in self.items:
			item.widget = self.generate_widget(item)
		self.pack_items(self.parent, self.items)
		if len(self.items) == 0:
			print >>sys.stderr, '%s: error: no items in menu' % (sys.argv[0])
			return False
		
		if self.args.print_items:
			max_id_len = max(*[ len(x.id) for x in self.items ])
			row_format ="{:>%s}:\t{}" % (max_id_len,)
			for item in self.items:
				print row_format.format(item.id, item.label)
		return True
	
	
	def enable_cursor(self):
		if not self._use_cursor:
			self.f.add(self.cursor)
			self.f.show_all()
			self._use_cursor = True
	
	
	def generate_widget(self, item):
		""" Generates gtk widget for specified menutitem """
		if isinstance(item, Separator) and item.label:
			widget = Gtk.Button.new_with_label(item.label)
			widget.set_relief(Gtk.ReliefStyle.NONE)
			widget.set_name("osd-menu-separator")
			return widget
		elif isinstance(item, Separator):
			widget = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
			widget.set_name("osd-menu-separator")
			return widget
		else:
			widget = Gtk.Button.new_with_label(item.label)
			widget.set_relief(Gtk.ReliefStyle.NONE)
			if hasattr(widget.get_children()[0], "set_xalign"):
				widget.get_children()[0].set_xalign(0)
			else:
				widget.get_children()[0].set_halign(Gtk.Align.START)
			if isinstance(item, Submenu):
				item.callback = self.show_submenu
				label1 = widget.get_children()[0]
				label2 = Gtk.Label(_(">>"))
				label2.set_property("margin-left", 30)
				box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
				widget.remove(label1)
				box.pack_start(label1, True, True, 1)
				box.pack_start(label2, False, True, 1)
				widget.add(box)
				widget.set_name("osd-menu-item")
			elif item.id is None:
				widget.set_name("osd-menu-dummy")
			else:
				widget.set_name("osd-menu-item")
			return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name("osd-menu-item")
		if self.items[index].id:
			self._selected = self.items[index]
			self._selected.widget.set_name("osd-menu-item-selected")
			return True
		return False
	
	
	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 show(self, *a):
		if not self.select(0):
			self._direction = 1
			self.next_item()
			self._direction = 0
		OSDWindow.show(self, *a)
	
	
	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.error("Sucessfully locked input")
			pass
		
		if not self.config:
			self.config = Config()
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		self.daemon.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if not self._is_submenu:
			self.daemon.unlock_all()
			for x in self._eh_ids:
				self.daemon.disconnect(x)
			self._eh_ids = []
		OSDWindow.quit(self, code)
	
	
	def next_item(self):
		""" Selects next menu item, based on self._direction """
		start, i = -1, 0
		try:
			start = self.items.index(self._selected)
			i = start + self._direction
		except: pass
		while True:
			if i == start:
				# Cannot find valid menu item
				self.select(start)
				break
			if i >= len(self.items):
				i = 0
				continue
			if i < 0:
				i = len(self.items) - 1
				continue
			if self.select(i):
				# Not a separator
				break
			i += self._direction
			if start < 0: start = 0
	
	
	def on_move(self):
		self.next_item()
		self.timer("move", self.REPEAT_DELAY, self.on_move)
	
	
	def on_submenu_closed(self, *a):
		if self._submenu.get_exit_code() in (0, -2):
			self.quit(self._submenu.get_exit_code())
			self._selected = self._submenu._selected
		self._submenu = None
	
	
	def show_submenu(self, trash, trash2, menuitem):
		""" Called when user chooses menu item pointing to submenu """
		filename = find_menu(menuitem.filename)
		if filename:
			self._submenu = self.__class__()
			sub_pos = list(self.position)
			for i in (0, 1):
				sub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET
						if sub_pos[i] < 0 else sub_pos[i] + self.SUBMENU_OFFSET)
					
			self._submenu.use_config(self.config)
			self._submenu.parse_argumets(["menu.py",
				"-x", str(sub_pos[0]), "-y", str(sub_pos[1]),
			 	"--from-file", filename,
				"--control-with", self._control_with,
				"--confirm-with", self._confirm_with,
				"--cancel-with", self._cancel_with
			])
			self._submenu.set_is_submenu()
			self._submenu.use_daemon(self.daemon)
			self._submenu.connect('destroy', self.on_submenu_closed)
			self._submenu.show()
	
	
	def on_event(self, daemon, what, data):
		if self._submenu:
			return self._submenu.on_event(daemon, what, data)
		if what == self._control_with:
			x, y = data
			if self._use_cursor:
				max_w = self.get_allocation().width - (self.cursor.get_allocation().width * 0.8)
				max_h = self.get_allocation().height - (self.cursor.get_allocation().height * 1.0)
				x = ((x / (STICK_PAD_MAX * 2.0)) + 0.5) * max_w
				y = (0.5 - (y / (STICK_PAD_MAX * 2.0))) * max_h
				
				x -= self.cursor.get_allocation().width * 0.5
				y -= self.cursor.get_allocation().height * 0.5
				
				self.f.move(self.cursor, int(x), int(y))
				for i in self.items:
					if point_in_gtkrect(i.widget.get_allocation(), x, y):
						self.select(self.items.index(i))
			else:
				if y < STICK_PAD_MIN / 3 and self._direction != 1:
					self._direction = 1
					self.on_move()
				if y > STICK_PAD_MAX / 3 and self._direction != -1:
					self._direction = -1
					self.on_move()
				if y < STICK_PAD_MAX / 3 and y > STICK_PAD_MIN / 3 and self._direction != 0:
					self._direction = 0
					self.cancel_timer("move")
		elif what == self._cancel_with:
			if data[0] == 0:	# Button released
				self.quit(-1)
		elif what == self._confirm_with:
			if data[0] == 0:	# Button released
				if self._selected and self._selected.callback:
					self._selected.callback(self, self.daemon, self._selected)
				elif self._selected:
					self.quit(0)
				else:
					self.quit(-1)
Esempio n. 4
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.keyboard = None
		self.keymap = Gdk.Keymap.get_default()
		self.keymap.connect('state-changed', self.on_state_changed)
		
		
		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.limit_left  = self.background.get_rect_area(self.background.get_element("LIMIT_LEFT"))
		self.limit_right = self.background.get_rect_area(self.background.get_element("LIMIT_RIGHT"))
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursor_left = Gtk.Image.new_from_file(cursor)
		self.cursor_left.set_name("osd-keyboard-cursor")
		self.cursor_right = Gtk.Image.new_from_file(cursor)
		self.cursor_right.set_name("osd-keyboard-cursor")
		
		self._eh_ids = []
		self._stick = 0, 0
		self._hovers = { self.cursor_left : None, self.cursor_right : None }
		self._pressed = { self.cursor_left : None, self.cursor_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.cursor_left)
		self.f.add(self.cursor_right)
		self.c.add(self.f)
		self.add(self.c)
		
		self.set_cursor_position(0, 0, self.cursor_left, self.limit_left)
		self.set_cursor_position(0, 0, self.cursor_right, self.limit_right)
		
		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
					code = Gdk.keyval_to_unicode(
						self.keymap.translate_keyboard_state(k.keycode, mt, group)
						.keyval)
					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 just in case
		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.keyboard
		OSDWindow.quit(self, code)
	
	
	def show(self, *a):
		OSDWindow.show(self, *a)
		self.keyboard = uinputKeyboard(b"SCC OSD Keyboard")
	
	
	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 = (limit[0] + w * 0.5) + x * w * 0.5
		y = (limit[1] + h * 0.5) + y * h * 0.5
		
		self.f.move(cursor, int(x), int(y))
		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.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 on_event(self, daemon, what, data):
		"""
		Called when button press, button release or stick / pad update is
		send by daemon.
		"""
		if what == LEFT:
			x, y = data
			self.set_cursor_position(x, y, self.cursor_left, self.limit_left)
		elif what == RIGHT:
			x, y = data
			self.set_cursor_position(x, y, self.cursor_right, self.limit_right)
		elif what == STICK:
			self._stick = data
			if not self.timer_active('stick'):
				self.timer("stick", 0.05, self._move_window)
		elif what == SCButtons.LPAD.name:
			self.key_from_cursor(self.cursor_left, data[0] == 1)
		elif what == SCButtons.RPAD.name:
			self.key_from_cursor(self.cursor_right, data[0] == 1)
		elif what in (SCButtons.RPADTOUCH.name, SCButtons.LPADTOUCH.name):
			pass
		elif what in self.BUTTON_MAP:
			if data[0]:
				self.keyboard.pressEvent([ self.BUTTON_MAP[what] ])
			else:
				self.keyboard.releaseEvent([ self.BUTTON_MAP[what] ])
		elif what in [ b.name for b in SCButtons ]:
			self.quit(0)
	
	
	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 = self.f.child_get_property(cursor, "x")
		y = self.f.child_get_property(cursor, "y")
		
		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.keyboard.releaseEvent([ self._pressed[cursor] ])
						self.keyboard.pressEvent([ key ])
						self._pressed[cursor] = key
					break
		elif self._pressed[cursor] is not None:
			self.keyboard.releaseEvent([ self._pressed[cursor] ])
			self._pressed[cursor] = None
Esempio n. 5
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