예제 #1
0
class Scene(quickstart.scenes.BaseScene):
	
	application_selection_dialog = None
	
	events = {
		"destroy" : (
			"main",
		),
		"toggled" : (
			"enabled_checkbox",
		),
		"clicked" : (
			"add_button",
			"remove_button",
		),
		"cursor-changed" : (
			"enabled_treeview",
		),
		"changed" : (
			"position_combo",
		)
	}

	def on_enabled_treeview_cursor_changed(self, treeview):
		""" Fired when the user changed something on the enabled_treeview. """
		
		# Enable remove button
		GObject.idle_add(self.objects["remove_button"].set_sensitive, True)

	def on_add_button_clicked(self, button):
		""" Fired when the Add launcher button has been clicked. """
				
		if not self.application_selection_dialog:
			self.application_selection_dialog = ApplicationSelectionDialog()
			self.application_selection_dialog.build_application_list()
			
			# Connect response signal
			self.application_selection_dialog.connect("response", self.on_application_selection_dialog_response)
			
			# Bind sensitiveness of the parent with the visibility of the new window
			self.application_selection_dialog.bind_property(
				"visible",
				self.objects["main"],
				"sensitive",
				GObject.BindingFlags.INVERT_BOOLEAN
			)
		
		self.application_selection_dialog.show()
		
	
	def on_remove_button_clicked(self, button):
		""" Fired when the Remove launcher button has been clicked. """
		
		# Get selection
		selection = self.objects["enabled_treeview"].get_selection()
		model, treeiter = selection.get_selected()
		
		del model[treeiter]
		
		# Disable remove button if we should
		if len(model) == 0: GObject.idle_add(self.objects["remove_button"].set_sensitive, False)
	
	def on_application_selection_dialog_response(self, dialog, response_id):
		"""
		Fired when the user triggered a response on the application_selection_dialog.
		"""
		
		# Hide
		dialog.hide()
		
		if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
			return
		
		# Get and add selection
		self.enabled_model.append(dialog.get_selection())

	
	def on_scene_asked_to_close(self):
		""" Fired when the back button has been pressed """
		
		settings = Gtk.Settings.get_default()
		icon_theme = settings.get_property("gtk-icon-theme-name")
		
		if not os.path.exists(os.path.dirname(CONFIG)):
			os.makedirs(os.path.dirname(CONFIG))
		
		with open(CONFIG, "w") as f:
			if self.objects["position_combo"].get_active() != position_dict["bottom"]:
				# Panel position. If it is Bottom do not save it.
				# This avoids messing up with the panel position for those
				# who already modified it in the *primary* config.
				f.write("panel_position = %s left horizontal\n" % self.objects["combostore"][self.objects["position_combo"].get_active_iter()][1])
				
				# Ensure that windows can be on top of the panel if the position is top
				if self.objects["position_combo"].get_active() == position_dict["top"]:
					f.write("panel_layer = bottom\n")
			if self.objects["Hide_checkbox"].get_active():
				f.write("autohide = 1\n")
				
			if self.objects["ampm_enabled"].get_active():
				f.write("time1_format = %I:%M %p\n")
				
			if self.objects["enabled_checkbox"].get_active():
				f.write("panel_items = LTSC\n")
			else:
				f.write("panel_items = TSC\n")
				
			f.write("launcher_icon_theme = %s\n" % icon_theme)
			
			for treeiter in self.enabled_model:
				f.write("launcher_item_app = %s\n" % treeiter[1])				
			
			if self.objects["inverted_scroll_actions"].get_active():
				f.write("mouse_scroll_down = toggle\nmouse_scroll_up = iconify\n")
		
		os.system("killall -SIGUSR1 tint2")
		
		# Destroy application_selection_dialog
		if self.application_selection_dialog:
			self.application_selection_dialog.destroy()
			self.application_selection_dialog = None
		
		return True
	
	def on_main_destroy(self, window):
		""" Fired when the main window should be destroyed. """
		
		Gtk.main_quit()
	
	def on_enabled_checkbox_toggled(self, checkbox):
		""" Fired when the enabled checkbox has been toggled. """
		
		if checkbox.get_active():
			GObject.idle_add(self.objects["enabled_box"].set_sensitive, True)
			GObject.idle_add(self.objects["add_button"].set_sensitive, True)
		else:
			GObject.idle_add(self.objects["enabled_box"].set_sensitive, False)
	
	def on_position_combo_changed(self, combo):
		""" Fired when the position combobox has been changed. """
		
		# Pre-set "Invert mouse scroll actions" if the newly selected
		# position is Top.
		if self.objects["position_combo"].get_active() == position_dict["top"]:
			self.objects["inverted_scroll_actions"].set_active(True)
		else:
			self.objects["inverted_scroll_actions"].set_active(False)
	
	@quickstart.threads.thread
	def initialize(self):
		""" Builds the enabled list. """
		
		# Set-up the enabled_treeview
		self.enabled_model = Gtk.ListStore(str, str, Gio.Icon)
		# Link the treeview
		self.objects["enabled_treeview"].set_model(self.enabled_model)
		
		# Column
		column = Gtk.TreeViewColumn("Everything")
		
		# Icon
		cell_icon = Gtk.CellRendererPixbuf()
		column.pack_start(cell_icon, False)
		column.add_attribute(cell_icon, "gicon", 2)

		# Text
		cell_text = Gtk.CellRendererText()
		column.pack_start(cell_text, False)
		column.add_attribute(cell_text, "text", 0)
		
		# Append
		self.objects["enabled_treeview"].append_column(column)

		# Position combo: create renderer
		cellrenderer = Gtk.CellRendererText()
		self.objects["position_combo"].pack_start(cellrenderer, True)
		self.objects["position_combo"].add_attribute(cellrenderer, "text", 0)

		# Default position is "Bottom" (will be overriden later if we need to)
		self.objects["position_combo"].set_active(position_dict["bottom"])

		# Open config
		if os.path.exists(CONFIG):
			
			up_inverted = False
			down_inverted = False
			
			with open(CONFIG) as f:
				for line in f.readlines():
					try:
						line = line.split("=")
						if line[0].startswith("launcher_item_app"):
							# A launcher!
							path = line[1].strip("\n").replace(" /","/",1)
							desktopentry = xdg.DesktopEntry.DesktopEntry(path)
							iconpath = desktopentry.getIcon()
							if iconpath and iconpath.startswith("/"):
								icon = Gio.Icon.new_for_string(iconpath)
							elif iconpath:
								icon = Gio.ThemedIcon.new(iconpath.replace(".png",""))
							else:
								icon = None
							self.enabled_model.append((desktopentry.getName(), path, icon))
						elif line[0].startswith("panel_items"):
							# Is the launcher enabled or not?
							if "L" in line[1]:
								# YES!
								self.objects["enabled_checkbox"].set_active(True)
							else:
								# No :/
								GObject.idle_add(self.objects["enabled_box"].set_sensitive, False)
						elif line[0].startswith("time1_format"):
							# AM/PM?
							if "%p" in line[1]:
								# Yes!
								self.objects["ampm_enabled"].set_active(True)
							else:
								# No
								self.objects["ampm_enabled"].set_active(False)
						elif line[0].startswith("autohide"):
							# Autohide?
							if "1" in line[1]:
								# Yes
								self.objects["Hide_checkbox"].set_active(True)
							else:
								# No
								self.objects["Hide_checkbox"].set_active(False)
						elif line[0].startswith("panel_position"):
							# Position
							splt = line[1].strip("\n").split(" ")
							while splt.count(""):
								splt.remove("")
							self.objects["position_combo"].set_active(position_dict[splt[0]])
						elif line[0].startswith("mouse_scroll_"):
							# Mouse scroll (up/down) actions.
							# Usually if they are on the secondary_config
							# it's because they're inverted, but let's check
							# anyways...
							if "down" in line[0] and "toggle" in line[1]:
								down_inverted = True
							elif "up" in line[0] and "iconify" in line[1]:
								up_inverted = True
					except Exception:
						print("Error while loading configuration.")
			
			self.objects["inverted_scroll_actions"].set_active(down_inverted and up_inverted)

		else:
			# Ensure the enabled_checkbox is not active.
			self.objects["enabled_checkbox"].set_active(False)
			self.objects["add_button"].set_sensitive(False)

		# First start, disable remove button.
		GObject.idle_add(self.objects["remove_button"].set_sensitive, False)
	
	def prepare_scene(self):
		""" Called when doing the scene setup. """
		
		self.scene_container = self.objects.main
		
		self.initialize()
		
		self.objects["main"].show_all()
class Scene(quickstart.scenes.BaseScene):
    """
	The main scene.
	"""

    events = {
        "response": ("add_new_custom_dialog",
                     ),  # the application selection dialog is handled manually
        "delete-event": ("add_new_custom_dialog", ),
        "changed": (
            "custom_name",
            "custom_command",
        ),
    }

    application_selection_dialog = None

    desktop_list = []

    current_edit_informations = {}

    def on_custom_entry_changed(self, entry):
        """
		Fired when a custom entry has been changed.
		This method isn't directly connected to the custom GtkEntries, but it's
		directly linked to the on_custom_<id>_changed methods that quickstart
		expects.
		"""

        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.OK).set_sensitive(not (
                (self.objects.custom_name.get_text_length() == 0) or
                (self.objects.custom_command.get_text_length() == 0)))

    on_custom_name_changed = on_custom_entry_changed
    on_custom_command_changed = on_custom_entry_changed

    def on_application_selection_dialog_response(self, dialog, response_id):
        """
		Fired when the user triggered a response on the application_selection_dialog.
		"""

        # Hide
        dialog.hide()

        if response_id in (Gtk.ResponseType.CANCEL,
                           Gtk.ResponseType.DELETE_EVENT):
            return

        # Get selection
        desktop_file = dialog.get_selection()[1]
        desktop_basename = os.path.basename(desktop_file)

        if os.path.basename(desktop_file) in self.desktop_list:
            # Already in list, bye
            return

        # Copy the desktop file and prepend a new row.
        # FIXME: currently applications with KDE in "OnlyShowIn" will get
        # regularly added, but they won't be autostarted by vera and they
        # will not show again on this module.
        #
        # This can be resolved once vera supports a "force-list", see
        # https://github.com/vera-desktop/vera/issues/2

        # Copy the desktop file to ~/.config/autostart
        directory = os.path.expanduser("~/.config/autostart")
        target_file = os.path.join(directory, desktop_basename)
        if not os.path.exists(directory):
            os.makedirs(directory)
        shutil.copy2(desktop_file, target_file)

        entry = DesktopEntry(target_file)

        row = ApplicationRow(desktop_basename, entry)

        # Connect the changed signal
        row.connect("changed", self.on_row_changed)

        # Connect the requests_edit signal
        row.connect("requests_edit", self.on_row_requests_edit)

        # Prepend the row
        self.objects.list.prepend(row)

        self.desktop_list.append(desktop_basename)

    def on_add_new_application_activated(self, menuitem, parameter):
        """
		Fired when the add new application item has been selected.
		"""

        if not self.application_selection_dialog:
            self.application_selection_dialog = ApplicationSelectionDialog()
            self.application_selection_dialog.build_application_list()

            # Connect response signal
            self.application_selection_dialog.connect(
                "response", self.on_application_selection_dialog_response)

            # Bind sensitiveness of the parent with the visibility of the new window
            self.application_selection_dialog.bind_property(
                "visible", self.objects.main, "sensitive",
                GObject.BindingFlags.INVERT_BOOLEAN)

        self.application_selection_dialog.show()

    def on_add_new_custom_activated(self, menuitem, parameter):
        """
		Fired when the add new custom application item has been selected.
		"""

        # Set the proper title
        self.objects.add_new_custom_dialog.set_title(
            _("Add a new custom command"))

        # Hide the Remove button
        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.NO).hide()

        # Disable the sensitiveness of the Select button
        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.OK).set_sensitive(False)

        # Grab focus on the custom_name entry
        self.objects.custom_name.grab_focus()

        # Show the add_new_custom dialog
        self.objects.add_new_custom_dialog.show()

    def on_row_requests_edit(self, row, application_desktop):
        """
		Fired when the add new custom application item has been selected.
		"""

        # Set the proper title
        self.objects.add_new_custom_dialog.set_title(_("Edit application"))

        # Show the Remove button
        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.NO).show()

        # Disable the sensitiveness of the Select button
        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.OK).set_sensitive(False)

        # Grab focus on the custom_name entry
        self.objects.custom_name.grab_focus()

        # Preload Name and Exec
        self.objects.custom_name.set_text(application_desktop.getName())
        self.objects.custom_command.set_text(application_desktop.getExec())

        # Populate self.current_edit_informations
        self.current_edit_informations["row"] = row
        self.current_edit_informations["desktop"] = application_desktop

        # Show the add_new_custom dialog
        self.objects.add_new_custom_dialog.show()

    def on_add_new_custom_dialog_response(self, dialog, response_id):
        """
		Fired when the user triggered a response on the add_new_custom_dialog.
		"""

        # Clunky way to see if we have edit mode, but it works
        on_edit = dialog.get_widget_for_response(
            Gtk.ResponseType.NO).props.visible

        if not on_edit and response_id == Gtk.ResponseType.OK:
            # Obtain a working filename
            directory = os.path.expanduser("~/.config/autostart")
            if not os.path.exists(directory):
                os.makedirs(directory)

            filename = os.path.join(
                directory,
                self.objects.custom_name.get_text().lower().replace(" ", "-") +
                ".custom%s.desktop" % (random.randint(0, 1000), ))
            if os.path.exists(filename):
                return on_add_new_custom_dialog_response(dialog, response_id)

            desktop_basename = os.path.basename(filename)

            entry = DesktopEntry(filename)
            entry.set("Version", 1.0)
            entry.set("Name", self.objects.custom_name.get_text())
            entry.set("Exec", self.objects.custom_command.get_text())
            entry.set("X-Vera-Autostart-Phase", "Other")
            entry.write()

            row = ApplicationRow(desktop_basename, entry)

            # Connect the changed signal
            row.connect("changed", self.on_row_changed)

            # Connect the requests_edit signal
            row.connect("requests_edit", self.on_row_requests_edit)

            # Prepend the row
            self.objects.list.prepend(row)

            self.desktop_list.append(desktop_basename)
        elif on_edit and response_id == Gtk.ResponseType.OK:
            # Edit

            self.current_edit_informations["desktop"].set(
                "Name", self.objects.custom_name.get_text(), locale=True)
            self.current_edit_informations["desktop"].set(
                "Exec", self.objects.custom_command.get_text())
            self.current_edit_informations["desktop"].write()

            self.current_edit_informations["row"].name.set_text(
                self.objects.custom_name.get_text())
        elif on_edit and response_id == Gtk.ResponseType.NO:
            # Remove

            # Cleanup the entry from the ignore list by ensuring that
            # it's enabled in its last moments...
            self.on_row_changed(
                self.current_edit_informations["row"],
                os.path.basename(
                    self.current_edit_informations["desktop"].filename), True)

            # Finally, remove
            os.remove(self.current_edit_informations["desktop"].filename)
            self.current_edit_informations["row"].destroy()

        # Hide
        dialog.hide()

        # Cleanup
        self.objects.custom_name.set_text("")
        self.objects.custom_command.set_text("")
        self.current_edit_informations = {}

    def on_add_new_custom_dialog_delete_event(self, dialog, event):
        """
		Fired when the delete-event event of the add_new_custom_dialog is emitted.
		"""

        # Do not destroy
        return True

    def on_row_changed(self, row, application, enabled):
        """
		Fired when the switch of a row has been modified.
		"""

        if enabled and application in BLACKLIST:
            # Remove from the blacklist
            BLACKLIST.remove(application)
        elif not enabled and application not in BLACKLIST:
            # Add to the blacklist
            BLACKLIST.append(application)

        # Set the new array
        SETTINGS.set_strv("autostart-ignore", BLACKLIST)

    #@quickstart.threads.on_idle
    @quickstart.threads.thread
    def add_applications(self):
        """
		Populates the self.objects.list ListBox with the applications
		in SEARCH_PATH.
		"""

        for path in SEARCH_PATH:

            if not os.path.exists(path): continue

            for application in os.listdir(path):

                # Add the application, if we can
                try:
                    entry = DesktopEntry(os.path.join(path, application))
                    if not "KDE" in entry.getOnlyShowIn(
                    ) and not application in self.desktop_list:

                        # While excluding only KDE is not ideal, we do so
                        # to have consistency with vera's AutostartManager.
                        # This check is obviously a FIXME.

                        row = ApplicationRow(application, entry)

                        # Connect the changed signal
                        row.connect("changed", self.on_row_changed)

                        # Connect the requests_edit signal
                        row.connect("requests_edit", self.on_row_requests_edit)

                        GObject.idle_add(self.objects.list.insert, row, -1)

                        self.desktop_list.append(application)
                except:
                    print("Unable to show informations for %s." % application)

    def prepare_scene(self):
        """
		Scene setup.
		"""

        self.scene_container = self.objects.main

        # Create menu
        actiongroup = Gio.SimpleActionGroup.new()

        menu = Gio.Menu()
        menu.append(_("Select..."), "add-new.application")
        menu.append(_("Use a custom command"), "add-new.custom")

        add_new_application = Gio.SimpleAction.new("application", None)
        add_new_application.connect("activate",
                                    self.on_add_new_application_activated)

        add_new_custom = Gio.SimpleAction.new("custom", None)
        add_new_custom.connect("activate", self.on_add_new_custom_activated)

        actiongroup.add_action(add_new_application)
        actiongroup.add_action(add_new_custom)

        # Create popover
        self.popover = Gtk.Popover.new_from_model(self.objects.add_new, menu)
        self.popover.set_position(Gtk.PositionType.BOTTOM)
        self.popover.insert_action_group("add-new", actiongroup)

        self.objects.add_new.set_popover(self.popover)

        # Set-up the add_new_custom_dialog
        self.objects.add_new_custom_dialog.add_buttons(_("_Remove"),
                                                       Gtk.ResponseType.NO,
                                                       _("_Cancel"),
                                                       Gtk.ResponseType.CANCEL,
                                                       _("_Save"),
                                                       Gtk.ResponseType.OK)

        # Bind sensitiveness of the parent with the visibility of the new window
        self.objects.add_new_custom_dialog.bind_property(
            "visible", self.objects.main, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN)

        # Make the Select button the default
        self.objects.add_new_custom_dialog.set_default_response(
            Gtk.ResponseType.OK)

        # Set destructive-action to the Remove button
        self.objects.add_new_custom_dialog.get_widget_for_response(
            Gtk.ResponseType.NO).get_style_context().add_class(
                "destructive-action")

        self.add_applications()

    def on_scene_asked_to_close(self):
        """
		Cleanup
		"""

        if self.application_selection_dialog:
            self.application_selection_dialog.destroy()
            self.application_selection_dialog = None

        return True
예제 #3
0
class Scene(quickstart.scenes.BaseScene):
	"""
	The main scene.
	"""
	
	events = {
		"response": ("add_new_custom_dialog",), # the application selection dialog is handled manually
		"delete-event" : ("add_new_custom_dialog",),
		"changed" : ("custom_name", "custom_command",),
	}
	
	application_selection_dialog = None
	
	desktop_list = []
	
	current_edit_informations = {}
	
	def on_custom_entry_changed(self, entry):
		"""
		Fired when a custom entry has been changed.
		This method isn't directly connected to the custom GtkEntries, but it's
		directly linked to the on_custom_<id>_changed methods that quickstart
		expects.
		"""
		
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive(
			not (
				(
					self.objects.custom_name.get_text_length() == 0
				) or (
					self.objects.custom_command.get_text_length() == 0
				)
			)
		)
	
	on_custom_name_changed = on_custom_entry_changed
	on_custom_command_changed = on_custom_entry_changed
	
	def on_application_selection_dialog_response(self, dialog, response_id):
		"""
		Fired when the user triggered a response on the application_selection_dialog.
		"""
		
		# Hide
		dialog.hide()

		if response_id in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
			return
		
		# Get selection
		desktop_file = dialog.get_selection()[1]
		desktop_basename = os.path.basename(desktop_file)
		
		if os.path.basename(desktop_file) in self.desktop_list:
			# Already in list, bye
			return
		
		# Copy the desktop file and prepend a new row.
		# FIXME: currently applications with KDE in "OnlyShowIn" will get
		# regularly added, but they won't be autostarted by vera and they
		# will not show again on this module.
		#
		# This can be resolved once vera supports a "force-list", see
		# https://github.com/vera-desktop/vera/issues/2
		
		# Copy the desktop file to ~/.config/autostart
		directory = os.path.expanduser("~/.config/autostart")
		target_file = os.path.join(directory, desktop_basename)
		if not os.path.exists(directory):
			os.makedirs(directory)
		shutil.copy2(
			desktop_file,
			target_file
		)

		entry = DesktopEntry(target_file)
		
		row = ApplicationRow(desktop_basename, entry)
		
		# Connect the changed signal
		row.connect("changed", self.on_row_changed)
		
		# Connect the requests_edit signal
		row.connect("requests_edit", self.on_row_requests_edit)
		
		# Prepend the row
		self.objects.list.prepend(row)
		
		self.desktop_list.append(desktop_basename)
	
	def on_add_new_application_activated(self, menuitem, parameter):
		"""
		Fired when the add new application item has been selected.
		"""

		if not self.application_selection_dialog:
			self.application_selection_dialog = ApplicationSelectionDialog()
			self.application_selection_dialog.build_application_list()
			
			# Connect response signal
			self.application_selection_dialog.connect("response", self.on_application_selection_dialog_response)

			# Bind sensitiveness of the parent with the visibility of the new window
			self.application_selection_dialog.bind_property(
				"visible",
				self.objects.main,
				"sensitive",
				GObject.BindingFlags.INVERT_BOOLEAN
			)
		
		self.application_selection_dialog.show()
	
	def on_add_new_custom_activated(self, menuitem, parameter):
		"""
		Fired when the add new custom application item has been selected.
		"""
		
		# Set the proper title
		self.objects.add_new_custom_dialog.set_title(_("Add a new custom command"))
		
		# Hide the Remove button
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).hide()
		
		# Disable the sensitiveness of the Select button
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive(False)
		
		# Grab focus on the custom_name entry
		self.objects.custom_name.grab_focus()
		
		# Show the add_new_custom dialog
		self.objects.add_new_custom_dialog.show()

	def on_row_requests_edit(self, row, application_desktop):
		"""
		Fired when the add new custom application item has been selected.
		"""
		
		# Set the proper title
		self.objects.add_new_custom_dialog.set_title(_("Edit application"))
		
		# Show the Remove button
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).show()
		
		# Disable the sensitiveness of the Select button
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.OK).set_sensitive(False)
		
		# Grab focus on the custom_name entry
		self.objects.custom_name.grab_focus()

		# Preload Name and Exec
		self.objects.custom_name.set_text(application_desktop.getName())
		self.objects.custom_command.set_text(application_desktop.getExec())
		
		# Populate self.current_edit_informations
		self.current_edit_informations["row"] = row
		self.current_edit_informations["desktop"] = application_desktop
		
		# Show the add_new_custom dialog
		self.objects.add_new_custom_dialog.show()
	
	def on_add_new_custom_dialog_response(self, dialog, response_id):
		"""
		Fired when the user triggered a response on the add_new_custom_dialog.
		"""
		
		# Clunky way to see if we have edit mode, but it works
		on_edit = dialog.get_widget_for_response(Gtk.ResponseType.NO).props.visible
		
		if not on_edit and response_id == Gtk.ResponseType.OK:
			# Obtain a working filename
			directory = os.path.expanduser("~/.config/autostart")
			if not os.path.exists(directory):
				os.makedirs(directory)
			
			filename = os.path.join(
				directory,
				self.objects.custom_name.get_text().lower().replace(" ","-") + ".custom%s.desktop" %
					(random.randint(0,1000),)
			)
			if os.path.exists(filename):
				return on_add_new_custom_dialog_response(dialog, response_id)
			
			desktop_basename = os.path.basename(filename)
			
			entry = DesktopEntry(filename)
			entry.set("Version", 1.0)
			entry.set("Name", self.objects.custom_name.get_text())
			entry.set("Exec", self.objects.custom_command.get_text())
			entry.set("X-Vera-Autostart-Phase", "Other")
			entry.write()
			
			row = ApplicationRow(desktop_basename, entry)
			
			# Connect the changed signal
			row.connect("changed", self.on_row_changed)

			# Connect the requests_edit signal
			row.connect("requests_edit", self.on_row_requests_edit)
			
			# Prepend the row
			self.objects.list.prepend(row)
			
			self.desktop_list.append(desktop_basename)
		elif on_edit and response_id == Gtk.ResponseType.OK:
			# Edit
			
			self.current_edit_informations["desktop"].set("Name", self.objects.custom_name.get_text(), locale=True)
			self.current_edit_informations["desktop"].set("Exec", self.objects.custom_command.get_text())
			self.current_edit_informations["desktop"].write()
			
			self.current_edit_informations["row"].name.set_text(self.objects.custom_name.get_text())
		elif on_edit and response_id == Gtk.ResponseType.NO:
			# Remove
			
			# Cleanup the entry from the ignore list by ensuring that
			# it's enabled in its last moments...
			self.on_row_changed(
				self.current_edit_informations["row"],
				os.path.basename(self.current_edit_informations["desktop"].filename),
				True
			)
			
			# Finally, remove
			os.remove(self.current_edit_informations["desktop"].filename)
			self.current_edit_informations["row"].destroy()
		
		# Hide
		dialog.hide()
		
		# Cleanup
		self.objects.custom_name.set_text("")
		self.objects.custom_command.set_text("")
		self.current_edit_informations = {}
	
	def on_add_new_custom_dialog_delete_event(self, dialog, event):
		"""
		Fired when the delete-event event of the add_new_custom_dialog is emitted.
		"""
		
		# Do not destroy
		return True
	
	def on_row_changed(self, row, application, enabled):
		"""
		Fired when the switch of a row has been modified.
		"""
		
		if enabled and application in BLACKLIST:
			# Remove from the blacklist
			BLACKLIST.remove(application)
		elif not enabled and application not in BLACKLIST:
			# Add to the blacklist
			BLACKLIST.append(application)
		
		# Set the new array
		SETTINGS.set_strv("autostart-ignore", BLACKLIST)
	
	#@quickstart.threads.on_idle
	@quickstart.threads.thread
	def add_applications(self):
		"""
		Populates the self.objects.list ListBox with the applications
		in SEARCH_PATH.
		"""
		
		for path in SEARCH_PATH:
			
			if not os.path.exists(path): continue
			
			for application in os.listdir(path):
				
				# Add the application, if we can
				try:
					entry = DesktopEntry(os.path.join(path, application))
					if not "KDE" in entry.getOnlyShowIn() and not application in self.desktop_list:
						
						# While excluding only KDE is not ideal, we do so
						# to have consistency with vera's AutostartManager.
						# This check is obviously a FIXME.
						
						row = ApplicationRow(application, entry)
						
						# Connect the changed signal
						row.connect("changed", self.on_row_changed)
						
						# Connect the requests_edit signal
						row.connect("requests_edit", self.on_row_requests_edit)
						
						GObject.idle_add(self.objects.list.insert,
							row,
							-1
						)
						
						self.desktop_list.append(application)
				except:
					print("Unable to show informations for %s." % application)
	
	def prepare_scene(self):
		"""
		Scene setup.
		"""
				
		self.scene_container = self.objects.main
		
		# Create menu
		actiongroup = Gio.SimpleActionGroup.new()
		
		menu = Gio.Menu()
		menu.append(
			_("Select..."),
			"add-new.application"
		)
		menu.append(
			_("Use a custom command"),
			"add-new.custom"
		)
		
		add_new_application = Gio.SimpleAction.new("application", None)
		add_new_application.connect("activate", self.on_add_new_application_activated)
		
		add_new_custom = Gio.SimpleAction.new("custom", None)
		add_new_custom.connect("activate", self.on_add_new_custom_activated)
		
		actiongroup.add_action(add_new_application)
		actiongroup.add_action(add_new_custom)
		
		# Create popover
		self.popover = Gtk.Popover.new_from_model(self.objects.add_new, menu)
		self.popover.set_position(Gtk.PositionType.BOTTOM)
		self.popover.insert_action_group("add-new", actiongroup)

		self.objects.add_new.set_popover(self.popover)
		
		# Set-up the add_new_custom_dialog
		self.objects.add_new_custom_dialog.add_buttons(
			_("_Remove"), Gtk.ResponseType.NO,
			_("_Cancel"), Gtk.ResponseType.CANCEL,
			_("_Save"), Gtk.ResponseType.OK
		)

		# Bind sensitiveness of the parent with the visibility of the new window
		self.objects.add_new_custom_dialog.bind_property(
			"visible",
			self.objects.main,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN
		)

		# Make the Select button the default
		self.objects.add_new_custom_dialog.set_default_response(Gtk.ResponseType.OK)
		
		# Set destructive-action to the Remove button
		self.objects.add_new_custom_dialog.get_widget_for_response(Gtk.ResponseType.NO).get_style_context().add_class("destructive-action")
		
		self.add_applications()
	
	def on_scene_asked_to_close(self):
		"""
		Cleanup
		"""
		
		if self.application_selection_dialog:
			self.application_selection_dialog.destroy()
			self.application_selection_dialog = None
		
		return True
예제 #4
0
class Scene(quickstart.scenes.BaseScene):

    application_selection_dialog = None

    events = {
        "destroy": ("main", ),
        "toggled": ("enabled_checkbox", ),
        "clicked": (
            "add_button",
            "remove_button",
        ),
        "cursor-changed": ("enabled_treeview", ),
        "changed": ("position_combo", )
    }

    def on_enabled_treeview_cursor_changed(self, treeview):
        """ Fired when the user changed something on the enabled_treeview. """

        # Enable remove button
        GObject.idle_add(self.objects["remove_button"].set_sensitive, True)

    def on_add_button_clicked(self, button):
        """ Fired when the Add launcher button has been clicked. """

        if not self.application_selection_dialog:
            self.application_selection_dialog = ApplicationSelectionDialog()
            self.application_selection_dialog.build_application_list()

            # Connect response signal
            self.application_selection_dialog.connect(
                "response", self.on_application_selection_dialog_response)

            # Bind sensitiveness of the parent with the visibility of the new window
            self.application_selection_dialog.bind_property(
                "visible", self.objects["main"], "sensitive",
                GObject.BindingFlags.INVERT_BOOLEAN)

        self.application_selection_dialog.show()

    def on_remove_button_clicked(self, button):
        """ Fired when the Remove launcher button has been clicked. """

        # Get selection
        selection = self.objects["enabled_treeview"].get_selection()
        model, treeiter = selection.get_selected()

        del model[treeiter]

        # Disable remove button if we should
        if len(model) == 0:
            GObject.idle_add(self.objects["remove_button"].set_sensitive,
                             False)

    def on_application_selection_dialog_response(self, dialog, response_id):
        """
		Fired when the user triggered a response on the application_selection_dialog.
		"""

        # Hide
        dialog.hide()

        if response_id in (Gtk.ResponseType.CANCEL,
                           Gtk.ResponseType.DELETE_EVENT):
            return

        # Get and add selection
        self.enabled_model.append(dialog.get_selection())

    def on_scene_asked_to_close(self):
        """ Fired when the back button has been pressed """

        settings = Gtk.Settings.get_default()
        icon_theme = settings.get_property("gtk-icon-theme-name")

        if not os.path.exists(os.path.dirname(CONFIG)):
            os.makedirs(os.path.dirname(CONFIG))

        with open(CONFIG, "w") as f:
            if self.objects["position_combo"].get_active(
            ) != position_dict["bottom"]:
                # Panel position. If it is Bottom do not save it.
                # This avoids messing up with the panel position for those
                # who already modified it in the *primary* config.
                f.write("panel_position = %s left horizontal\n" %
                        self.objects["combostore"]
                        [self.objects["position_combo"].get_active_iter()][1])

                # Ensure that windows can be on top of the panel if the position is top
                if self.objects["position_combo"].get_active(
                ) == position_dict["top"]:
                    f.write("panel_layer = bottom\n")
            if self.objects["Hide_checkbox"].get_active():
                f.write("autohide = 1\n")

            if self.objects["ampm_enabled"].get_active():
                f.write("time1_format = %I:%M %p\n")

            if self.objects["enabled_checkbox"].get_active():
                f.write("panel_items = LTSC\n")
            else:
                f.write("panel_items = TSC\n")

            f.write("launcher_icon_theme = %s\n" % icon_theme)

            for treeiter in self.enabled_model:
                f.write("launcher_item_app = %s\n" % treeiter[1])

            if self.objects["inverted_scroll_actions"].get_active():
                f.write(
                    "mouse_scroll_down = toggle\nmouse_scroll_up = iconify\n")

        os.system("killall -SIGUSR1 tint2")

        # Destroy application_selection_dialog
        if self.application_selection_dialog:
            self.application_selection_dialog.destroy()
            self.application_selection_dialog = None

        return True

    def on_main_destroy(self, window):
        """ Fired when the main window should be destroyed. """

        Gtk.main_quit()

    def on_enabled_checkbox_toggled(self, checkbox):
        """ Fired when the enabled checkbox has been toggled. """

        if checkbox.get_active():
            GObject.idle_add(self.objects["enabled_box"].set_sensitive, True)
            GObject.idle_add(self.objects["add_button"].set_sensitive, True)
        else:
            GObject.idle_add(self.objects["enabled_box"].set_sensitive, False)

    def on_position_combo_changed(self, combo):
        """ Fired when the position combobox has been changed. """

        # Pre-set "Invert mouse scroll actions" if the newly selected
        # position is Top.
        if self.objects["position_combo"].get_active() == position_dict["top"]:
            self.objects["inverted_scroll_actions"].set_active(True)
        else:
            self.objects["inverted_scroll_actions"].set_active(False)

    @quickstart.threads.thread
    def initialize(self):
        """ Builds the enabled list. """

        # Set-up the enabled_treeview
        self.enabled_model = Gtk.ListStore(str, str, Gio.Icon)
        # Link the treeview
        self.objects["enabled_treeview"].set_model(self.enabled_model)

        # Column
        column = Gtk.TreeViewColumn("Everything")

        # Icon
        cell_icon = Gtk.CellRendererPixbuf()
        column.pack_start(cell_icon, False)
        column.add_attribute(cell_icon, "gicon", 2)

        # Text
        cell_text = Gtk.CellRendererText()
        column.pack_start(cell_text, False)
        column.add_attribute(cell_text, "text", 0)

        # Append
        self.objects["enabled_treeview"].append_column(column)

        # Position combo: create renderer
        cellrenderer = Gtk.CellRendererText()
        self.objects["position_combo"].pack_start(cellrenderer, True)
        self.objects["position_combo"].add_attribute(cellrenderer, "text", 0)

        # Default position is "Bottom" (will be overriden later if we need to)
        self.objects["position_combo"].set_active(position_dict["bottom"])

        # Open config
        if os.path.exists(CONFIG):

            up_inverted = False
            down_inverted = False

            with open(CONFIG) as f:
                for line in f.readlines():
                    try:
                        line = line.split("=")
                        if line[0].startswith("launcher_item_app"):
                            # A launcher!
                            path = line[1].strip("\n").replace(" /", "/", 1)
                            desktopentry = xdg.DesktopEntry.DesktopEntry(path)
                            iconpath = desktopentry.getIcon()
                            if iconpath and iconpath.startswith("/"):
                                icon = Gio.Icon.new_for_string(iconpath)
                            elif iconpath:
                                icon = Gio.ThemedIcon.new(
                                    iconpath.replace(".png", ""))
                            else:
                                icon = None
                            self.enabled_model.append(
                                (desktopentry.getName(), path, icon))
                        elif line[0].startswith("panel_items"):
                            # Is the launcher enabled or not?
                            if "L" in line[1]:
                                # YES!
                                self.objects["enabled_checkbox"].set_active(
                                    True)
                            else:
                                # No :/
                                GObject.idle_add(
                                    self.objects["enabled_box"].set_sensitive,
                                    False)
                        elif line[0].startswith("time1_format"):
                            # AM/PM?
                            if "%p" in line[1]:
                                # Yes!
                                self.objects["ampm_enabled"].set_active(True)
                            else:
                                # No
                                self.objects["ampm_enabled"].set_active(False)
                        elif line[0].startswith("autohide"):
                            # Autohide?
                            if "1" in line[1]:
                                # Yes
                                self.objects["Hide_checkbox"].set_active(True)
                            else:
                                # No
                                self.objects["Hide_checkbox"].set_active(False)
                        elif line[0].startswith("panel_position"):
                            # Position
                            splt = line[1].strip("\n").split(" ")
                            while splt.count(""):
                                splt.remove("")
                            self.objects["position_combo"].set_active(
                                position_dict[splt[0]])
                        elif line[0].startswith("mouse_scroll_"):
                            # Mouse scroll (up/down) actions.
                            # Usually if they are on the secondary_config
                            # it's because they're inverted, but let's check
                            # anyways...
                            if "down" in line[0] and "toggle" in line[1]:
                                down_inverted = True
                            elif "up" in line[0] and "iconify" in line[1]:
                                up_inverted = True
                    except Exception:
                        print("Error while loading configuration.")

            self.objects["inverted_scroll_actions"].set_active(down_inverted
                                                               and up_inverted)

        else:
            # Ensure the enabled_checkbox is not active.
            self.objects["enabled_checkbox"].set_active(False)
            self.objects["add_button"].set_sensitive(False)

        # First start, disable remove button.
        GObject.idle_add(self.objects["remove_button"].set_sensitive, False)

    def prepare_scene(self):
        """ Called when doing the scene setup. """

        self.scene_container = self.objects.main

        self.initialize()

        self.objects["main"].show_all()