def do_style_overrides(self): """ Here we try to check for theme support in the current system's gtk theme. We do this by retrieving a string of the current theme's final style sheet, then searching for the .csstage style class. If it's found, we return, otherwise we add our own application-priority provider as a fallback. While we have the theme string, we check for a variable name we can use for the fallback experience, and adjust it in our application stylesheet if necessary before adding it as a provider. """ theme_name = Gtk.Settings.get_default().get_property("gtk-theme-name") provider = Gtk.CssProvider.get_named(theme_name) css = provider.to_string() if ".csstage" not in css: print( "Cinnamon Screensaver support not found in current theme - adding some..." ) if utils.have_gtk_version("3.20.0"): path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.20.css") elif utils.have_gtk_version("3.18.0"): path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.18.css") else: path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.14.css") f = open(path, 'r') fallback_css = f.read() f.close() if "@define-color theme_selected_bg_color" in css: pass elif "@define-color selected_bg_color" in css: print( "replacing theme_selected_bg_color with selected_bg_color") fallback_css = fallback_css.replace("@theme_selected_bg_color", "@selected_bg_color") else: print("replacing theme_selected_bg_color with Adwaita blue") fallback_css = fallback_css.replace("@selected_bg_color", "#4a90d9") fallback_prov = Gtk.CssProvider() if fallback_prov.load_from_data(fallback_css.encode()): Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), fallback_prov, 600) Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
def update_geometry(self): """ Override BaseWindow.update_geometry() - the Stage should always be the GdkScreen size, unless status.InteractiveDebug is True """ if status.InteractiveDebug: # Gdk 3.22 introduces GdkMonitor objects, and GdkScreen-reported # monitor info is no-longer reliable if utils.have_gtk_version("3.22.0"): monitor = Gdk.Display.get_default().get_primary_monitor() self.rect = monitor.get_geometry() else: monitor_n = self.screen.get_primary_monitor() self.rect = self.screen.get_monitor_geometry(monitor_n) else: self.rect = Gdk.Rectangle() self.rect.x = 0 self.rect.y = 0 self.rect.width = self.screen.get_width() self.rect.height = self.screen.get_height() hints = Gdk.Geometry() hints.min_width = self.rect.width hints.min_height = self.rect.height hints.max_width = self.rect.width hints.max_height = self.rect.height hints.base_width = self.rect.width hints.base_height = self.rect.height self.set_geometry_hints(self, hints, Gdk.WindowHints.MIN_SIZE | Gdk.WindowHints.MAX_SIZE | Gdk.WindowHints.BASE_SIZE)
def get_theme_height(self): ctx = self.get_style_context() if utils.have_gtk_version("3.20.0"): return ctx.get_property("min-height", Gtk.StateFlags.NORMAL) else: color = ctx.get_color(Gtk.StateFlags.NORMAL) return (color.red * 255) + (color.green * 255) + (color.blue * 255)
def do_style_overrides(self): """ Here we try to check for theme support in the current system's gtk theme. We do this by retrieving a string of the current theme's final style sheet, then searching for the .csstage style class. If it's found, we return, otherwise we add our own application-priority provider as a fallback. While we have the theme string, we check for a variable name we can use for the fallback experience, and adjust it in our application stylesheet if necessary before adding it as a provider. """ theme_name = Gtk.Settings.get_default().get_property("gtk-theme-name") provider = Gtk.CssProvider.get_named(theme_name) css = provider.to_string() if ".csstage" not in css: print("Cinnamon Screensaver support not found in current theme - adding some...") if utils.have_gtk_version("3.20.0"): path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.20.css") elif utils.have_gtk_version("3.18.0"): path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.18.css") else: path = os.path.join(config.pkgdatadir, "cinnamon-screensaver-gtk3.14.css") f = open(path, "r") fallback_css = f.read() f.close() if "@define-color theme_selected_bg_color" in css: pass elif "@define-color selected_bg_color" in css: print("replacing theme_selected_bg_color with selected_bg_color") fallback_css = fallback_css.replace("@theme_selected_bg_color", "@selected_bg_color") else: print("replacing theme_selected_bg_color with Adwaita blue") fallback_css = fallback_css.replace("@selected_bg_color", "#4a90d9") fallback_prov = Gtk.CssProvider() if fallback_prov.load_from_data(fallback_css.encode()): Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), fallback_prov, 600) Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
def transition_out(self, effect_time, callback): """ This is the primary way of destroying the stage. This can end up being called multiple times, so we keep track of if we've already started a transition, and ignore further calls. """ if self.destroying: return self.destroying = True self.fader.cancel() if utils.have_gtk_version("3.18.0"): self.fader.fade_out(effect_time, callback) else: self.hide() callback()
def on_state_changed(self, controller=None, state=0): if controller and controller != self.controller: old = self.controller self.controller = controller del old if self.controller.get_state() == Cvc.MixerControlState.READY: new = self.controller.get_default_sink() if new is not None: if self.output and self.output != new: old = self.output self.output = new del old else: self.output = new trackers.con_tracker_get().connect(self.output, "notify::is-muted", self.on_volume_changed) trackers.con_tracker_get().connect(self.output, "notify::volume", self.on_volume_changed) trackers.con_tracker_get().connect(self.volume_slider, "value-changed", self.on_volume_slider_changed) trackers.con_tracker_get().connect(self.volume_slider, "button-press-event", self.on_button_press_event) if not utils.have_gtk_version("3.18.0"): trackers.con_tracker_get().connect(self.volume_slider, "scroll-event", self.on_scroll_event) self.on_volume_changed(None, None) self.show() return self.hide()
def __init__(self): super(VolumeControl, self).__init__(orientation=Gtk.Orientation.HORIZONTAL) self.output = None self.controller = None self.volume_slider = VolumeSlider() trackers.con_tracker_get().connect(self.volume_slider, "value-changed", self.on_volume_slider_changed) trackers.con_tracker_get().connect(self.volume_slider, "button-press-event", self.on_button_press_event) if not utils.have_gtk_version("3.18.0"): trackers.con_tracker_get().connect(self.volume_slider, "scroll-event", self.on_scroll_event) self.pack_start(self.volume_slider, False, False, 6) self.initialize_sound_controller()
def on_state_changed(self, controller=None, state=0): if controller and controller != self.controller: old = self.controller self.controller = controller del old if self.controller.get_state() == Cvc.MixerControlState.READY: new = self.controller.get_default_sink() if new is not None: if self.output and self.output != new: old = self.output self.output = new del old else: self.output = new trackers.con_tracker_get().connect(self.output, "notify::is-muted", self.on_volume_changed) trackers.con_tracker_get().connect(self.output, "notify::volume", self.on_volume_changed) trackers.con_tracker_get().connect( self.volume_slider, "value-changed", self.on_volume_slider_changed) trackers.con_tracker_get().connect(self.volume_slider, "button-press-event", self.on_button_press_event) if not utils.have_gtk_version("3.18.0"): trackers.con_tracker_get().connect(self.volume_slider, "scroll-event", self.on_scroll_event) self.on_volume_changed(None, None) self.show() return self.hide()
def position_overlay_child(self, overlay, child, allocation): """ Callback for our GtkOverlay, think of this as a mini- window manager for our Stage. Depending on what type child is, we position it differently. We always call child.get_preferred_size() whether we plan to use it or not - this prevents allocation warning spew, particularly in Gtk >= 3.20. Returning True says, yes draw it. Returning False tells it to skip drawing. If a new widget type is introduced that spawns directly on the stage, it must have its own handling code here. """ if isinstance(child, MonitorView): """ MonitorView is always the size and position of its assigned monitor. This is calculated and stored by the child in child.rect) """ w, h = child.get_preferred_size() allocation.x = child.rect.x allocation.y = child.rect.y allocation.width = child.rect.width allocation.height = child.rect.height return True if isinstance(child, UnlockDialog): """ UnlockDialog always shows on the currently focused monitor (the one the mouse is currently in), and is kept centered. """ monitor = utils.get_mouse_monitor() monitor_rect = self.screen.get_monitor_geometry(monitor) min_rect, nat_rect = child.get_preferred_size() allocation.width = nat_rect.width allocation.height = nat_rect.height allocation.x = monitor_rect.x + (monitor_rect.width / 2) - (nat_rect.width / 2) allocation.y = monitor_rect.y + (monitor_rect.height / 2) - (nat_rect.height / 2) return True if isinstance(child, ClockWidget) or isinstance(child, AlbumArt): """ ClockWidget and AlbumArt behave differently depending on if status.Awake is True or not. The widgets' halign and valign properties are used to store their gross position on the monitor. This limits the number of possible positions to (3 * 3 * n_monitors) when our screensaver is not Awake, and the widgets have an internal timer that randomizes halign, valign, and current monitor every so many seconds, calling a queue_resize on itself after each timer tick (which forces this function to run). """ min_rect, nat_rect = child.get_preferred_size() current_monitor = child.current_monitor if status.Awake: """ If we're Awake, force the clock to track to the active monitor, and be aligned to the left-center. The albumart widget aligns right-center. """ if isinstance(child, ClockWidget): child.set_halign(Gtk.Align.START) else: child.set_halign(Gtk.Align.END) child.set_valign(Gtk.Align.CENTER) current_monitor = utils.get_mouse_monitor() else: for floater in self.floaters: """ Don't let our floating widgets end up in the same spot. """ if floater is child: continue if floater.get_halign() != child.get_halign( ) and floater.get_valign() != child.get_valign(): continue fa = floater.get_halign() ca = child.get_halign() while fa == ca: ca = ALIGNMENTS[random.randint(0, 2)] child.set_halign(ca) fa = floater.get_valign() ca = child.get_valign() while fa == ca: ca = ALIGNMENTS[random.randint(0, 2)] child.set_valign(ca) monitor_rect = self.screen.get_monitor_geometry(current_monitor) allocation.width = nat_rect.width allocation.height = nat_rect.height halign = child.get_halign() valign = child.get_valign() if halign == Gtk.Align.START: allocation.x = monitor_rect.x elif halign == Gtk.Align.CENTER: allocation.x = monitor_rect.x + (monitor_rect.width / 2) - (nat_rect.width / 2) elif halign == Gtk.Align.END: allocation.x = monitor_rect.x + monitor_rect.width - nat_rect.width if valign == Gtk.Align.START: allocation.y = monitor_rect.y elif valign == Gtk.Align.CENTER: allocation.y = monitor_rect.y + (monitor_rect.height / 2) - (nat_rect.height / 2) elif valign == Gtk.Align.END: allocation.y = monitor_rect.y + monitor_rect.height - nat_rect.height # Earlier gtk versions don't appear to include css padding in their preferred-size calculation # This is true at least in 3.14 (Betsy/Jessir - is 3.16 relevant anywhere?) if not utils.have_gtk_version("3.18.0"): padding = child.get_style_context().get_padding( Gtk.StateFlags.NORMAL) if halign == Gtk.Align.START: allocation.x += padding.left elif halign == Gtk.Align.END: allocation.x -= padding.right if valign == Gtk.Align.START: allocation.y += padding.top elif valign == Gtk.Align.END: allocation.y -= padding.bottom return True if isinstance(child, AudioPanel): """ The AudioPanel is only shown when Awake, and attaches itself to the upper-left corner of the active monitor. """ min_rect, nat_rect = child.get_preferred_size() if status.Awake: current_monitor = utils.get_mouse_monitor() monitor_rect = self.screen.get_monitor_geometry( current_monitor) allocation.x = monitor_rect.x allocation.y = monitor_rect.y allocation.width = nat_rect.width allocation.height = nat_rect.height else: allocation.x = child.rect.x allocation.y = child.rect.y allocation.width = nat_rect.width allocation.height = nat_rect.height return True if isinstance(child, InfoPanel): """ The InfoPanel can be shown while not Awake, but only if we're not running a screensaver plugin. In any case, it will only appear if a) We have received notifications while the screensaver is running, or b) we're either on battery or plugged in but with a non-full battery. It attaches itself to the upper-right corner of the monitor. """ min_rect, nat_rect = child.get_preferred_size() if status.Awake: current_monitor = utils.get_mouse_monitor() monitor_rect = self.screen.get_monitor_geometry( current_monitor) allocation.x = monitor_rect.x + monitor_rect.width - nat_rect.width allocation.y = monitor_rect.y allocation.width = nat_rect.width allocation.height = nat_rect.height else: allocation.x = child.rect.x + child.rect.width - nat_rect.width allocation.y = child.rect.y allocation.width = nat_rect.width allocation.height = nat_rect.height return True return False