コード例 #1
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "changed": ["selected_channel"],
        "toggled": [
            "enable_proposed_updates", "enable_development_updates",
            "show_details_button"
        ],
        "clicked": ["refresh_button", "download_button", "install_button"],
        "realize": ["install_scene"],
        "size-allocate": ["details_textview"],
    }

    # Used to define the currently-enabled semplice-base channel.
    base_channel_enabled = None

    # Current variant
    current_variant = "current"

    building = False

    package_transactions = {}

    def on_scene_asked_to_close(self):
        """
		Do some cleanup
		"""

        # If we are in the install scene, return to the main one,
        # but only if the installation has been completed
        if self.objects.main.get_visible_child(
        ) == self.objects.install_scene and not self.handler.props.installing:
            self.objects.main.set_visible_child(self.objects.summary)

            # Clear list
            self.update_list.clear()

            # Check for updates
            self.handler.check(force=True)

            return False

        # This is relatively safe
        self.bus_cancellable.cancel()

        return True

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

        self.scene_container = self.objects.main

        # Connect to the handler
        self.handler = UpdateHandler()

        # Convenience variable that houses the "installing" state.
        # Used when checking whether to continue following the apt log,
        # without flooding the DBus bus.
        self._installing = False

        # Determines if we are following the log or not
        self.following_log = False

        # APT_LOG follower (FIXME: should move it elsewhere)
        self.follower = None

        # Determines if the log has been fully written to the buffer
        self.log_written = False

        # Set appropriate font size and weight for the "Distribution upgrades" label
        context = self.objects.distribution_upgrade_label.create_pango_context(
        )
        desc = context.get_font_description()
        desc.set_weight(Pango.Weight.LIGHT)  # Weight
        desc.set_size(Pango.SCALE * 13)  # Size
        self.objects.distribution_upgrade_label.override_font(desc)

        # Do the same for the "Application updates" label
        self.objects.application_updates_label.override_font(desc)

        # ...and for the "The software is up-to-date" one
        self.objects.application_updates_status.override_font(desc)

        # ..and for the error_description
        desc.set_size(Pango.SCALE * 10)
        self.objects.error_description.override_font(desc)

        # ...and for the "Semplice is installing the updates" one
        desc.set_size(Pango.SCALE * 20)
        self.objects.install_label.override_font(desc)

        # Create unlockbar
        self.unlockbar = UnlockBar("org.semplicelinux.channels.manage")
        self.objects.summary.pack_start(self.unlockbar, False, False, 0)

        # Set-up channel combobox
        renderer = Gtk.CellRendererText()
        self.objects.selected_channel.pack_start(renderer, True)
        self.objects.selected_channel.add_attribute(renderer, "text", 1)
        self.objects.selected_channel.add_attribute(renderer, "sensitive", 2)

        # Create update list
        self.update_list = UpdateList()
        self.objects.application_updates_scroll.add(self.update_list)
        #self.objects.application_updates_content.pack_start(self.update_list, True, True, 5)
        #self.update_list.prepend(Gtk.Label("HAAA"))
        #print(len(self.update_list), self.update_list.props.empty, self.update_list.props.selection_mode)
        self.update_list.show_all()

        # Open AppStream database
        # FIXME pending AppStream API update. See #4
        #Database.open()

        #for item in ["glade", "pokerth", "banshee", "gnome-software"]:
        #	self.update_list.add_item(-1, item, "XX", "upgrade", True)

        # FIXME
        self.objects.distribution_upgrade.hide()

        # Connect to status-toggled
        self.update_list.connect("status-toggled", self.on_status_toggled)

        # Connect to lock failed:
        self.handler.connect("lock-failed", self.on_lock_failed)

        # Connect to generic failure:
        self.handler.connect("generic-failure", self.on_generic_failure)

        # Connect to update found:
        self.handler.connect("update-found", self.on_update_found)

        # Connect to package-status-changed
        self.handler.connect("package-status-changed",
                             self.on_package_status_changed)

        # Connect to package-fetch signals
        self.handler.connect("package-fetch-started",
                             self.on_package_fetch_started)
        self.handler.connect("package-fetch-failed",
                             self.on_package_fetch_failed)
        self.handler.connect("package-fetch-finished",
                             self.on_package_fetch_finished)

        # React when checking changes
        self.handler.connect("notify::checking", self.on_checking_changed)

        # React when refreshing
        self.handler.connect("notify::refreshing", self.on_refreshing_changed)

        # React when downloading
        self.handler.connect("notify::downloading",
                             self.on_downloading_changed)

        # React when installing
        self.handler.connect("notify::installing", self.on_installing_changed)

        # React when we know the total size to download
        self.handler.connect("notify::update-required-download",
                             self.on_update_required_download_changed)

        # Ensure that the updates_frame is not sensitive when the settings
        # are locked...
        self.unlockbar.bind_property("lock", self.objects.settings_frame,
                                     "sensitive",
                                     GObject.BindingFlags.INVERT_BOOLEAN)

        # Show the up-to-date container if the update list is empty
        self.update_list.bind_property(
            "empty", self.objects.application_updates_status, "visible",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "status-scene", self.objects.application_updates_status,
            "visible_child_name",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE)
        #self.handler.bind_property(
        #	"cache-operation",
        #	self.objects.application_updates_checking_container,
        #	"visible",
        #	GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
        #)
        #self.objects.application_updates_checking_container.bind_property(
        #	"visible",
        #	self.objects.software_uptodate_container,
        #	"visible",
        #	GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
        #)
        self.update_list.bind_property(
            "empty", self.objects.application_updates_scroll, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.update_list.bind_property(
            "empty", self.objects.application_updates_actions, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Show download indicators when the cache is refreshing
        self.handler.bind_property("cache-operation", self.objects.spinner,
                                   "active", GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-operation",
                                   self.objects.download_rate, "visible",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-operation",
                                   self.objects.download_eta, "visible",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("download-rate", self.objects.download_rate,
                                   "label", GObject.BindingFlags.DEFAULT)
        self.handler.bind_property("download-eta", self.objects.download_eta,
                                   "label", GObject.BindingFlags.DEFAULT)
        self.handler.bind_property("download-current-item",
                                   self.objects.download_rate, "tooltip_text",
                                   GObject.BindingFlags.DEFAULT)
        self.handler.bind_property(
            "cache-operation", self.objects.refresh_button, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "cache-operation", self.update_list, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property(
            "cache-operation", self.objects.application_updates_actions,
            "sensitive", GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Remove sensitiveness on checkbuttons when downloading
        self.handler.bind_property(
            "downloading", self.update_list.package_checkbox, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Update labels in the action buttons accordingly to the current mode
        self.handler.bind_property("download-operation-label",
                                   self.objects.download_button, "label",
                                   GObject.BindingFlags.SYNC_CREATE)
        self.handler.bind_property("install-operation-label",
                                   self.objects.install_button, "label",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Update sensitiveness of the install_button when downloading
        self.handler.bind_property(
            "downloading", self.objects.install_button, "sensitive",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        # Update the installation_scene label
        self.handler.bind_property("install-scene-label",
                                   self.objects.install_label, "label",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Show the log textview when "Show details" has been pressed
        self.objects.show_details_button.bind_property(
            "active", self.objects.details_scrolled, "visible",
            GObject.BindingFlags.SYNC_CREATE)

        # Switch into the installation mode if channels is already installing
        if self.handler.props.installing:
            self.on_installing_changed()
        else:
            # Check for updates
            if not self.handler.props.refreshing:
                self.handler.check()

            # Downloading? Enable the mode
            if self.handler.props.downloading:
                #self.on_downloading_changed()
                self.update_list.enable_downloading_mode()

    def on_install_scene_realize(self, widget):
        """
		Fired when the install_progress is going to be realized.
		"""

        # Create a circular progress bar, and add it to the stack
        self.install_progress = CircularProgressBar()
        self.objects.progress_stack.add_named(self.install_progress,
                                              "progress")

        self.handler.bind_property("install-progress", self.install_progress,
                                   "fraction",
                                   GObject.BindingFlags.SYNC_CREATE)

        # Resize the CircularProgressBar so that it has the same requested
        # width and height of the details_scrolled
        width, height = self.objects.details_scrolled.get_size_request()
        self.install_progress.set_size_request(width, height)

        # Ensure that the CircularProgressBar stays visible when the
        # details textview is not
        self.objects.details_scrolled.bind_property(
            "visible", self.install_progress, "visible",
            GObject.BindingFlags.INVERT_BOOLEAN
            | GObject.BindingFlags.SYNC_CREATE)

        #self.install_progress.show()

        #self.objects.show_details_button.set_active(True)

    def on_show_details_button_toggled(self, button):
        """
		Fired when the show_details_button has been toggled.
		"""

        if button.props.active and not self.following_log and not self.log_written:
            self.follow_log()

    @quickstart.threads.on_idle
    def append_text(self, line):
        """
		Appends the text to the details_buffer.
		"""

        self.objects.details_buffer.insert(
            self.objects.details_buffer.get_end_iter(), line)

    @quickstart.threads.thread
    def follow_log(self):
        """
		Follows the APT logfile
		"""

        self.following_log = True

        # Wait until it has been properly created
        while not os.path.exists(APT_LOGFILE) and self._installing:
            time.sleep(1)

        if self._installing:
            self.follower = Follower(APT_LOGFILE)
            for line in self.follower:
                Gdk.threads_enter()
                self.append_text(line)
                Gdk.threads_leave()
        else:
            # Not installed anymore, the upgrade process completed
            # So directly dump the entire file:
            with open(APT_LOGFILE) as f:
                for line in f:
                    Gdk.threads_enter()
                    self.append_text(line)
                    Gdk.threads_leave()
            self.log_written = True

        self.following_log = False

    def on_details_textview_size_allocate(self, textview, allocation):
        """
		Fired when the new text has been properly allocated.
		"""

        adj = self.objects.details_scrolled.get_vadjustment()
        adj.set_value(adj.props.upper - adj.props.page_size)

    def on_installing_changed(self, handler=None, value=None):
        """
		Fired when the service began the package installation.
		"""

        # FIXME: Remove that once proper DBus proprieties caching
        # has been implemented
        self._installing = self.handler.props.installing

        # FIXME: Should move that elsewhere
        if not self._installing and self.following_log and self.follower:
            # Stop the follower
            print("Stopping follower")
            self.follower.stop()
            self.log_written = True
            self.follower = None

        if self._installing:
            # New installation, ensure that we are in a somewhat clean state
            self.log_written = False

            # Hide the details scrolled for now
            self.objects.details_scrolled.hide()

            # Finally, show the scene!
            self.scene_container.set_visible_child(self.objects.install_scene)

    def on_generic_failure(self, handler, error, description):
        """
		Fired when the APT Lock failed.
		"""

        dialog = Gtk.MessageDialog(
            self.objects.main.get_toplevel(),
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
            Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, error)
        dialog.set_title(_("Error"))
        dialog.format_secondary_text(description)

        dialog.run()
        dialog.destroy()

    def on_lock_failed(self, handler):
        """
		Fired when the APT Lock failed.
		"""

        return self.on_generic_failure(
            handler, _("Unable to lock the APT database"),
            _("Please close the active package managers."))

    @quickstart.threads.on_idle
    def on_update_found(self, handler, id, name, version, reason, status,
                        size):
        """
		Fired when an update has been found.
		"""

        self.update_list.add_item(id, name, version, reason, status, size)

    @quickstart.threads.on_idle
    def on_package_status_changed(self, handler, id, reason):
        """
		Fired when a package status has been changed.
		"""

        print("STATUS!")
        self.update_list.update_status(id, reason)

    def on_package_fetch_started(self, handler, transaction_id, description,
                                 shortdesc):
        """
		Fired when a package is being fetched.
		"""

        if not shortdesc in self.update_list.names_with_id:
            # wat?
            return

        # Get package id from the UpdateList
        id = self.update_list.names_with_id[shortdesc]

        # Associate transaction with the id
        self.package_transactions[transaction_id] = id

        # Finally update the status on the list
        self.update_list.set_downloading(id, True)

    def on_package_fetch_failed(self, handler, transaction_id):
        """
		Fired when a package failed to download.
		"""

        # FIXME: should notify the user!

        if not transaction_id in self.package_transactions:
            return

        self.update_list.set_downloading(
            self.package_transactions[transaction_id], False)

    def on_package_fetch_finished(self, handler, transaction_id):
        """
		Fired when a package has been downloaded.
		"""

        if not transaction_id in self.package_transactions:
            return

        self.update_list.set_downloading(
            self.package_transactions[transaction_id], False)

    def on_downloading_changed(self, handler, value):
        """
		Fired when handler's downloading property changed.
		"""

        if handler.props.downloading:
            self.update_list.enable_downloading_mode()
        else:
            self.update_list.disable_downloading_mode()
            self.package_transactions = {}

    def on_status_toggled(self, updatelist, id, reason):
        """
		Fired when a package status changed locally.
		"""

        print(id, reason)

        self.handler.change_status(id, reason)

    def on_download_button_clicked(self, button):
        """
		Fired when the download button has been clicked.
		"""

        if not self.handler.props.downloading:
            # Start fetching
            self.handler.fetch()
        else:
            # Stop fetching
            self.handler.fetch_stop()

    def on_install_button_clicked(self, button):
        """
		Fired when the install button has been clicked.
		"""

        if not self.handler.props.downloading:
            # Start fetching
            self.handler.fetch(trigger_installation=True)

    def on_refresh_button_clicked(self, button):
        """
		Fired when the refresh button has been clicked.
		"""

        # Clear list
        self.update_list.clear()

        self.handler.refresh()

    def on_checking_changed(self, handler, value):
        """
		Fired when handler's checking property changed.
		"""

        print("Checking changed!")

        if not self.handler.props.checking:
            # Expand the update_list
            self.update_list.expand_all()

    def on_refreshing_changed(self, handler, value):
        """
		Fired when handler's refreshing property changed.
		"""

        if not self.handler.props.refreshing:
            # Check for updates
            self.handler.check()

    def on_update_required_download_changed(self, handler, value):
        """
		Fired when handler's update-required-download property changed.
		"""

        self.objects.download_size.show()
        self.objects.download_size.set_text(
            _("Total download size: %s") %
            self.handler.props.update_required_download)

    def on_selected_channel_changed(self, combobox):
        """
		Fired when the selected channel has been changed.
		"""

        if self.building:
            return

        new_channel = self.objects.semplice_base_channels.get_value(
            combobox.get_active_iter(), 0)

        self.Channels.Enable("(s)", new_channel)

        # Store the new choice
        self.base_channel_enabled = new_channel

        # Check for features
        self.check_for_features()

    def on_enable_proposed_updates_toggled(self, checkbutton):
        """
		Fired when the Enable proposed updates checkbutton has been toggled.
		"""

        if self.building:
            return

        (self.Channels.EnableComponent if checkbutton.get_active() else
         self.Channels.DisableComponent)("(ss)", self.base_channel_enabled,
                                         PROPOSED_COMPONENT)

    def on_enable_development_updates_toggled(self, checkbutton):
        """
		Fired when the Enable development updates checkbutton has been toggled.
		"""

        if self.building:
            return

        (self.Channels.Enable if checkbutton.get_active() else
         self.Channels.Disable)("(s)", DEVELOPMENT_CHANNEL)

    @quickstart.threads.on_idle
    def check_for_features(self):
        """
		"Enable proposed updates" and "Enable development updates" are
		available only on semplice-current.
		
		This method ensures that those checkbuttons are not sensitive if
		the selected base channel is not semplice-current and loads their
		settings if it is.
		"""

        self.building = True

        if self.base_channel_enabled == CURRENT_CHANNEL:
            self.objects.enable_development_updates.set_sensitive(True)
            if self.Channels.GetEnabled("(s)", DEVELOPMENT_CHANNEL):
                self.objects.enable_development_updates.set_active(True)
            else:
                self.objects.enable_development_updates.set_active(False)
        else:
            self.objects.enable_development_updates.set_sensitive(False)
            self.objects.enable_development_updates.set_active(False)

        # Check if the "proposed" channel is available
        if self.Channels.HasComponent("(ss)", self.base_channel_enabled,
                                      PROPOSED_COMPONENT):
            self.objects.enable_proposed_updates.set_sensitive(True)

            if self.Channels.GetComponentEnabled("(ss)",
                                                 self.base_channel_enabled,
                                                 PROPOSED_COMPONENT):
                self.objects.enable_proposed_updates.set_active(True)
            else:
                self.objects.enable_proposed_updates.set_active(False)
        else:
            self.objects.enable_proposed_updates.set_sensitive(False)
            self.objects.enable_proposed_updates.set_active(False)

        self.building = False

    @quickstart.threads.on_idle
    def load(self):
        """
		Reloads the semplice_base_channels ListStore.
		"""

        self.building = True

        self.objects.semplice_base_channels.clear()

        for channel in self.Providers.WhatProvides("(s)", BASE_PROVIDER):
            details = self.Channels.GetDetails("(sas)", channel, ["name"])
            itr = self.objects.semplice_base_channels.append([
                channel, details["name"] if "name" in details else channel,
                False if self.current_variant == "current"
                and channel != CURRENT_CHANNEL else True
            ])

            if self.Channels.GetEnabled("(s)", channel):
                self.objects.selected_channel.set_active_iter(itr)
                self.base_channel_enabled = channel

        self.check_for_features()

        # self.building will be restored by check_for_features()

    def on_scene_called(self):
        """
		Fired when the scene has been called.
		"""

        # Load current variant
        if os.path.exists(VARIANT_FILE):
            with open(VARIANT_FILE, "r") as f:
                self.current_variant = f.read().strip()

        # Enter in the bus
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.Channels = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/channels/channels",
            "org.semplicelinux.channels.channels", self.bus_cancellable)
        self.Providers = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/channels/providers",
            "org.semplicelinux.channels.providers", self.bus_cancellable)

        # We are locked
        self.unlockbar.emit("locked")

        self.load()
コード例 #2
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "value-changed": ["brightness_scale"],
    }

    building = False

    client = Up.Client.new()
    with_battery = False

    @quickstart.threads.thread
    def set_value(self, obj, value):
        """
		Sets the value
		"""

        obj('(sb)', value, True)

    def on_brightness_scale_value_changed(self, scale):
        """
		Fired when the brightness level has been changed.
		"""

        if not self.building:
            self.VeraPowerManager.SetBrightness('(i)',
                                                int(scale.get_value()) + 1)

    def on_brightness_level_changed_external(self, params=None):
        """
		Fired when the brightness level has been changed from the outside.
		"""

        if not params: params = (self.VeraPowerManager.GetBrightness(), )

        self.building = True
        self.objects.brightness_level.set_value(float(params[0]))
        self.building = False

    def on_combobox_changed(self, combobox):
        """
		Fired when a combobox has been changed.
		"""

        if self.building: return

        if combobox == self.objects.lid_switch_action:
            # Lid switch
            obj = self.VeraPowerManager.SetHandleLidSwitch
        elif combobox == self.objects.power_button_action:
            # Power button
            obj = self.VeraPowerManager.SetHandlePowerKey

        self.set_value(obj, ACTIONS[combobox.get_active()])

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

        self.scene_container = self.objects.main

        # g-signals
        self.signal_handlers = {
            "BrightnessChanged": self.on_brightness_level_changed_external,
        }

        # Create unlockbar
        self.unlockbar = UnlockBar(
            "org.semplicelinux.vera.powermanager.modify-logind")
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        # Search for batteries
        for device in self.client.get_devices():
            if device.props.is_present and device.props.power_supply and device.props.kind == Up.DeviceKind.BATTERY:
                # Found a power supply, and we'll show this in the UI.
                self.with_battery = device

                # We show only one battery, otherwise the UI will be a mess
                break

        # If there is a battery, bind properties to get live updates on the status
        if self.with_battery:
            # Show the battery frame
            self.objects.battery_frame.show()

            # Name
            self.objects.battery_name.set_text("%s %s" %
                                               (self.with_battery.props.vendor,
                                                self.with_battery.props.model))

            # Percentage on charge_bar
            self.with_battery.bind_property(
                "percentage", self.objects.charge_bar, "value",
                GObject.BindingFlags.DEFAULT
                | GObject.BindingFlags.SYNC_CREATE)

            # Percentage on label
            # Currently python-gi doesn't support bind_property_full(), which is
            # a shame because it's fantastic.
            # So we are using a connection here.
            percentage_callback = lambda x, y: self.objects.battery_percentage.set_text(
                "%s%%" % int(self.with_battery.props.percentage))
            self.with_battery.connect("notify::percentage",
                                      percentage_callback)
            percentage_callback(None, None)

            # Status on label
            # Unforunately, same as above.
            status_callback = lambda x, y: self.objects.battery_status.set_text(
                "%s, " % BATTERY_STATE[self.with_battery.props.state])
            self.with_battery.connect("notify::state", status_callback)
            status_callback(None, None)

        # Check for lid switch
        if os.path.exists("/proc/acpi/button/lid"):
            # That's a pretty dirty check
            self.objects.lid_switch_container.show()

        # Create cells for the comboboxes
        for combo in (self.objects.power_button_action,
                      self.objects.lid_switch_action):
            cellrenderer = Gtk.CellRendererText()
            combo.pack_start(cellrenderer, True)
            combo.add_attribute(cellrenderer, "text", 1)

            combo.connect("changed", self.on_combobox_changed)

        # Disable "Buttons" frame when locked
        self.unlockbar.bind_property(
            "lock", self.objects.buttons_frame, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.INVERT_BOOLEAN)

    def on_scene_called(self):
        """
		Fired when the scene has been called.
		"""

        # Locked
        self.unlockbar.emit("locked")

        # Enter in the bus
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.VeraPowerManager = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/vera/powermanager", BUS_NAME,
            self.bus_cancellable)
        # connect signals
        self.VeraPowerManager.connect(
            "g-signal",
            lambda proxy, sender, signal, params: self.signal_handlers[signal]
            (params) if signal in self.signal_handlers else None)

        # Check for backlight support
        if self.VeraPowerManager.IsBacklightSupported():
            self.objects.display_frame.show()
            self.on_brightness_level_changed_external()
        else:
            self.objects.display_frame.hide()

        # Update comboboxes
        self.building = True
        self.objects.power_button_action.set_active(
            ACTIONS.index(self.VeraPowerManager.GetHandlePowerKey()))
        self.objects.lid_switch_action.set_active(
            ACTIONS.index(self.VeraPowerManager.GetHandleLidSwitch()))
        self.building = False

    def on_scene_asked_to_close(self):
        """
		Fired when the scene has been asked to close.
		"""

        # Close dbus connection
        self.bus_cancellable.cancel()

        return True
コード例 #3
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"value-changed" : ["brightness_scale"],
	}
	
	building = False
	
	client = Up.Client.new()
	with_battery = False
	
	@quickstart.threads.thread
	def set_value(self, obj, value):
		"""
		Sets the value
		"""
		
		obj('(sb)', value, True)
	
	def on_brightness_scale_value_changed(self, scale):
		"""
		Fired when the brightness level has been changed.
		"""
		
		if not self.building:
			self.VeraPowerManager.SetBrightness('(i)', int(scale.get_value())+1)
	
	def on_brightness_level_changed_external(self, params=None):
		"""
		Fired when the brightness level has been changed from the outside.
		"""
		
		if not params: params = (self.VeraPowerManager.GetBrightness(),)
		
		self.building = True
		self.objects.brightness_level.set_value(float(params[0]))
		self.building = False
	
	def on_combobox_changed(self, combobox):
		"""
		Fired when a combobox has been changed.
		"""
		
		if self.building: return
		
		if combobox == self.objects.lid_switch_action:
			# Lid switch
			obj = self.VeraPowerManager.SetHandleLidSwitch
		elif combobox == self.objects.power_button_action:
			# Power button
			obj = self.VeraPowerManager.SetHandlePowerKey
		
		self.set_value(obj, ACTIONS[combobox.get_active()])
	
	def prepare_scene(self):
		""" Called when doing the scene setup. """
		
		self.scene_container = self.objects.main
		
		# g-signals
		self.signal_handlers = {
			"BrightnessChanged" : self.on_brightness_level_changed_external,
		}
		
		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.vera.powermanager.modify-logind")
		self.objects.main.pack_start(self.unlockbar, False, False, 0)
				
		# Search for batteries
		for device in self.client.get_devices():
			if device.props.is_present and device.props.power_supply and device.props.kind == Up.DeviceKind.BATTERY:
				# Found a power supply, and we'll show this in the UI.
				self.with_battery = device
				
				# We show only one battery, otherwise the UI will be a mess
				break
		
		# If there is a battery, bind properties to get live updates on the status
		if self.with_battery:
			# Show the battery frame
			self.objects.battery_frame.show()
			
			# Name
			self.objects.battery_name.set_text(
				"%s %s" % (
					self.with_battery.props.vendor, self.with_battery.props.model
				)
			)
			
			# Percentage on charge_bar
			self.with_battery.bind_property(
				"percentage",
				self.objects.charge_bar,
				"value",
				GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
			)
			
			# Percentage on label
			# Currently python-gi doesn't support bind_property_full(), which is
			# a shame because it's fantastic.
			# So we are using a connection here.
			percentage_callback = lambda x, y: self.objects.battery_percentage.set_text("%s%%" % int(self.with_battery.props.percentage))
			self.with_battery.connect(
				"notify::percentage",
				percentage_callback
			)
			percentage_callback(None, None)
			
			# Status on label
			# Unforunately, same as above.
			status_callback = lambda x, y: self.objects.battery_status.set_text("%s, " % BATTERY_STATE[self.with_battery.props.state])
			self.with_battery.connect(
				"notify::state",
				status_callback
			)
			status_callback(None, None)
		
		# Check for lid switch
		if os.path.exists("/proc/acpi/button/lid"):
			# That's a pretty dirty check
			self.objects.lid_switch_container.show()
		
		# Create cells for the comboboxes
		for combo in (self.objects.power_button_action, self.objects.lid_switch_action):
			cellrenderer = Gtk.CellRendererText()
			combo.pack_start(cellrenderer, True)
			combo.add_attribute(cellrenderer, "text", 1)
			
			combo.connect("changed", self.on_combobox_changed)
		
		# Disable "Buttons" frame when locked
		self.unlockbar.bind_property(
			"lock",
			self.objects.buttons_frame,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN
		)
	
	def on_scene_called(self):
		"""
		Fired when the scene has been called.
		"""
		
		# Locked
		self.unlockbar.emit("locked")

		# Enter in the bus
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.VeraPowerManager = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/vera/powermanager",
			BUS_NAME,
			self.bus_cancellable
		)
		# connect signals
		self.VeraPowerManager.connect(
			"g-signal",
			lambda proxy, sender, signal, params: self.signal_handlers[signal](params) if signal in self.signal_handlers else None
		)
		
		# Check for backlight support
		if self.VeraPowerManager.IsBacklightSupported():
			self.objects.display_frame.show()
			self.on_brightness_level_changed_external()
		else:
			self.objects.display_frame.hide()
		
		# Update comboboxes
		self.building = True
		self.objects.power_button_action.set_active(
			ACTIONS.index(self.VeraPowerManager.GetHandlePowerKey())
		)
		self.objects.lid_switch_action.set_active(
			ACTIONS.index(self.VeraPowerManager.GetHandleLidSwitch())
		)
		self.building = False
	
	def on_scene_asked_to_close(self):
		"""
		Fired when the scene has been asked to close.
		"""
		
		# Close dbus connection
		self.bus_cancellable.cancel()
		
		return True
コード例 #4
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"changed" : ["selected_channel"],
		"toggled" : ["enable_proposed_updates", "enable_development_updates", "show_details_button"],
		"clicked" : ["refresh_button", "download_button", "install_button"],
		"realize" : ["install_scene"],
		"size-allocate" : ["details_textview"],
	}
	
	# Used to define the currently-enabled semplice-base channel. 
	base_channel_enabled = None
	
	# Current variant
	current_variant = "current"
	
	building = False
	
	package_transactions = {}
	
	def on_scene_asked_to_close(self):
		"""
		Do some cleanup
		"""
		
		# If we are in the install scene, return to the main one,
		# but only if the installation has been completed
		if self.objects.main.get_visible_child() == self.objects.install_scene and not self.handler.props.installing:
			self.objects.main.set_visible_child(self.objects.summary)
			
			# Clear list
			self.update_list.clear()
			
			# Check for updates
			self.handler.check(force=True)
			
			return False
		
		# This is relatively safe
		self.bus_cancellable.cancel()
		
		return True
	
	def prepare_scene(self):
		"""
		Fired when doing the scene setup.
		"""
				
		self.scene_container = self.objects.main
		
		# Connect to the handler
		self.handler = UpdateHandler()
		
		# Convenience variable that houses the "installing" state.
		# Used when checking whether to continue following the apt log,
		# without flooding the DBus bus.
		self._installing = False
		
		# Determines if we are following the log or not
		self.following_log = False
		
		# APT_LOG follower (FIXME: should move it elsewhere)
		self.follower = None
		
		# Determines if the log has been fully written to the buffer
		self.log_written = False

		# Set appropriate font size and weight for the "Distribution upgrades" label
		context = self.objects.distribution_upgrade_label.create_pango_context()
		desc = context.get_font_description()
		desc.set_weight(Pango.Weight.LIGHT) # Weight
		desc.set_size(Pango.SCALE*13) # Size
		self.objects.distribution_upgrade_label.override_font(desc)
		
		# Do the same for the "Application updates" label
		self.objects.application_updates_label.override_font(desc)
		
		# ...and for the "The software is up-to-date" one
		self.objects.application_updates_status.override_font(desc)
		
		# ..and for the error_description
		desc.set_size(Pango.SCALE*10)
		self.objects.error_description.override_font(desc)
		
		# ...and for the "Semplice is installing the updates" one
		desc.set_size(Pango.SCALE*20)
		self.objects.install_label.override_font(desc)

		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.channels.manage")
		self.objects.summary.pack_start(self.unlockbar, False, False, 0)

		# Set-up channel combobox
		renderer = Gtk.CellRendererText()
		self.objects.selected_channel.pack_start(renderer, True)
		self.objects.selected_channel.add_attribute(renderer, "text", 1)
		self.objects.selected_channel.add_attribute(renderer, "sensitive", 2)
		
		# Create update list
		self.update_list = UpdateList()
		self.objects.application_updates_scroll.add(self.update_list)
		#self.objects.application_updates_content.pack_start(self.update_list, True, True, 5)
		#self.update_list.prepend(Gtk.Label("HAAA"))
		#print(len(self.update_list), self.update_list.props.empty, self.update_list.props.selection_mode)
		self.update_list.show_all()
		
		# Open AppStream database
		# FIXME pending AppStream API update. See #4
		#Database.open()
		
		#for item in ["glade", "pokerth", "banshee", "gnome-software"]:
		#	self.update_list.add_item(-1, item, "XX", "upgrade", True)
		
		# FIXME
		self.objects.distribution_upgrade.hide()
		
		# Connect to status-toggled
		self.update_list.connect("status-toggled", self.on_status_toggled)
		
		# Connect to lock failed:
		self.handler.connect("lock-failed", self.on_lock_failed)
		
		# Connect to generic failure:
		self.handler.connect("generic-failure", self.on_generic_failure)
		
		# Connect to update found:
		self.handler.connect("update-found", self.on_update_found)
		
		# Connect to package-status-changed
		self.handler.connect("package-status-changed", self.on_package_status_changed)
		
		# Connect to package-fetch signals
		self.handler.connect("package-fetch-started", self.on_package_fetch_started)
		self.handler.connect("package-fetch-failed", self.on_package_fetch_failed)
		self.handler.connect("package-fetch-finished", self.on_package_fetch_finished)

		# React when checking changes
		self.handler.connect("notify::checking", self.on_checking_changed)
		
		# React when refreshing
		self.handler.connect("notify::refreshing", self.on_refreshing_changed)
		
		# React when downloading
		self.handler.connect("notify::downloading", self.on_downloading_changed)
		
		# React when installing
		self.handler.connect("notify::installing", self.on_installing_changed)
		
		# React when we know the total size to download
		self.handler.connect("notify::update-required-download", self.on_update_required_download_changed)
		
		# Ensure that the updates_frame is not sensitive when the settings
		# are locked...
		self.unlockbar.bind_property(
			"lock",
			self.objects.settings_frame,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN
		)
		
		# Show the up-to-date container if the update list is empty
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_status,
			"visible",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"status-scene",
			self.objects.application_updates_status,
			"visible_child_name",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		)
		#self.handler.bind_property(
		#	"cache-operation",
		#	self.objects.application_updates_checking_container,
		#	"visible",
		#	GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE
		#)
		#self.objects.application_updates_checking_container.bind_property(
		#	"visible",
		#	self.objects.software_uptodate_container,
		#	"visible",
		#	GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		#)
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_scroll,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.update_list.bind_property(
			"empty",
			self.objects.application_updates_actions,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
				
		# Show download indicators when the cache is refreshing
		self.handler.bind_property(
			"cache-operation",
			self.objects.spinner,
			"active",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-operation",
			self.objects.download_rate,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-operation",
			self.objects.download_eta,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"download-rate",
			self.objects.download_rate,
			"label",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"download-eta",
			self.objects.download_eta,
			"label",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"download-current-item",
			self.objects.download_rate,
			"tooltip_text",
			GObject.BindingFlags.DEFAULT
		)
		self.handler.bind_property(
			"cache-operation",
			self.objects.refresh_button,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"cache-operation",
			self.update_list,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"cache-operation",
			self.objects.application_updates_actions,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Remove sensitiveness on checkbuttons when downloading
		self.handler.bind_property(
			"downloading",
			self.update_list.package_checkbox,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update labels in the action buttons accordingly to the current mode
		self.handler.bind_property(
			"download-operation-label",
			self.objects.download_button,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		self.handler.bind_property(
			"install-operation-label",
			self.objects.install_button,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update sensitiveness of the install_button when downloading
		self.handler.bind_property(
			"downloading",
			self.objects.install_button,
			"sensitive",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		# Update the installation_scene label
		self.handler.bind_property(
			"install-scene-label",
			self.objects.install_label,
			"label",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Show the log textview when "Show details" has been pressed
		self.objects.show_details_button.bind_property(
			"active",
			self.objects.details_scrolled,
			"visible",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Switch into the installation mode if channels is already installing
		if self.handler.props.installing:
			self.on_installing_changed()
		else:
			# Check for updates
			if not self.handler.props.refreshing:
				self.handler.check()
			
			# Downloading? Enable the mode
			if self.handler.props.downloading:
				#self.on_downloading_changed()
				self.update_list.enable_downloading_mode()
	
	def on_install_scene_realize(self, widget):
		"""
		Fired when the install_progress is going to be realized.
		"""
				
		# Create a circular progress bar, and add it to the stack
		self.install_progress = CircularProgressBar()
		self.objects.progress_stack.add_named(self.install_progress, "progress")
		
		self.handler.bind_property(
			"install-progress",
			self.install_progress,
			"fraction",
			GObject.BindingFlags.SYNC_CREATE
		)
		
		# Resize the CircularProgressBar so that it has the same requested
		# width and height of the details_scrolled
		width, height = self.objects.details_scrolled.get_size_request()
		self.install_progress.set_size_request(width, height)
		
		# Ensure that the CircularProgressBar stays visible when the
		# details textview is not
		self.objects.details_scrolled.bind_property(
			"visible",
			self.install_progress,
			"visible",
			GObject.BindingFlags.INVERT_BOOLEAN | GObject.BindingFlags.SYNC_CREATE
		)
		
		
		#self.install_progress.show()
		
		#self.objects.show_details_button.set_active(True)
	
	def on_show_details_button_toggled(self, button):
		"""
		Fired when the show_details_button has been toggled.
		"""
		
		if button.props.active and not self.following_log and not self.log_written:
			self.follow_log()
	
	@quickstart.threads.on_idle
	def append_text(self, line):
		"""
		Appends the text to the details_buffer.
		"""
		
		self.objects.details_buffer.insert(
			self.objects.details_buffer.get_end_iter(),
			line
		)
	
	@quickstart.threads.thread
	def follow_log(self):
		"""
		Follows the APT logfile
		"""
		
		self.following_log = True
		
		# Wait until it has been properly created
		while not os.path.exists(APT_LOGFILE) and self._installing:
			time.sleep(1)
		
		if self._installing:
			self.follower = Follower(APT_LOGFILE)
			for line in self.follower:
				Gdk.threads_enter()
				self.append_text(line)
				Gdk.threads_leave()
		else:
			# Not installed anymore, the upgrade process completed
			# So directly dump the entire file:
			with open(APT_LOGFILE) as f:
				for line in f:
					Gdk.threads_enter()
					self.append_text(line)
					Gdk.threads_leave()
			self.log_written = True
		
		self.following_log = False
	
	def on_details_textview_size_allocate(self, textview, allocation):
		"""
		Fired when the new text has been properly allocated.
		"""
		
		adj = self.objects.details_scrolled.get_vadjustment()
		adj.set_value(adj.props.upper - adj.props.page_size)

	
	def on_installing_changed(self, handler=None, value=None):
		"""
		Fired when the service began the package installation.
		"""
		
		# FIXME: Remove that once proper DBus proprieties caching
		# has been implemented
		self._installing = self.handler.props.installing
		
		# FIXME: Should move that elsewhere
		if not self._installing and self.following_log and self.follower:
			# Stop the follower
			print("Stopping follower")
			self.follower.stop()
			self.log_written = True
			self.follower = None
		
		if self._installing:
			# New installation, ensure that we are in a somewhat clean state
			self.log_written = False
			
			# Hide the details scrolled for now
			self.objects.details_scrolled.hide()
			
			# Finally, show the scene!
			self.scene_container.set_visible_child(self.objects.install_scene)

	def on_generic_failure(self, handler, error, description):
		"""
		Fired when the APT Lock failed.
		"""
		
		dialog = Gtk.MessageDialog(
			self.objects.main.get_toplevel(),
			Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
			Gtk.MessageType.ERROR,
			Gtk.ButtonsType.CLOSE,
			error
		)
		dialog.set_title(_("Error"))
		dialog.format_secondary_text(description)
		
		dialog.run()
		dialog.destroy()

	def on_lock_failed(self, handler):
		"""
		Fired when the APT Lock failed.
		"""
		
		return self.on_generic_failure(
			handler,
			_("Unable to lock the APT database"),
			_("Please close the active package managers.")
		)
	
	@quickstart.threads.on_idle
	def on_update_found(self, handler, id, name, version, reason, status, size):
		"""
		Fired when an update has been found.
		"""
		
		self.update_list.add_item(id, name, version, reason, status, size)
	
	@quickstart.threads.on_idle
	def on_package_status_changed(self, handler, id, reason):
		"""
		Fired when a package status has been changed.
		"""
		
		print("STATUS!")
		self.update_list.update_status(id, reason)
	
	def on_package_fetch_started(self, handler, transaction_id, description, shortdesc):
		"""
		Fired when a package is being fetched.
		"""
		
		if not shortdesc in self.update_list.names_with_id:
			# wat?
			return

		# Get package id from the UpdateList			
		id = self.update_list.names_with_id[shortdesc]
		
		# Associate transaction with the id
		self.package_transactions[transaction_id] = id
		
		# Finally update the status on the list
		self.update_list.set_downloading(id, True)
	
	def on_package_fetch_failed(self, handler, transaction_id):
		"""
		Fired when a package failed to download.
		"""
		
		# FIXME: should notify the user!
		
		if not transaction_id in self.package_transactions:
			return
		
		self.update_list.set_downloading(self.package_transactions[transaction_id], False)

	def on_package_fetch_finished(self, handler, transaction_id):
		"""
		Fired when a package has been downloaded.
		"""
				
		if not transaction_id in self.package_transactions:
			return
		
		self.update_list.set_downloading(self.package_transactions[transaction_id], False)
	
	def on_downloading_changed(self, handler, value):
		"""
		Fired when handler's downloading property changed.
		"""
		
		if handler.props.downloading:
			self.update_list.enable_downloading_mode()
		else:
			self.update_list.disable_downloading_mode()
			self.package_transactions = {}
	
	def on_status_toggled(self, updatelist, id, reason):
		"""
		Fired when a package status changed locally.
		"""
		
		print(id, reason)
		
		self.handler.change_status(id, reason)
	
	def on_download_button_clicked(self, button):
		"""
		Fired when the download button has been clicked.
		"""
		
		if not self.handler.props.downloading:
			# Start fetching
			self.handler.fetch()
		else:
			# Stop fetching
			self.handler.fetch_stop()
	
	def on_install_button_clicked(self, button):
		"""
		Fired when the install button has been clicked.
		"""
		
		if not self.handler.props.downloading:
			# Start fetching
			self.handler.fetch(trigger_installation=True)
	
	def on_refresh_button_clicked(self, button):
		"""
		Fired when the refresh button has been clicked.
		"""
		
		# Clear list
		self.update_list.clear()
		
		self.handler.refresh()
	
	def on_checking_changed(self, handler, value):
		"""
		Fired when handler's checking property changed.
		"""
		
		print("Checking changed!")
		
		if not self.handler.props.checking:
			# Expand the update_list
			self.update_list.expand_all()
	
	def on_refreshing_changed(self, handler, value):
		"""
		Fired when handler's refreshing property changed.
		"""
		
		if not self.handler.props.refreshing:
			# Check for updates
			self.handler.check()
	
	def on_update_required_download_changed(self, handler, value):
		"""
		Fired when handler's update-required-download property changed.
		"""
		
		self.objects.download_size.show()
		self.objects.download_size.set_text(
			_("Total download size: %s") % self.handler.props.update_required_download
		)

	def on_selected_channel_changed(self, combobox):
		"""
		Fired when the selected channel has been changed.
		"""
		
		if self.building:
			return
		
		new_channel = self.objects.semplice_base_channels.get_value(combobox.get_active_iter(), 0)
		
		self.Channels.Enable("(s)", new_channel)
		
		# Store the new choice
		self.base_channel_enabled = new_channel
		
		# Check for features
		self.check_for_features()
		

	def on_enable_proposed_updates_toggled(self, checkbutton):
		"""
		Fired when the Enable proposed updates checkbutton has been toggled.
		"""
		
		if self.building:
			return
		
		(self.Channels.EnableComponent if checkbutton.get_active() else self.Channels.DisableComponent)("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT)
	
	def on_enable_development_updates_toggled(self, checkbutton):
		"""
		Fired when the Enable development updates checkbutton has been toggled.
		"""
		
		if self.building:
			return
		
		(self.Channels.Enable if checkbutton.get_active() else self.Channels.Disable)("(s)", DEVELOPMENT_CHANNEL)

	@quickstart.threads.on_idle
	def check_for_features(self):
		"""
		"Enable proposed updates" and "Enable development updates" are
		available only on semplice-current.
		
		This method ensures that those checkbuttons are not sensitive if
		the selected base channel is not semplice-current and loads their
		settings if it is.
		"""
		
		self.building = True
		
		if self.base_channel_enabled == CURRENT_CHANNEL:			
			self.objects.enable_development_updates.set_sensitive(True)
			if self.Channels.GetEnabled("(s)", DEVELOPMENT_CHANNEL):
				self.objects.enable_development_updates.set_active(True)
			else:
				self.objects.enable_development_updates.set_active(False)
		else:
			self.objects.enable_development_updates.set_sensitive(False)
			self.objects.enable_development_updates.set_active(False)

		# Check if the "proposed" channel is available
		if self.Channels.HasComponent("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT):
			self.objects.enable_proposed_updates.set_sensitive(True)
			
			if self.Channels.GetComponentEnabled("(ss)", self.base_channel_enabled, PROPOSED_COMPONENT):
				self.objects.enable_proposed_updates.set_active(True)
			else:
				self.objects.enable_proposed_updates.set_active(False)
		else:
			self.objects.enable_proposed_updates.set_sensitive(False)
			self.objects.enable_proposed_updates.set_active(False)
		
		self.building = False
		
	@quickstart.threads.on_idle
	def load(self):
		"""
		Reloads the semplice_base_channels ListStore.
		"""
		
		self.building = True
		
		self.objects.semplice_base_channels.clear()
		
		for channel in self.Providers.WhatProvides("(s)", BASE_PROVIDER):
			details = self.Channels.GetDetails("(sas)", channel, ["name"])
			itr = self.objects.semplice_base_channels.append(
				[
					channel,
					details["name"] if "name" in details else channel,
					False if self.current_variant == "current" and channel != CURRENT_CHANNEL else True
				]
			)
			
			if self.Channels.GetEnabled("(s)", channel):
				self.objects.selected_channel.set_active_iter(itr)
				self.base_channel_enabled = channel
		
		self.check_for_features()
		
		# self.building will be restored by check_for_features()

	def on_scene_called(self):
		"""
		Fired when the scene has been called.
		"""
		
		# Load current variant
		if os.path.exists(VARIANT_FILE):
			with open(VARIANT_FILE, "r") as f:
				self.current_variant = f.read().strip()

		# Enter in the bus
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.Channels = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/channels/channels",
			"org.semplicelinux.channels.channels",
			self.bus_cancellable
		)
		self.Providers = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/channels/providers",
			"org.semplicelinux.channels.providers",
			self.bus_cancellable
		)

		# We are locked
		self.unlockbar.emit("locked")
		
		self.load()
コード例 #5
0
class Scene(quickstart.scenes.BaseScene):
	""" Desktop preferences. """
	
	events = {
		"clicked": (
			"change_fullname_button",
			"change_password_button",
			"change_groups_button",
			"delete_user_button",
		),
		"response": (
			"delete_user_dialog",
			"groups_dialog",
		),
		"delete-event": (
			"delete_user_dialog",
			"groups_dialog",
		),
		"row_selected": ("user_list",),
	}
	
	on_edit_mode = False
	
	current_user = None
	current_user_properties = None
	current_user_box = None
	caller_user_box = None
	
	def handle_delete_events(self, window, event):
		"""
		Handles a dialog delete-event.
		"""
		
		return True
	
	on_delete_user_dialog_delete_event = on_groups_dialog_delete_event = handle_delete_events
	
	def on_locked(self, unlockbar):
		"""
		Fired when the Polkit auth has been revoked.
		"""
		
		# Switch again to the caller user
		self.objects.user_list.select_row(self.caller_user_box)
	
	def on_unlocked(self, unlockbar):
		"""
		Fired when the Polkit auth has been given.
		"""
		
		pass
	
	@quickstart.threads.on_idle
	def restore_edit_mode(self):
		"""
		Restores the full "edit" mode on the user details part.
		"""
		
		self.objects.fullname.show()
		self.objects.new_fullname.hide()
		
		fullname = self.current_user_properties.Get("(ss)",
			USER_IFACE,
			"Fullname"
		)
		self.objects.fullname.set_markup(fullname if fullname else NO_FULLNAME_STRING)
		
		self.objects.change_fullname_button.set_image(self.objects.edit_image)
		
		self.on_edit_mode = False
	
	def on_change_fullname_button_clicked(self, button):
		"""
		Fired when the 'Edit fullname' button has been clicked.
		"""
		
		if not self.on_edit_mode:
			self.objects.fullname.hide()
			self.objects.new_fullname.show()
			
			self.objects.new_fullname.set_text(
				self.objects.fullname.get_text() if self.objects.fullname.get_text() != NO_FULLNAME_STRING.replace("<i>","").replace("</i>","") else ""
			)
			self.objects.new_fullname.grab_focus()
			
			self.objects.change_fullname_button.set_image(self.objects.apply_image)
			
			self.on_edit_mode = True
		else:
			# Save changes
			self.current_user_properties.Set("(sss)",
				USER_IFACE,
				"Fullname",
				self.objects.new_fullname.get_text()
			)
			
			self.current_user_box.set_username_and_fullname(
				self.current_user_box.user.get_text(),
				self.objects.new_fullname.get_text()
			)
			
			self.restore_edit_mode()
	
	def on_change_password_button_clicked(self, button):
		"""
		Fired when the 'Click to change' button on the password field
		has been clicked.
		"""
		
		self.current_user.ChangePassword('(s)', os.environ["DISPLAY"])
	
	def on_delete_user_button_clicked(self, button):
		"""
		Fired when the delete user button has been clicked.
		"""
		
		self.objects.delete_user_dialog.show()
	
	def on_delete_user_dialog_response(self, dialog, response_id):
		"""
		Fired when the delete user dialog got a response.
		"""
		
		if response_id == Gtk.ResponseType.OK:
			# Delete user
			# FIXME: error checking?
			self.current_user.DeleteUser("(b)", self.objects.delete_user_with_home.get_active())
		
		# Reset delete_user_with_home
		self.objects.delete_user_with_home.set_active(False)
		dialog.hide()
		return False
	
	def on_change_groups_button_clicked(self, dialog):
		"""
		Fired when the change groups button has been clicked.
		"""
		
		self.objects.groups_store.clear()
		
		# Build group list
		user_in = self.GroupConfig.GetGroupsForUser('(s)', self.current_user_box._user)
		for group, name in self.GroupConfig.GetGroups().items():
			name = name[0]
			if name in ("sudo",):
				# Skip
				continue
			self.objects.groups_store.insert_with_valuesv(-1, [0, 1, 2], [group, (name in user_in), name])
		
		self.objects.groups_dialog.show()
	
	def on_groups_dialog_response(self, dialog, response_id):
		"""
		Fired when the groups dialog got a response.
		"""
		
		dialog.hide()
		return False
	
	def on_group_enabled_toggle_toggled(self, toggle, path):
		"""
		Fired when a toggle in the groups dialog has been... toggled.
		"""
		
		itr = self.objects.groups_store.get_iter(path)
		
		# Connect to group object
		group_properties = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/group/%s" % self.objects.groups_store.get_value(itr, 0),
			"org.freedesktop.DBus.Properties",
			self.bus_cancellable
		)
		
		# Get current group members
		members = group_properties.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members')
		
		if not toggle.get_active():
			# Add user
			if not self.current_user_box._user in members: members.append(self.current_user_box._user)
		else:
			# Remove user
			if self.current_user_box._user in members: members.remove(self.current_user_box._user)
		
		# Set the new list
		try:
			group_properties.Set('(ssas)', 'org.semplicelinux.usersd.group', 'Members', members)
		except:
			# Failed/Aborted
			return
		
		# Finally set the enabled boolean in the row
		self.objects.groups_store.set_value(itr, 1, not toggle.get_active())
			
	@quickstart.threads.on_idle
	def build_user_list(self):
		"""
		Builds the user list.
		"""
		
		# Clear
		self.objects.user_list.foreach(lambda x: x.destroy())
		
		# "Add new"
		self.objects.user_list.add(AddNewBox())
		
		try:
			for uid, details in self.UsersConfig.GetUsers().items():
				if uid < 1000 or uid == 65534:
					# Skip system groups or nobody
					continue
				
				box = Gtk.ListBoxRow()
				box.add(UserBox(uid, details[0], details[1], details[2]))
				box.show()
				self.objects.user_list.add(box)
				
				# Current user?
				if uid == CURRENT_UID:
					# Yes!
					self.caller_user_box = box
					self.objects.user_list.select_row(self.caller_user_box)
		except:
			# Unable to build user list, probably caused by a non-working DBus connection
			self.objects.main.set_sensitive(False)
			self.objects.content.hide()
			self.objects.unable_to_connect_warning.show()
			
	def on_user_list_row_selected(self, listbox, row):
		"""
		Fired when a user has been selected.
		"""
		
		if row == None: return
		
		userbox = row.get_child()
		
		if userbox.add_new:
			return self.UsersConfig.ShowUserCreationUI('(sas)', os.environ["DISPLAY"], DEFAULT_GROUPS)
		
		self.current_user_box = userbox
		
		# Caller users cannot remove themselves
		if row == self.caller_user_box:
			self.objects.delete_user_button.hide()
		else:
			self.objects.delete_user_button.show()
		
		# Obtain DBus object for the current user
		self.current_user = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/user/%s" % userbox.uid,
			USER_IFACE,
			self.bus_cancellable
		)
		self.current_user_properties = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd/user/%s" % userbox.uid,
			"org.freedesktop.DBus.Properties",
			self.bus_cancellable
		)
		
		self.objects.fullname.set_markup(userbox.fullname.get_text() if userbox.fullname.get_text() else NO_FULLNAME_STRING)
		self.objects.user.set_text(userbox.user.get_text())
		
		# Administrator switch
		if self.SudoGroup:
			self.objects.administrator.set_active(
				(userbox.user.get_text() in self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members'))
			)
	
	def determine_row_sorting(self, row1, row2):
		"""
		Used to sort the user list by UIDs.
		"""
		
		child1 = row1.get_child()
		child2 = row2.get_child()
		
		if child2.add_new:
			return 1
		elif child1.add_new:
			return 0
		elif child1.uid > child2.uid:
			return 1
		else:
			return 0
	
	def prepare_scene(self):
		"""
		Fired when the module has just been loaded and we should setup
		things.
		"""
		
		self.scene_container = self.objects.main
		
		# g-signals
		self.signal_handlers = {
			"UserListChanged": self.build_user_list
		}

		# Create unlockbar
		self.unlockbar = UnlockBar("org.semplicelinux.usersd.manage")
		self.unlockbar.connect("locked", self.on_locked)
		self.unlockbar.connect("unlocked", self.on_unlocked)
		self.objects.main.pack_start(self.unlockbar, False, False, 0)
		
		# Bind the locked state to the sensitiveness of the use_scrolled ScrolledWindow
		self.unlockbar.bind_property(
			"lock",
			self.objects.user_scrolled,
			"sensitive",
			GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN
		)
		
		# ListBox sort
		self.objects.user_list.set_sort_func(self.determine_row_sorting)
		
		# Delete user dialog
		self.objects.delete_user_dialog.bind_property(
			"visible",
			self.objects.main,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN
		)
		self.objects.delete_user_dialog.add_buttons(
			_("_Cancel"),
			Gtk.ResponseType.CANCEL,
			_("_Delete user"),
			Gtk.ResponseType.OK
		)
		self.objects.delete_user_dialog.get_widget_for_response(Gtk.ResponseType.OK).get_style_context().add_class("destructive-action")
		
		# Groups dialog
		self.objects.groups_dialog.bind_property(
			"visible",
			self.objects.main,
			"sensitive",
			GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN
		)
		self.objects.groups_dialog.add_buttons(
			_("_Close"),
			Gtk.ResponseType.CLOSE
		)

		self.group_enabled_toggle = Gtk.CellRendererToggle()
		self.objects.groups_treeview.append_column(
			Gtk.TreeViewColumn(
				"Enabled",
				self.group_enabled_toggle,
				active=1
			)
		)
		self.group_enabled_toggle.connect("toggled", self.on_group_enabled_toggle_toggled)
		self.objects.groups_treeview.append_column(
			Gtk.TreeViewColumn(
				"Group description",
				Gtk.CellRendererText(),
				text=2
			)
		)
		self.objects.groups_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
		
		self.objects.administrator.connect("notify::active", self.on_administrator_changed)
	
	def on_administrator_changed(self, widget, param):
		"""
		Fired when the user wants to change the sudo membership of the selected user.
		"""
		
		members = self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group', 'Members')
		
		changed = False
		if widget.get_active():
			# Add
			if not self.current_user_box._user in members:
				members.append(self.current_user_box._user)
				changed = True
		else:
			# Remove
			if self.current_user_box._user in members:
				members.remove(self.current_user_box._user)
				changed = True
		
		if changed:
			# Commit changes
			self.SudoGroup.Set('(ssas)', 'org.semplicelinux.usersd.group', 'Members', members)		

	def on_scene_called(self):
		"""
		Fired when the user wants to see this scene.
		"""
		
		# Recover from a failed connection state
		self.objects.main.set_sensitive(True)
		self.objects.content.show()
		self.objects.unable_to_connect_warning.hide()
		
		# We are locked!
		self.unlockbar.emit("locked")
		
		# Estabilish DBus connection
		self.bus_cancellable = Gio.Cancellable()
		self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
		self.UsersConfig = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd",
			"org.semplicelinux.usersd.user",
			self.bus_cancellable
		)
		self.UsersConfig.connect(
			"g-signal",
			lambda proxy, sender, signal, params: self.signal_handlers[signal]() if signal in self.signal_handlers else None
		)
		self.GroupConfig = Gio.DBusProxy.new_sync(
			self.bus,
			0,
			None,
			BUS_NAME,
			"/org/semplicelinux/usersd",
			"org.semplicelinux.usersd.group",
			self.bus_cancellable
		)
		
		# Estabilish DBus connection to the "sudo" group
		try:
			self.SudoGroup = Gio.DBusProxy.new_sync(
				self.bus,
				0,
				None,
				BUS_NAME,
				self.GroupConfig.LookupGroup("(s)", "sudo"),
				"org.freedesktop.DBus.Properties",
				self.bus_cancellable
			)
		except:
			self.SudoGroup = None
		
		if not self.SudoGroup:
			self.objects.administrator.set_sensitive(False)
		else:
			self.objects.administrator.set_sensitive(True)
		
		self.build_user_list()			
コード例 #6
0
class Scene(quickstart.scenes.BaseScene):
    """ Desktop preferences. """

    events = {
        "clicked": (
            "change_fullname_button",
            "change_password_button",
            "change_groups_button",
            "delete_user_button",
        ),
        "response": (
            "delete_user_dialog",
            "groups_dialog",
        ),
        "delete-event": (
            "delete_user_dialog",
            "groups_dialog",
        ),
        "row_selected": ("user_list", ),
    }

    on_edit_mode = False

    current_user = None
    current_user_properties = None
    current_user_box = None
    caller_user_box = None

    def handle_delete_events(self, window, event):
        """
		Handles a dialog delete-event.
		"""

        return True

    on_delete_user_dialog_delete_event = on_groups_dialog_delete_event = handle_delete_events

    def on_locked(self, unlockbar):
        """
		Fired when the Polkit auth has been revoked.
		"""

        # Switch again to the caller user
        self.objects.user_list.select_row(self.caller_user_box)

    def on_unlocked(self, unlockbar):
        """
		Fired when the Polkit auth has been given.
		"""

        pass

    @quickstart.threads.on_idle
    def restore_edit_mode(self):
        """
		Restores the full "edit" mode on the user details part.
		"""

        self.objects.fullname.show()
        self.objects.new_fullname.hide()

        fullname = self.current_user_properties.Get("(ss)", USER_IFACE,
                                                    "Fullname")
        self.objects.fullname.set_markup(
            fullname if fullname else NO_FULLNAME_STRING)

        self.objects.change_fullname_button.set_image(self.objects.edit_image)

        self.on_edit_mode = False

    def on_change_fullname_button_clicked(self, button):
        """
		Fired when the 'Edit fullname' button has been clicked.
		"""

        if not self.on_edit_mode:
            self.objects.fullname.hide()
            self.objects.new_fullname.show()

            self.objects.new_fullname.set_text(
                self.objects.fullname.get_text(
                ) if self.objects.fullname.get_text() != NO_FULLNAME_STRING.
                replace("<i>", "").replace("</i>", "") else "")
            self.objects.new_fullname.grab_focus()

            self.objects.change_fullname_button.set_image(
                self.objects.apply_image)

            self.on_edit_mode = True
        else:
            # Save changes
            self.current_user_properties.Set(
                "(sss)", USER_IFACE, "Fullname",
                self.objects.new_fullname.get_text())

            self.current_user_box.set_username_and_fullname(
                self.current_user_box.user.get_text(),
                self.objects.new_fullname.get_text())

            self.restore_edit_mode()

    def on_change_password_button_clicked(self, button):
        """
		Fired when the 'Click to change' button on the password field
		has been clicked.
		"""

        self.current_user.ChangePassword('(s)', os.environ["DISPLAY"])

    def on_delete_user_button_clicked(self, button):
        """
		Fired when the delete user button has been clicked.
		"""

        self.objects.delete_user_dialog.show()

    def on_delete_user_dialog_response(self, dialog, response_id):
        """
		Fired when the delete user dialog got a response.
		"""

        if response_id == Gtk.ResponseType.OK:
            # Delete user
            # FIXME: error checking?
            self.current_user.DeleteUser(
                "(b)", self.objects.delete_user_with_home.get_active())

        # Reset delete_user_with_home
        self.objects.delete_user_with_home.set_active(False)
        dialog.hide()
        return False

    def on_change_groups_button_clicked(self, dialog):
        """
		Fired when the change groups button has been clicked.
		"""

        self.objects.groups_store.clear()

        # Build group list
        user_in = self.GroupConfig.GetGroupsForUser(
            '(s)', self.current_user_box._user)
        for group, name in self.GroupConfig.GetGroups().items():
            name = name[0]
            if name in ("sudo", ):
                # Skip
                continue
            self.objects.groups_store.insert_with_valuesv(
                -1, [0, 1, 2], [group, (name in user_in), name])

        self.objects.groups_dialog.show()

    def on_groups_dialog_response(self, dialog, response_id):
        """
		Fired when the groups dialog got a response.
		"""

        dialog.hide()
        return False

    def on_group_enabled_toggle_toggled(self, toggle, path):
        """
		Fired when a toggle in the groups dialog has been... toggled.
		"""

        itr = self.objects.groups_store.get_iter(path)

        # Connect to group object
        group_properties = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd/group/%s" %
            self.objects.groups_store.get_value(itr, 0),
            "org.freedesktop.DBus.Properties", self.bus_cancellable)

        # Get current group members
        members = group_properties.Get('(ss)',
                                       'org.semplicelinux.usersd.group',
                                       'Members')

        if not toggle.get_active():
            # Add user
            if not self.current_user_box._user in members:
                members.append(self.current_user_box._user)
        else:
            # Remove user
            if self.current_user_box._user in members:
                members.remove(self.current_user_box._user)

        # Set the new list
        try:
            group_properties.Set('(ssas)', 'org.semplicelinux.usersd.group',
                                 'Members', members)
        except:
            # Failed/Aborted
            return

        # Finally set the enabled boolean in the row
        self.objects.groups_store.set_value(itr, 1, not toggle.get_active())

    @quickstart.threads.on_idle
    def build_user_list(self):
        """
		Builds the user list.
		"""

        # Clear
        self.objects.user_list.foreach(lambda x: x.destroy())

        # "Add new"
        self.objects.user_list.add(AddNewBox())

        try:
            for uid, details in self.UsersConfig.GetUsers().items():
                if uid < 1000 or uid == 65534:
                    # Skip system groups or nobody
                    continue

                box = Gtk.ListBoxRow()
                box.add(UserBox(uid, details[0], details[1], details[2]))
                box.show()
                self.objects.user_list.add(box)

                # Current user?
                if uid == CURRENT_UID:
                    # Yes!
                    self.caller_user_box = box
                    self.objects.user_list.select_row(self.caller_user_box)
        except:
            # Unable to build user list, probably caused by a non-working DBus connection
            self.objects.main.set_sensitive(False)
            self.objects.content.hide()
            self.objects.unable_to_connect_warning.show()

    def on_user_list_row_selected(self, listbox, row):
        """
		Fired when a user has been selected.
		"""

        if row == None: return

        userbox = row.get_child()

        if userbox.add_new:
            return self.UsersConfig.ShowUserCreationUI('(sas)',
                                                       os.environ["DISPLAY"],
                                                       DEFAULT_GROUPS)

        self.current_user_box = userbox

        # Caller users cannot remove themselves
        if row == self.caller_user_box:
            self.objects.delete_user_button.hide()
        else:
            self.objects.delete_user_button.show()

        # Obtain DBus object for the current user
        self.current_user = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/usersd/user/%s" % userbox.uid, USER_IFACE,
            self.bus_cancellable)
        self.current_user_properties = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME,
            "/org/semplicelinux/usersd/user/%s" % userbox.uid,
            "org.freedesktop.DBus.Properties", self.bus_cancellable)

        self.objects.fullname.set_markup(userbox.fullname.get_text(
        ) if userbox.fullname.get_text() else NO_FULLNAME_STRING)
        self.objects.user.set_text(userbox.user.get_text())

        # Administrator switch
        if self.SudoGroup:
            self.objects.administrator.set_active(
                (userbox.user.get_text()
                 in self.SudoGroup.Get('(ss)',
                                       'org.semplicelinux.usersd.group',
                                       'Members')))

    def determine_row_sorting(self, row1, row2):
        """
		Used to sort the user list by UIDs.
		"""

        child1 = row1.get_child()
        child2 = row2.get_child()

        if child2.add_new:
            return 1
        elif child1.add_new:
            return 0
        elif child1.uid > child2.uid:
            return 1
        else:
            return 0

    def prepare_scene(self):
        """
		Fired when the module has just been loaded and we should setup
		things.
		"""

        self.scene_container = self.objects.main

        # g-signals
        self.signal_handlers = {"UserListChanged": self.build_user_list}

        # Create unlockbar
        self.unlockbar = UnlockBar("org.semplicelinux.usersd.manage")
        self.unlockbar.connect("locked", self.on_locked)
        self.unlockbar.connect("unlocked", self.on_unlocked)
        self.objects.main.pack_start(self.unlockbar, False, False, 0)

        # Bind the locked state to the sensitiveness of the use_scrolled ScrolledWindow
        self.unlockbar.bind_property(
            "lock", self.objects.user_scrolled, "sensitive",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.INVERT_BOOLEAN)

        # ListBox sort
        self.objects.user_list.set_sort_func(self.determine_row_sorting)

        # Delete user dialog
        self.objects.delete_user_dialog.bind_property(
            "visible", self.objects.main, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN)
        self.objects.delete_user_dialog.add_buttons(_("_Cancel"),
                                                    Gtk.ResponseType.CANCEL,
                                                    _("_Delete user"),
                                                    Gtk.ResponseType.OK)
        self.objects.delete_user_dialog.get_widget_for_response(
            Gtk.ResponseType.OK).get_style_context().add_class(
                "destructive-action")

        # Groups dialog
        self.objects.groups_dialog.bind_property(
            "visible", self.objects.main, "sensitive",
            GObject.BindingFlags.DEFAULT | GObject.BindingFlags.INVERT_BOOLEAN)
        self.objects.groups_dialog.add_buttons(_("_Close"),
                                               Gtk.ResponseType.CLOSE)

        self.group_enabled_toggle = Gtk.CellRendererToggle()
        self.objects.groups_treeview.append_column(
            Gtk.TreeViewColumn("Enabled", self.group_enabled_toggle, active=1))
        self.group_enabled_toggle.connect("toggled",
                                          self.on_group_enabled_toggle_toggled)
        self.objects.groups_treeview.append_column(
            Gtk.TreeViewColumn("Group description",
                               Gtk.CellRendererText(),
                               text=2))
        self.objects.groups_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)

        self.objects.administrator.connect("notify::active",
                                           self.on_administrator_changed)

    def on_administrator_changed(self, widget, param):
        """
		Fired when the user wants to change the sudo membership of the selected user.
		"""

        members = self.SudoGroup.Get('(ss)', 'org.semplicelinux.usersd.group',
                                     'Members')

        changed = False
        if widget.get_active():
            # Add
            if not self.current_user_box._user in members:
                members.append(self.current_user_box._user)
                changed = True
        else:
            # Remove
            if self.current_user_box._user in members:
                members.remove(self.current_user_box._user)
                changed = True

        if changed:
            # Commit changes
            self.SudoGroup.Set('(ssas)', 'org.semplicelinux.usersd.group',
                               'Members', members)

    def on_scene_called(self):
        """
		Fired when the user wants to see this scene.
		"""

        # Recover from a failed connection state
        self.objects.main.set_sensitive(True)
        self.objects.content.show()
        self.objects.unable_to_connect_warning.hide()

        # We are locked!
        self.unlockbar.emit("locked")

        # Estabilish DBus connection
        self.bus_cancellable = Gio.Cancellable()
        self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, self.bus_cancellable)
        self.UsersConfig = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd",
            "org.semplicelinux.usersd.user", self.bus_cancellable)
        self.UsersConfig.connect(
            "g-signal",
            lambda proxy, sender, signal, params: self.signal_handlers[signal]
            () if signal in self.signal_handlers else None)
        self.GroupConfig = Gio.DBusProxy.new_sync(
            self.bus, 0, None, BUS_NAME, "/org/semplicelinux/usersd",
            "org.semplicelinux.usersd.group", self.bus_cancellable)

        # Estabilish DBus connection to the "sudo" group
        try:
            self.SudoGroup = Gio.DBusProxy.new_sync(
                self.bus, 0, None, BUS_NAME,
                self.GroupConfig.LookupGroup("(s)", "sudo"),
                "org.freedesktop.DBus.Properties", self.bus_cancellable)
        except:
            self.SudoGroup = None

        if not self.SudoGroup:
            self.objects.administrator.set_sensitive(False)
        else:
            self.objects.administrator.set_sensitive(True)

        self.build_user_list()