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()
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
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
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()
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()
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()