コード例 #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)
コード例 #2
0
ファイル: dialog.py プロジェクト: kozec/sc-controller
class Dialog(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	
	def __init__(self, cls="osd-menu"):
		self._buttons = None
		self._text = None
		
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		self.parent = self.create_parent()
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
		
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._selected = None
		self._eh_ids = []
	
	
	def create_parent(self):
		self._text = Gtk.Label()
		self._buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
		dialog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		dialog.pack_start(self._text, True, True, 0)
		dialog.pack_start(self._buttons, True, True, 0)
		
		dialog.set_name("osd-dialog")
		self._buttons.set_name("osd-dialog-buttons")
		self._text.set_name("osd-dialog-text")
		return dialog
	
	
	def pack_items(self, parent, items):
		for item in items:
			if hasattr(item.widget, "set_alignment"):
				item.widget.set_alignment(0.5, 0.5)
			self._buttons.pack_end(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
		self._connect_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):
		# Just to be compatibile with menus when called from scc-osd-daemon
		return None
	
	
	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('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel dialog")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
		self.argparser.add_argument('--text', type=str, metavar='text',
			help="Dialog text")
		self.argparser.add_argument('items', type=str, nargs='*', metavar='id text',
			help="Dialog buttons")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		
		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
		
		self._text.set_label(self.args.text)
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			self.feedback = side, int(self.args.feedback_amplitude)
		
		# Create buttons that are displayed on screen
		items = self.items.generate(self)
		self.items = []
		for item in items:
			item.widget = self.generate_widget(item)
			if item.widget is not None:
				self.items.append(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
		
		return True
	
	
	def generate_widget(self, item):
		""" Generates gtk widget for specified menutitem """
		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)
		widget.set_name("osd-menu-item")
		
		return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name(self._selected.widget.get_name()
				.replace("-selected", ""))
		if self.items[index].id:
			if self._selected != self.items[index]:
				if self.feedback and self.controller:
					self.controller.feedback(*self.feedback)
			self._selected = self.items[index]
			self._selected.widget.set_name(
					self._selected.widget.get_name() + "-selected")
			return True
		return False
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_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")
		
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		
		ccfg = self.config.get_controller_config(self.controller.get_id())
		self._control_with = ccfg["menu_control"]
		self._confirm_with = ccfg["menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		self._eh_ids += [ (self.controller, self.controller.connect('event', self.on_event)) ]
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if self.get_controller():
			self.get_controller().unlock_all()
		for source, eid in self._eh_ids:
			source.disconnect(eid)
		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): break
			i += direction
			if start < 0: start = 0
	
	
	def on_stick_direction(self, trash, x, y):
		if x != 0:
			self.next_item(x)
	
	
	def on_event(self, daemon, what, data):
		if what == self._control_with:
			self._scon.set_stick(*data)
		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.controller, self._selected)
				elif self._selected:
					self.quit(0)
				else:
					self.quit(-1)
コード例 #3
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
    PREFER_BW_ICONS = True

    def __init__(self, cls="osd-menu"):
        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.feedback = None
        self.controller = 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._connect_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=DEFAULT,
            choices=(DEFAULT, LEFT, RIGHT, STICK),
            help="which pad or stick should be used to navigate menu")
        self.argparser.add_argument('--confirm-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to confirm choice")
        self.argparser.add_argument('--cancel-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to cancel menu")
        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('--size',
                                    type=int,
                                    help="sets prefered width or height")
        self.argparser.add_argument(
            '--feedback-amplitude',
            type=int,
            help=
            "enables and sets power of feedback effect generated when active menu option is changed"
        )
        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")

    @staticmethod
    def _get_on_screen_position(w):
        a = w.get_allocation()
        parent = w.get_parent()
        if parent:
            if isinstance(parent, Menu) and parent.get_window() is not None:
                x, y = parent.get_window().get_position()
            else:
                x, y = Menu._get_on_screen_position(parent)
            return a.x + x, a.y + y
        else:
            return a.x, a.y

    def parse_menu(self):
        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:
                self._menuid = self.args.from_file
                self.items = MenuData.from_file(self.args.from_file)
            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
        return True

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.parse_menu():
            return False
        if not self.config:
            self.config = Config()

        # Parse simpler arguments
        self._size = self.args.size

        # Create buttons that are displayed on screen
        items = self.items.generate(self)
        self.items = []
        for item in items:
            item.widget = self.generate_widget(item)
            if item.widget is not None:
                self.items.append(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")

            if isinstance(item.icon, Gio.FileIcon):
                icon_file = item.icon.get_file().get_path()
                has_colors = True
            elif isinstance(item.icon, Gio.ThemedIcon):
                icon = Gtk.IconTheme.get_default().choose_icon(
                    item.icon.get_names(), 64, 0)
                icon_file = icon.get_filename() if icon else None
                has_colors = True
            else:
                icon_file, has_colors = find_icon(item.icon,
                                                  self.PREFER_BW_ICONS)

            if icon_file:
                icon = MenuIcon(icon_file, has_colors)
                label = widget.get_children()[0]
                for c in [] + widget.get_children():
                    widget.remove(c)
                box = Gtk.Box()
                box.pack_start(icon, False, True, 0)
                box.pack_start(label, True, True, 10)
                widget.add(box)

            return widget

    def select(self, index):
        if self._selected:
            self._selected.widget.set_name(
                self._selected.widget.get_name().replace("-selected", ""))
        if self.items[index].id:
            if self._selected != self.items[index]:
                if self.feedback and self.controller:
                    self.controller.feedback(*self.feedback)
            self._selected = self.items[index]
            self._selected.widget.set_name(self._selected.widget.get_name() +
                                           "-selected")
            GLib.timeout_add(2, self._check_on_screen_position)
            return True
        return False

    def _check_on_screen_position(self):
        x, y = Menu._get_on_screen_position(self._selected.widget)
        screen_height = self.get_window().get_screen().get_height()
        if y < 50:
            wx, wy = self.get_window().get_position()
            wy += 5
            self.get_window().move(wx, wy)
            GLib.timeout_add(2, self._check_on_screen_position)
        if y > screen_height - 100:
            wx, wy = self.get_window().get_position()
            wy -= 5
            self.get_window().move(wx, wy)
            GLib.timeout_add(2, self._check_on_screen_position)

    def _connect_handlers(self):
        self._eh_ids += [
            (self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('error', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('alive',
                                              self.on_daemon_connected)),
        ]

    def run(self):
        self.daemon = DaemonManager()
        self._connect_handlers()
        OSDWindow.run(self)

    def show(self, *a):
        if not self.select(0):
            self.next_item(1)
        OSDWindow.show(self, *a)

    def on_daemon_connected(self, *a):
        if not self.config:
            self.config = Config()
        self.controller = self.choose_controller(self.daemon)
        if self.controller is None or not self.controller.is_connected():
            # There is no controller connected to daemon
            self.on_failed_to_lock("Controller not connected")
            return
        self.use_controller(self.controller)

        self._eh_ids += [
            (self.controller, self.controller.connect('event', self.on_event)),
            (self.controller,
             self.controller.connect('lost', self.on_controller_lost)),
        ]
        self.lock_inputs()

    def use_controller(self, controller):
        ccfg = self.config.get_controller_config(controller.get_id())
        self._control_with = ccfg[
            "menu_control"] if self.args.control_with == DEFAULT else self.args.control_with
        self._cancel_with = ccfg[
            "menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with

        if self.args.confirm_with == DEFAULT:
            self._confirm_with = ccfg["menu_confirm"]
        elif self.args.confirm_with == SAME:
            if self._control_with == RIGHT:
                self._confirm_with = SCButtons.RPADTOUCH.name
            else:
                self._confirm_with = SCButtons.LPADTOUCH.name
        else:
            self._confirm_with = self.args.confirm_with

        if self.args.use_cursor:
            # As special case, using LEFT pad on controller with
            # actual DPAD should not display cursor
            if self._control_with != LEFT or (controller.get_flags()
                                              & ControllerFlags.HAS_DPAD) == 0:
                self.enable_cursor()

        if self.args.feedback_amplitude:
            side = "LEFT"
            if self._control_with == "RIGHT":
                side = "RIGHT"
            elif self._control_with == "STICK":
                side = "BOTH"
            self.feedback = side, int(self.args.feedback_amplitude)

    def lock_inputs(self):
        def success(*a):
            log.error("Sucessfully locked input")

        locks = [self._control_with, self._confirm_with, self._cancel_with]
        self.controller.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if not self._is_submenu:
            if self.get_controller():
                self.get_controller().unlock_all()
            for source, eid in self._eh_ids:
                source.disconnect(eid)
            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):
        self.set_name("osd-menu")
        if self._submenu.get_exit_code() in (0, -2):
            self._menuid = self._submenu._menuid
            self._selected = self._submenu._selected
            self.quit(self._submenu.get_exit_code())
        self._submenu = None

    def show_submenu(self, trash, trash2, trash3, 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.use_controller(self.controller)
            self._submenu.controller = self.controller
            self._submenu.connect('destroy', self.on_submenu_closed)
            self._submenu.show()
            self.set_name("osd-menu-inactive")

    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

                pad_w = self.cursor.get_allocation().width * 0.5
                pad_h = self.cursor.get_allocation().height * 0.5
                max_w = self.get_allocation().width - 2 * pad_w
                max_h = self.get_allocation().height - 2 * pad_h

                x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0),
                                        y / (STICK_PAD_MAX * 2.0))
                x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w,
                          max_w - pad_w)
                y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1,
                          max_h - pad_h)
                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.controller,
                                            self._selected)
                elif self._selected:
                    self.quit(0)
                else:
                    self.quit(-1)
コード例 #4
0
ファイル: menu.py プロジェクト: kozec/sc-controller
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
	PREFER_BW_ICONS = True
	
	
	def __init__(self, cls="osd-menu"):
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = 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._control_with_dpad = False
		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._connect_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=DEFAULT, choices=(DEFAULT, LEFT, RIGHT, STICK),
			help="which pad or stick should be used to navigate menu")
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel menu")
		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('--size', type=int,
			help="sets prefered width or height")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
		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")
	
	
	@staticmethod
	def _get_on_screen_position(w):
		a = w.get_allocation()
		parent = w.get_parent()
		if parent:
			if isinstance(parent, Menu) and parent.get_window() is not None:
				x, y = parent.get_window().get_position()
			else:
				x, y = Menu._get_on_screen_position(parent)
			return a.x + x, a.y + y
		else:
			return a.x, a.y
	
	
	def parse_menu(self):
		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:
				self._menuid = self.args.from_file
				self.items = MenuData.from_file(self.args.from_file)
			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
		return True
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.parse_menu():
			return False
		if not self.config:
			self.config = Config()
		
		# Parse simpler arguments
		self._size = self.args.size
		
		# Create buttons that are displayed on screen
		items = self.items.generate(self)
		self.items = []
		for item in items:
			item.widget = self.generate_widget(item)
			if item.widget is not None:
				self.items.append(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")
			
			if isinstance(item.icon, Gio.FileIcon):
				icon_file = item.icon.get_file().get_path()
				has_colors = True
			elif isinstance(item.icon, Gio.ThemedIcon):
				icon = Gtk.IconTheme.get_default().choose_icon(
					item.icon.get_names(), 64, 0)
				icon_file = icon.get_filename() if icon else None
				has_colors = True
			else:
				icon_file, has_colors = find_icon(item.icon, self.PREFER_BW_ICONS)
			
			if icon_file:
				icon = MenuIcon(icon_file, has_colors)
				label = widget.get_children()[0]
				for c in [] + widget.get_children():
					widget.remove(c)
				box = Gtk.Box()
				box.pack_start(icon,  False, True, 0)
				box.pack_start(label, True, True, 10)
				widget.add(box)
				
			return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.widget.set_name(self._selected.widget.get_name()
				.replace("-selected", ""))
		if self.items[index].id:
			if self._selected != self.items[index]:
				if self.feedback and self.controller:
					self.controller.feedback(*self.feedback)
			self._selected = self.items[index]
			self._selected.widget.set_name(
					self._selected.widget.get_name() + "-selected")
			GLib.timeout_add(2, self._check_on_screen_position)
			return True
		return False
	
	
	def _check_on_screen_position(self, quick=False):
		x, y = Menu._get_on_screen_position(self._selected.widget)
		try:
			m = self.get_window().get_display().get_monitor_at_window(self.get_window())
			assert m
			y_offset = m.get_geometry().y
			screen_height = m.get_geometry().height
		except:
			y_offset = 0
			screen_height = self.get_window().get_screen().get_height()
		y -= y_offset
		if y < 50:
			wx, wy = self.get_window().get_position()
			if quick:
				wy = 50 - (y - wy)
			else:
				wy += 5
				GLib.timeout_add(2, self._check_on_screen_position)
			self.get_window().move(wx, wy)
		if y > screen_height - 100:
			wx, wy = self.get_window().get_position()
			if quick:
				wy = screen_height - 100 - (y - wy)
			else:
				wy -= 5
				GLib.timeout_add(2, self._check_on_screen_position)
			self.get_window().move(wx, wy)
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
		GLib.timeout_add(1, self._check_on_screen_position, True)
	
	
	def on_daemon_connected(self, *a):
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		self.use_controller(self.controller)
		
		self._eh_ids += [
			(self.controller, self.controller.connect('event', self.on_event)),
			(self.controller, self.controller.connect('lost', self.on_controller_lost)),
		]
		self.lock_inputs()
	
	
	def use_controller(self, controller):
		ccfg = self.config.get_controller_config(controller.get_id())
		self._control_with = ccfg["menu_control"] if self.args.control_with == DEFAULT else self.args.control_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		if self.args.confirm_with == DEFAULT:
			self._confirm_with = ccfg["menu_confirm"]
		elif self.args.confirm_with == SAME:
			if self._control_with == RIGHT:
				self._confirm_with = SCButtons.RPADTOUCH.name
			else:
				self._confirm_with = SCButtons.LPADTOUCH.name
		else:
			self._confirm_with = self.args.confirm_with
		
		if self.args.use_cursor:
			# As special case, using LEFT pad on controller with
			# actual DPAD should not display cursor
			if self._control_with != LEFT or (controller.get_flags() & ControllerFlags.HAS_DPAD) == 0:
				self.enable_cursor()
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			if self._control_with == "RIGHT":
				side = "RIGHT"
			elif self._control_with == "STICK":
				side = "BOTH"
			self.feedback = side, int(self.args.feedback_amplitude)
	
	
	def lock_inputs(self):
		def success(*a):
			log.error("Sucessfully locked input")
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		if self._control_with == "STICK":
			if self.controller.get_flags() & ControllerFlags.HAS_DPAD != 0:
				self._control_with_dpad = True
				locks += [ "LEFT" ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if not self._is_submenu:
			if self.get_controller():
				self.get_controller().unlock_all()
			for source, eid in self._eh_ids:
				source.disconnect(eid)
			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
				GLib.timeout_add(1, self._check_on_screen_position, True)
				continue
			if i < 0:
				i = len(self.items) - 1
				GLib.timeout_add(1, self._check_on_screen_position, True)
				continue
			if self.select(i):
				# Not a separator
				break
			i += direction
			if start < 0: start = 0
	
	
	def on_submenu_closed(self, *a):
		self.set_name("osd-menu")
		if self._submenu.get_exit_code() in (0, -2):
			self._menuid = self._submenu._menuid
			self._selected = self._submenu._selected
			self.quit(self._submenu.get_exit_code())
		self._submenu = None
	
	
	def show_submenu(self, trash, trash2, trash3, 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.use_controller(self.controller)
			self._submenu.controller = self.controller
			self._submenu.connect('destroy', self.on_submenu_closed)
			self._submenu.show()
			self.set_name("osd-menu-inactive")
	
	
	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 or what == "LEFT" and self._control_with_dpad:
			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
				
				pad_w = self.cursor.get_allocation().width * 0.5
				pad_h = self.cursor.get_allocation().height * 0.5
				max_w = self.get_allocation().width - 2 * pad_w
				max_h = self.get_allocation().height - 2 * pad_h
				
				x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))
				x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
				y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
				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.controller, self._selected)
				elif self._selected:
					self.quit(0)
				else:
					self.quit(-1)
コード例 #5
0
class Dialog(OSDWindow):
    EPILOG = """Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""

    def __init__(self, cls="osd-menu"):
        self._buttons = None
        self._text = None

        OSDWindow.__init__(self, cls)
        self.daemon = None
        self.config = None
        self.feedback = None
        self.controller = None
        self.xdisplay = X.Display(hash(
            GdkX11.x11_get_default_xdisplay()))  # Magic

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

        self._scon = StickController()
        self._scon.connect("direction", self.on_stick_direction)
        self._selected = None
        self._eh_ids = []

    def create_parent(self):
        self._text = Gtk.Label()
        self._buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        dialog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        dialog.pack_start(self._text, True, True, 0)
        dialog.pack_start(self._buttons, True, True, 0)

        dialog.set_name("osd-dialog")
        self._buttons.set_name("osd-dialog-buttons")
        self._text.set_name("osd-dialog-text")
        return dialog

    def pack_items(self, parent, items):
        for item in items:
            if hasattr(item.widget, "set_alignment"):
                item.widget.set_alignment(0.5, 0.5)
            self._buttons.pack_end(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
        self._connect_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):
        # Just to be compatibile with menus when called from scc-osd-daemon
        return None

    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('--confirm-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to confirm choice")
        self.argparser.add_argument('--cancel-with',
                                    type=str,
                                    metavar="button",
                                    default=DEFAULT,
                                    help="button used to cancel dialog")
        self.argparser.add_argument(
            '--feedback-amplitude',
            type=int,
            help=
            "enables and sets power of feedback effect generated when active menu option is changed"
        )
        self.argparser.add_argument('--text',
                                    type=str,
                                    metavar='text',
                                    help="Dialog text")
        self.argparser.add_argument('items',
                                    type=str,
                                    nargs='*',
                                    metavar='id text',
                                    help="Dialog buttons")

    def parse_argumets(self, argv):
        if not OSDWindow.parse_argumets(self, argv):
            return False
        if not self.config:
            self.config = Config()

        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

        self._text.set_label(self.args.text)

        if self.args.feedback_amplitude:
            side = "LEFT"
            self.feedback = side, int(self.args.feedback_amplitude)

        # Create buttons that are displayed on screen
        items = self.items.generate(self)
        self.items = []
        for item in items:
            item.widget = self.generate_widget(item)
            if item.widget is not None:
                self.items.append(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

        return True

    def generate_widget(self, item):
        """ Generates gtk widget for specified menutitem """
        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)
        widget.set_name("osd-menu-item")

        return widget

    def select(self, index):
        if self._selected:
            self._selected.widget.set_name(
                self._selected.widget.get_name().replace("-selected", ""))
        if self.items[index].id:
            if self._selected != self.items[index]:
                if self.feedback and self.controller:
                    self.controller.feedback(*self.feedback)
            self._selected = self.items[index]
            self._selected.widget.set_name(self._selected.widget.get_name() +
                                           "-selected")
            return True
        return False

    def _connect_handlers(self):
        self._eh_ids += [
            (self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('error', self.on_daemon_died)),
            (self.daemon, self.daemon.connect('alive',
                                              self.on_daemon_connected)),
        ]

    def run(self):
        self.daemon = DaemonManager()
        self._connect_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")

        if not self.config:
            self.config = Config()
        self.controller = self.choose_controller(self.daemon)
        if self.controller is None or not self.controller.is_connected():
            # There is no controller connected to daemon
            self.on_failed_to_lock("Controller not connected")
            return

        ccfg = self.config.get_controller_config(self.controller.get_id())
        self._control_with = ccfg["menu_control"]
        self._confirm_with = ccfg[
            "menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
        self._cancel_with = ccfg[
            "menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with

        self._eh_ids += [(self.controller,
                          self.controller.connect('event', self.on_event))]
        locks = [self._control_with, self._confirm_with, self._cancel_with]
        self.controller.lock(success, self.on_failed_to_lock, *locks)

    def quit(self, code=-2):
        if self.get_controller():
            self.get_controller().unlock_all()
        for source, eid in self._eh_ids:
            source.disconnect(eid)
        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): break
            i += direction
            if start < 0: start = 0

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

    def on_event(self, daemon, what, data):
        if what == self._control_with:
            self._scon.set_stick(*data)
        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.controller,
                                            self._selected)
                elif self._selected:
                    self.quit(0)
                else:
                    self.quit(-1)
コード例 #6
0
ファイル: menu.py プロジェクト: mulark/sc-controller
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._connect_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
		
		print "_control_with", self._control_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 _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_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")
		
		if not self.config:
			self.config = Config()
		locks = [ self._control_with, self._confirm_with, self._cancel_with ]
		c = self.choose_controller(self.daemon)
		if c is None or not c.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
		
		self._eh_ids += [ (c, c.connect('event', self.on_event)) ]
		c.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if not self._is_submenu:
			if self.get_controller():
				self.get_controller().unlock_all()
			for source, eid in self._eh_ids:
				source.disconnect(eid)
			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._menuid = self._submenu._menuid
			self._selected = self._submenu._selected
			self.quit(self._submenu.get_exit_code())
		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)
コード例 #7
0
ファイル: launcher.py プロジェクト: soekul/sc-controller
class Launcher(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	
	BUTTONS = [
		"1",	 	"2 ABC",	"3 DEF",
		"5 GHI", 	"5 JKL",	"6 MNO",
		"7 PQRS",	"8 TUV",	"9 WXYZ",
		"", 		"0"
	]
	
	VALID_CHARS = "12ABC3DEF5GHI5JKL6MNO7PQRS8TUV9WXYZ0"
	CHAR_TO_NUMBER = { }	# Generated on runtime
	
	MAX_ROWS = 5
	
	_app_db = None	# Static list of all know applications
	
	def __init__(self, cls="osd-menu"):
		self._buttons = None
		self._string = ""
		
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		self.create_parent()
		self.create_app_list()
		self.create_buttons()
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursors = [ Gtk.Image.new_from_file(cursor), Gtk.Image.new_from_file(cursor) ]
		for c in self.cursors:
			c.set_name("osd-menu-cursor")
			c.selected = None
			self.f.add(c)
		self.f.show_all()
		
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._selected = None
		self._menuid = None
		self._eh_ids = []
		self._confirm_with = 'A'
		self._cancel_with = 'B'
		
		if Launcher._app_db is None:
			Launcher._app_db = []
			for x in Launcher.BUTTONS:
				for c in x:
					Launcher.CHAR_TO_NUMBER[c] = x[0]
			
			for x in Gio.AppInfo.get_all():
				try:
					Launcher._app_db.append(( Launcher.name_to_keys(x), x ))
				except UnicodeDecodeError:
					# Just f**k them...
					pass
	
	
	@staticmethod
	def name_to_keys(appinfo):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x]
			for x in appinfo.get_display_name().upper()
			if x in Launcher.VALID_CHARS
		])
		return name
	
	
	@staticmethod
	def string_to_keys_and_spaces(string):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x] if x in Launcher.VALID_CHARS else " "
			for x in string.upper()
		])
		return name
	
	
	def create_parent(self):
		self.parent = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		self.parent.set_name("osd-dialog")
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
	
	
	def create_app_list(self):
		lst = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		lst.set_name("osd-application-list")
		self.items = [ self.generate_widget("") for x in xrange(self.MAX_ROWS) ]
		for a in self.items:
			lst.pack_start(a, False, True, 0)
		self.parent.pack_start(lst, True, True, 0)
		self._set_launchers([  ])
		lst.show_all()
	
	
	def create_buttons(self):
		self.grid = Gtk.Grid()
		self.parent.pack_start(self.grid, True, True, 0)
		self._buttons = []
		
		x, y = 0, 0
		for label in self.BUTTONS:
			if label:
				w = self.generate_widget(label)
				w.set_name("osd-key-buton")
				self.grid.attach(w, x, y, 1, 1)
				self._buttons.append(w)
			x += 1
			if x > 2:
				x = 0
				y += 1
		
		
		w = self.generate_widget(_("Run"))
		self.grid.attach(w, x, y, 1, 1)
		
		
		self.grid.set_name("osd-dialog-buttons")
	
	
	def pack_items(self, parent, items):
		for item in items:
			if hasattr(item.widget, "set_alignment"):
				item.widget.set_alignment(0.5, 0.5)
			self._buttons.pack_end(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
		self._connect_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 None
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		return None
	
	
	def _launch(self):
		self._selected.launcher.launch()
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		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('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		
		# Parse simpler arguments
		self._confirm_with = self.args.confirm_with
		self._cancel_with = self.args.cancel_with
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			self.feedback = side, int(self.args.feedback_amplitude)
		
		# Create buttons that are displayed on screen
		return True
	
	
	def _set_launchers(self, launchers):
		launchers = launchers[0:self.MAX_ROWS]
		for x in self.items:
			x.set_label("")
			x.set_name("osd-hidden-item")
			x.launcher = None
		for i in xrange(0, len(launchers)):
			self.items[i].set_name("osd-launcher-item")
			self.items[i].launcher = launchers[i]
			label = self.items[i].get_children()[0]
			label.set_markup(self._format_label_markup(launchers[i]))
			label.set_max_width_chars(1)
			label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
			label.set_xalign(0)
	
	
	def _format_label_markup(self, label):
		if hasattr(label, "get_display_name"):
			label = label.get_display_name()
		else:
			label = str(label)
		
		def _check(substr):
			i, ch = 0, self._string
			while len(substr) > 0 and substr[0] == ch[0]:
				ch = ch[1:]
				substr = substr[1:]
				i += 1
				if len(ch) == 0: return i
			while len(substr) > 0 and substr[0] == " ":
				substr = substr[1:]
				i += 1
			return -1
			
		keys = Launcher.string_to_keys_and_spaces(label)
		index1, index2 = -1, -1
		for i in xrange(0, len(keys)):
			if keys[i] == self._string[0]:
				index2 = _check(keys[i:])
				if index2 > 0:
					index1 = i
					index2 = i + index2
					break
		
		label = "%s<span color='#%s'>%s</span>%s" % (
			label[0:index1],
			self.config["osd_colors"]["menuitem_hilight_text"],
			label[index1:index2],
			label[index2:]
		)
		return label
	
	
	def _update_items(self):
		if len(self._string) > 0:
			gen = ( item for (keys, item) in self._app_db if self._string in keys )
			launchers = []
			for i in gen:
				launchers.append(i)
				if len(launchers) > self.MAX_ROWS: break
			self._set_launchers(launchers)
			self.select(0)
		else:
			self._set_launchers([])
	
	
	def generate_widget(self, label):
		""" Generates gtk widget for specified menutitem """
		if hasattr(label, "label"): label = label.label
		widget = Gtk.Button.new_with_label(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)
		widget.set_name("osd-menu-item")
		
		return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.set_name(self._selected.get_name()
				.replace("-selected", ""))
			self._selected = None
		if self.items[index].launcher is not None:
			self._selected = self.items[index]
			self._selected.set_name(
					self._selected.get_name() + "-selected")
			return True
		return False
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
		for c in self.cursors:
			c.set_visible(False)
	
	
	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")
		
		if not self.config:
			self.config = Config()
		locks = [ LEFT, RIGHT, STICK, "LPAD", "RPAD", "LB",
		 	self._confirm_with, self._cancel_with ]
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		
		self._eh_ids += [ (self.controller, self.controller.connect('event', self.on_event)) ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if self.get_controller():
			self.get_controller().unlock_all()
		for source, eid in self._eh_ids:
			source.disconnect(eid)
		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): break
			i += direction
			if start < 0: start = 0
	
	
	def on_stick_direction(self, trash, x, y):
		if y != 0:
			self.next_item(y)
	
	
	def _move_cursor(self, cursor, x, y):
		if (x, y) == (0, 0):
			cursor.set_visible(False)
			return
		cursor.set_visible(True)
		pad_w = cursor.get_allocation().width * 0.5
		pad_h = cursor.get_allocation().height * 0.5
		max_w = self.grid.get_allocation().width - 2 * pad_w
		max_h = self.grid.get_allocation().height - 2 * pad_h
		
		x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))
		x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
		y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
		x += self.grid.get_allocation().x
		y += self.grid.get_allocation().y
		self.f.move(cursor, int(x), int(y))
		
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				if cursor.selected:
					cursor.selected.set_name("osd-key-buton")
				cursor.selected = i
				cursor.selected.set_name("osd-key-buton-hilight")
				break
	
	
	def _get_under_cursor(self, cursor):
		x, y = self.f.child_get(cursor, "x", "y")
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				return i
		return None
	
	
	def on_event(self, daemon, what, data):
		if what == LEFT:
			self._move_cursor(self.cursors[0], *data)
		elif what == "LPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[0])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == RIGHT:
			self._move_cursor(self.cursors[1], *data)
		elif what == "RPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[1])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == "LB":
			if len(self._string) > 0:
				self._string = self._string[:-1]
				self._update_items()
		elif what == STICK:
			self._scon.set_stick(*data)
		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:
					self._launch()
					self.quit(0)
				else:
					self.quit(-1)
コード例 #8
0
ファイル: launcher.py プロジェクト: kozec/sc-controller
class Launcher(OSDWindow):
	EPILOG="""Exit codes:
   0  - clean exit, user selected option
  -1  - clean exit, user canceled dialog
   1  - error, invalid arguments
   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.
   3  - erorr, failed to lock input stick, pad or button(s)
	"""
	
	BUTTONS = [
		"1",	 	"2 ABC",	"3 DEF",
		"5 GHI", 	"5 JKL",	"6 MNO",
		"7 PQRS",	"8 TUV",	"9 WXYZ",
		"", 		"0"
	]
	
	VALID_CHARS = "12ABC3DEF5GHI5JKL6MNO7PQRS8TUV9WXYZ0"
	CHAR_TO_NUMBER = { }	# Generated on runtime
	
	MAX_ROWS = 5
	
	_app_db = None	# Static list of all know applications
	
	def __init__(self, cls="osd-menu"):
		self._buttons = None
		self._string = ""
		
		OSDWindow.__init__(self, cls)
		self.daemon = None
		self.config = None
		self.feedback = None
		self.controller = None
		self.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))	# Magic
		
		self.create_parent()
		self.create_app_list()
		self.create_buttons()
		
		cursor = os.path.join(get_share_path(), "images", 'menu-cursor.svg')
		self.cursors = [ Gtk.Image.new_from_file(cursor), Gtk.Image.new_from_file(cursor) ]
		for c in self.cursors:
			c.set_name("osd-menu-cursor")
			c.selected = None
			self.f.add(c)
		self.f.show_all()
		
		self._scon = StickController()
		self._scon.connect("direction", self.on_stick_direction)
		self._selected = None
		self._menuid = None
		self._eh_ids = []
		self._confirm_with = 'A'
		self._cancel_with = 'B'
		
		if Launcher._app_db is None:
			Launcher._app_db = []
			for x in Launcher.BUTTONS:
				for c in x:
					Launcher.CHAR_TO_NUMBER[c] = x[0]
			
			for x in Gio.AppInfo.get_all():
				try:
					Launcher._app_db.append(( Launcher.name_to_keys(x), x ))
				except UnicodeDecodeError:
					# Just f**k them...
					pass
	
	
	@staticmethod
	def name_to_keys(appinfo):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x]
			for x in appinfo.get_display_name().upper()
			if x in Launcher.VALID_CHARS
		])
		return name
	
	
	@staticmethod
	def string_to_keys_and_spaces(string):
		name = "".join([
			Launcher.CHAR_TO_NUMBER[x] if x in Launcher.VALID_CHARS else " "
			for x in string.upper()
		])
		return name
	
	
	def create_parent(self):
		self.parent = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		self.parent.set_name("osd-dialog")
		self.f = Gtk.Fixed()
		self.f.add(self.parent)
		self.add(self.f)
	
	
	def create_app_list(self):
		lst = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
		lst.set_name("osd-application-list")
		self.items = [ self.generate_widget("") for x in xrange(self.MAX_ROWS) ]
		for a in self.items:
			lst.pack_start(a, False, True, 0)
		self.parent.pack_start(lst, True, True, 0)
		self._set_launchers([  ])
		lst.show_all()
	
	
	def create_buttons(self):
		self.grid = Gtk.Grid()
		self.parent.pack_start(self.grid, True, True, 0)
		self._buttons = []
		
		x, y = 0, 0
		for label in self.BUTTONS:
			if label:
				w = self.generate_widget(label)
				w.set_name("osd-key-buton")
				self.grid.attach(w, x, y, 1, 1)
				self._buttons.append(w)
			x += 1
			if x > 2:
				x = 0
				y += 1
		
		
		w = self.generate_widget(_("Run"))
		self.grid.attach(w, x, y, 1, 1)
		
		
		self.grid.set_name("osd-dialog-buttons")
	
	
	def pack_items(self, parent, items):
		for item in items:
			if hasattr(item.widget, "set_alignment"):
				item.widget.set_alignment(0.5, 0.5)
			self._buttons.pack_end(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
		self._connect_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 None
	
	
	def get_selected_item_id(self):
		"""
		Returns ID of selected item or None if nothing is selected.
		"""
		return None
	
	
	def _launch(self):
		self._selected.launcher.launch()
	
	
	def _add_arguments(self):
		OSDWindow._add_arguments(self)
		self.argparser.add_argument('--confirm-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to confirm choice")
		self.argparser.add_argument('--cancel-with', type=str,
			metavar="button", default=DEFAULT,
			help="button used to cancel dialog")
		self.argparser.add_argument('--feedback-amplitude', type=int,
			help="enables and sets power of feedback effect generated when active menu option is changed")
	
	
	def parse_argumets(self, argv):
		if not OSDWindow.parse_argumets(self, argv):
			return False
		if not self.config:
			self.config = Config()
		
		if self.args.feedback_amplitude:
			side = "LEFT"
			self.feedback = side, int(self.args.feedback_amplitude)
		
		# Create buttons that are displayed on screen
		return True
	
	
	def _set_launchers(self, launchers):
		launchers = launchers[0:self.MAX_ROWS]
		for x in self.items:
			x.set_label("")
			x.set_name("osd-hidden-item")
			x.launcher = None
		for i in xrange(0, len(launchers)):
			self.items[i].set_name("osd-launcher-item")
			self.items[i].launcher = launchers[i]
			label = self.items[i].get_children()[0]
			label.set_markup(self._format_label_markup(launchers[i]))
			label.set_max_width_chars(1)
			label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
			label.set_xalign(0)
	
	
	def _format_label_markup(self, label):
		if hasattr(label, "get_display_name"):
			label = label.get_display_name()
		else:
			label = str(label)
		
		def _check(substr):
			i, ch = 0, self._string
			while len(substr) > 0 and substr[0] == ch[0]:
				ch = ch[1:]
				substr = substr[1:]
				i += 1
				if len(ch) == 0: return i
			while len(substr) > 0 and substr[0] == " ":
				substr = substr[1:]
				i += 1
			return -1
			
		keys = Launcher.string_to_keys_and_spaces(label)
		index1, index2 = -1, -1
		for i in xrange(0, len(keys)):
			if keys[i] == self._string[0]:
				index2 = _check(keys[i:])
				if index2 > 0:
					index1 = i
					index2 = i + index2
					break
		
		label = "%s<span color='#%s'>%s</span>%s" % (
			label[0:index1],
			self.config["osd_colors"]["menuitem_hilight_text"],
			label[index1:index2],
			label[index2:]
		)
		return label
	
	
	def _update_items(self):
		if len(self._string) > 0:
			gen = ( item for (keys, item) in self._app_db if self._string in keys )
			launchers = []
			for i in gen:
				launchers.append(i)
				if len(launchers) > self.MAX_ROWS: break
			self._set_launchers(launchers)
			self.select(0)
		else:
			self._set_launchers([])
	
	
	def generate_widget(self, label):
		""" Generates gtk widget for specified menutitem """
		if hasattr(label, "label"): label = label.label
		widget = Gtk.Button.new_with_label(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)
		widget.set_name("osd-menu-item")
		
		return widget
	
	
	def select(self, index):
		if self._selected:
			self._selected.set_name(self._selected.get_name()
				.replace("-selected", ""))
			self._selected = None
		if self.items[index].launcher is not None:
			self._selected = self.items[index]
			self._selected.set_name(
					self._selected.get_name() + "-selected")
			return True
		return False
	
	
	def _connect_handlers(self):
		self._eh_ids += [
			(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('error', self.on_daemon_died)),
			(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),
		]
	
	
	def run(self):
		self.daemon = DaemonManager()
		self._connect_handlers()
		OSDWindow.run(self)
	
	
	def show(self, *a):
		if not self.select(0):
			self.next_item(1)
		OSDWindow.show(self, *a)
		for c in self.cursors:
			c.set_visible(False)
	
	
	def on_daemon_connected(self, *a):
		def success(*a):
			log.error("Sucessfully locked input")
		
		if not self.config:
			self.config = Config()
		self.controller = self.choose_controller(self.daemon)
		if self.controller is None or not self.controller.is_connected():
			# There is no controller connected to daemon
			self.on_failed_to_lock("Controller not connected")
			return
		
		ccfg = self.config.get_controller_config(self.controller.get_id())
		self._confirm_with = ccfg["menu_confirm"] if self.args.confirm_with == DEFAULT else self.args.confirm_with
		self._cancel_with = ccfg["menu_cancel"] if self.args.cancel_with == DEFAULT else self.args.cancel_with
		
		self._eh_ids += [
			(self.controller, self.controller.connect('event', self.on_event)),
			(self.controller, self.controller.connect('lost', self.on_controller_lost)),
		]
		locks = [ LEFT, RIGHT, STICK, "LPAD", "RPAD", "LB",
			self._confirm_with, self._cancel_with ]
		self.controller.lock(success, self.on_failed_to_lock, *locks)
	
	
	def quit(self, code=-2):
		if self.get_controller():
			self.get_controller().unlock_all()
		for source, eid in self._eh_ids:
			source.disconnect(eid)
		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): break
			i += direction
			if start < 0: start = 0
	
	
	def on_stick_direction(self, trash, x, y):
		if y != 0:
			self.next_item(y)
	
	
	def _move_cursor(self, cursor, x, y):
		if (x, y) == (0, 0):
			cursor.set_visible(False)
			return
		cursor.set_visible(True)
		pad_w = cursor.get_allocation().width * 0.5
		pad_h = cursor.get_allocation().height * 0.5
		max_w = self.grid.get_allocation().width - 2 * pad_w
		max_h = self.grid.get_allocation().height - 2 * pad_h
		
		x, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))
		x = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)
		y = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)
		x += self.grid.get_allocation().x
		y += self.grid.get_allocation().y
		self.f.move(cursor, int(x), int(y))
		
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				if cursor.selected:
					cursor.selected.set_name("osd-key-buton")
				cursor.selected = i
				cursor.selected.set_name("osd-key-buton-hilight")
				break
	
	
	def _get_under_cursor(self, cursor):
		x, y = self.f.child_get(cursor, "x", "y")
		for i in self._buttons:
			if point_in_gtkrect(i.get_allocation(), x, y):
				return i
		return None
	
	
	def on_event(self, daemon, what, data):
		if what == LEFT:
			self._move_cursor(self.cursors[0], *data)
		elif what == "LPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[0])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == RIGHT:
			self._move_cursor(self.cursors[1], *data)
		elif what == "RPAD" and data[0] == 1:
			b = self._get_under_cursor(self.cursors[1])
			if b: self._string += b.get_label()[0]
			self._update_items()
		elif what == "LB":
			if len(self._string) > 0:
				self._string = self._string[:-1]
				self._update_items()
		elif what == STICK:
			self._scon.set_stick(*data)
		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:
					self._launch()
					self.quit(0)
				else:
					self.quit(-1)