1#!/usr/local/bin/python3.8
2
3import gi
4gi.require_version('UPowerGlib', '1.0')
5from gi.repository import UPowerGlib
6
7from SettingsWidgets import SidePage
8from xapp.GSettingsWidgets import *
9
10POWER_BUTTON_OPTIONS = [
11    ("blank", _("Lock the screen")),
12    ("suspend", _("Suspend")),
13    ("shutdown", _("Shut down immediately")),
14    ("hibernate", _("Hibernate")),
15    ("interactive", _("Ask what to do")),
16    ("nothing", _("Do nothing"))
17]
18
19IDLE_BRIGHTNESS_OPTIONS = [
20    (5, _("5%")),
21    (10, _("10%")),
22    (30, _("30%")),
23    (50, _("50%")),
24    (75, _("75%"))
25]
26
27IDLE_DELAY_OPTIONS = [
28    (30, _("30 seconds")),
29    (60, _("60 seconds")),
30    (90, _("90 seconds")),
31    (120, _("2 minutes")),
32    (300, _("5 minutes")),
33    (600, _("10 minutes"))
34]
35
36SLEEP_DELAY_OPTIONS = [
37    (300, _("5 minutes")),
38    (600, _("10 minutes")),
39    (900, _("15 minutes")),
40    (1800, _("30 minutes")),
41    (2700, _("45 minutes")),
42    (3600, _("1 hour")),
43    (7200, _("2 hours")),
44    (10800, _("3 hours")),
45    (0, _("Never"))
46]
47
48(UP_ID, UP_VENDOR, UP_MODEL, UP_TYPE, UP_ICON, UP_PERCENTAGE, UP_STATE, UP_BATTERY_LEVEL, UP_SECONDS) = range(9)
49
50try:
51    UPowerGlib.DeviceLevel
52except AttributeError:
53    class DeviceLevel:
54        UNKNOWN = 1
55        NONE = 1
56        LOW = 3
57        CRITICAL = 4
58        NORMAL = 6
59        HIGH = 7
60        FULL = 9
61    UPowerGlib.DeviceLevel = DeviceLevel
62
63
64def get_timestring(time_seconds):
65    minutes = int((time_seconds / 60.0) + 0.5)
66
67    if minutes == 0:
68        time_string = _("Unknown time")
69        return time_string
70
71    if minutes < 60:
72        if minutes == 1:
73            time_string = ("%d " % minutes) + _("minute")
74        else:
75            time_string = ("%d " % minutes) + _("minutes")
76        return time_string
77
78    hours = minutes / 60
79    minutes = minutes % 60
80
81    if minutes == 0:
82        if hours == 1:
83            time_string = ("%d " % hours) + _("hour")
84            return time_string
85        else:
86            time_string = ("%d " % hours) + _("hours")
87            return time_string
88
89    if hours == 1:
90        if minutes == 1:
91            time_string = ("%d " % hours + _("hour")) + (" %d " % minutes + _("minute"))
92            return time_string
93        else:
94            time_string = ("%d " % hours + _("hour")) + (" %d " % minutes + _("minutes"))
95            return time_string
96
97    time_string = ("%d " % hours + _("hours")) + (" %d " % minutes + _("minutes"))
98    return time_string
99
100
101CSD_SCHEMA = "org.cinnamon.settings-daemon.plugins.power"
102CSM_SCHEMA = "org.cinnamon.SessionManager"
103
104class Module:
105    name = "power"
106    category = "hardware"
107    comment = _("Manage power settings")
108
109    def __init__(self, content_box):
110        keywords = _("power, suspend, hibernate, laptop, desktop, brightness, screensaver")
111        self.sidePage = SidePage(_("Power Management"), "cs-power", keywords, content_box, -1, module=self)
112
113    def on_module_selected(self):
114        if self.loaded:
115            # self.loaded = False
116            return
117        print("Loading Power module")
118
119        self.up_client = UPowerGlib.Client.new()
120
121        self.csd_power_proxy = Gio.DBusProxy.new_sync(
122            Gio.bus_get_sync(Gio.BusType.SESSION, None),
123            Gio.DBusProxyFlags.NONE,
124            None,
125            "org.cinnamon.SettingsDaemon.Power",
126            "/org/cinnamon/SettingsDaemon/Power",
127            "org.cinnamon.SettingsDaemon.Power",
128            None)
129
130        self.settings = Gio.Settings.new("org.cinnamon")
131
132        device_types = [x[UP_TYPE] for x in self.csd_power_proxy.GetDevices()]
133
134        self.has_battery = UPowerGlib.DeviceKind.BATTERY in device_types or UPowerGlib.DeviceKind.UPS in device_types
135        self.has_lid = self.up_client.get_lid_is_present()
136
137        self.sidePage.stack = SettingsStack()
138
139        # Power
140
141        power_page = SettingsPage()
142
143        section = power_page.add_section(_("Power Options"))
144
145        lid_options, button_power_options, critical_options, can_suspend, can_hybrid_sleep, can_hibernate = get_available_options(self.up_client)
146
147        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
148
149        if self.has_battery:
150            header = SettingsWidget()
151            label_ac = Gtk.Label()
152            label_ac.set_markup("<b>%s</b>" % _("On A/C power"))
153            size_group.add_widget(label_ac)
154            label_battery = Gtk.Label()
155            label_battery.set_markup("<b>%s</b>" % _("On battery power"))
156            size_group.add_widget(label_battery)
157            header.pack_end(label_battery, False, False, 0)
158            header.pack_end(label_ac, False, False, 0)
159
160            section.add_row(header)
161
162            section.add_row(GSettings2ComboBox(_("Turn off the screen when inactive for"), CSD_SCHEMA, "sleep-display-ac", "sleep-display-battery", SLEEP_DELAY_OPTIONS, valtype="int", size_group=size_group))
163
164            section.add_row(GSettings2ComboBox(_("Suspend when inactive for"), CSD_SCHEMA, "sleep-inactive-ac-timeout", "sleep-inactive-battery-timeout", SLEEP_DELAY_OPTIONS, valtype="int", size_group=size_group))
165
166            if self.has_lid:
167                section.add_row(GSettings2ComboBox(_("When the lid is closed"), CSD_SCHEMA, "lid-close-ac-action", "lid-close-battery-action", lid_options, size_group=size_group))
168
169        else:
170            section.add_row(GSettingsComboBox(_("Turn off the screen when inactive for"), CSD_SCHEMA, "sleep-display-ac", SLEEP_DELAY_OPTIONS, valtype=int, size_group=size_group))
171
172            section.add_row(GSettingsComboBox(_("Suspend when inactive for"), CSD_SCHEMA, "sleep-inactive-ac-timeout", SLEEP_DELAY_OPTIONS, valtype=int, size_group=size_group))
173
174            if self.has_lid:
175                section.add_row(GSettingsComboBox(_("When the lid is closed"), CSD_SCHEMA, "lid-close-ac-action", lid_options, size_group=size_group))
176
177        section = power_page.add_section(_("Extra options"))
178
179        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
180
181        section.add_row(GSettingsComboBox(_("When the power button is pressed"), CSD_SCHEMA, "button-power", button_power_options, size_group=size_group))
182
183        if self.has_lid:
184            section.add_row(GSettingsSwitch(_("Perform lid-closed action even with external monitors attached"), CSD_SCHEMA, "lid-close-suspend-with-external-monitor"))
185
186        if self.has_battery and UPowerGlib.MAJOR_VERSION == 0 and UPowerGlib.MINOR_VERSION <= 99:
187            section.add_row(GSettingsComboBox(_("When the battery is critically low"), CSD_SCHEMA, "critical-battery-action", critical_options, size_group=size_group))
188
189        if can_suspend and can_hybrid_sleep:
190            self.hybrid_switch = GSettingsSwitch(_("Enable Hybrid Sleep"), CSM_SCHEMA, "prefer-hybrid-sleep")
191            self.hybrid_switch.set_tooltip_text(_("Replaces Suspend with Hybrid Sleep"))
192            self.hybrid_switch.content_widget.connect("notify::active", self.on_hybrid_toggled)
193            section.add_row(self.hybrid_switch)
194
195        if can_suspend and can_hibernate:
196            self.sth_switch = GSettingsSwitch(_("Enable Hibernate after suspend"), CSM_SCHEMA, "suspend-then-hibernate")
197            self.sth_switch.set_tooltip_text(_("First suspend the machine and hibernate it after a certain amount of time."))
198            self.sth_switch.content_widget.connect("notify::active", self.on_sth_toggled)
199            section.add_row(self.sth_switch)
200
201        # Batteries
202
203        self.battery_page = SettingsPage()
204        self.show_battery_page = False
205        self.battery_label_size_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
206
207        self.build_battery_page()
208        self.csd_power_proxy.connect("g-properties-changed", self.build_battery_page)
209
210        proxy = Gio.DBusProxy.new_sync(
211            Gio.bus_get_sync(Gio.BusType.SESSION, None),
212            Gio.DBusProxyFlags.NONE,
213            None,
214            "org.cinnamon.SettingsDaemon.Power",
215            "/org/cinnamon/SettingsDaemon/Power",
216            "org.cinnamon.SettingsDaemon.Power.Screen",
217            None)
218
219        try:
220            brightness = proxy.GetPercentage()
221        except GLib.Error as e:
222            print("Power module brightness page not available: %s" % e.message)
223
224            if self.show_battery_page:
225                self.sidePage.add_widget(self.sidePage.stack)
226                self.sidePage.stack.add_titled(power_page, "power", _("Power"))
227                self.sidePage.stack.add_titled(self.battery_page, "batteries", _("Batteries"))
228            else:
229                self.sidePage.add_widget(power_page)
230        else:
231            self.sidePage.add_widget(self.sidePage.stack)
232            self.sidePage.stack.add_titled(power_page, "power", _("Power"))
233            if self.show_battery_page:
234                self.sidePage.stack.add_titled(self.battery_page, "batteries", _("Batteries"))
235
236            page = SettingsPage()
237            self.sidePage.stack.add_titled(page, "brightness", _("Brightness"))
238
239            size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
240
241            section = page.add_section(_("Screen brightness"))
242            section.add_row(BrightnessSlider(section, proxy, _("Screen brightness")))
243
244            section.add_row(GSettingsSwitch(_("On battery, dim screen when inactive"), CSD_SCHEMA, "idle-dim-battery"))
245
246            section.add_reveal_row(GSettingsComboBox(_("Brightness level when inactive"), CSD_SCHEMA, "idle-brightness", IDLE_BRIGHTNESS_OPTIONS, valtype=int, size_group=size_group), CSD_SCHEMA, "idle-dim-battery")
247
248            section.add_reveal_row(GSettingsComboBox(_("Dim screen after inactive for"), CSD_SCHEMA, "idle-dim-time", IDLE_DELAY_OPTIONS, valtype=int, size_group=size_group), CSD_SCHEMA, "idle-dim-battery")
249
250            proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
251                                           Gio.DBusProxyFlags.NONE,
252                                           None,
253                                           "org.cinnamon.SettingsDaemon.Power",
254                                           "/org/cinnamon/SettingsDaemon/Power",
255                                           "org.cinnamon.SettingsDaemon.Power.Keyboard",
256                                           None)
257
258            try:
259                brightness = proxy.GetPercentage()
260            except GLib.Error as e:
261                print("Power module no keyboard backlight: %s" % e.message)
262            else:
263                section = page.add_section(_("Keyboard backlight"))
264                section.add_row(BrightnessSlider(section, proxy, _("Backlight brightness")))
265
266    def on_sth_toggled(self, widget, gparam):
267        active = widget.get_active()
268        if active and hasattr(self, "hybrid_switch"):
269            self.hybrid_switch.set_value(False)
270
271    def on_hybrid_toggled(self, widget, gparam):
272        active = widget.get_active()
273        if active and hasattr(self, "sth_switch"):
274            self.sth_switch.set_value(False)
275
276    def build_battery_page(self, *args):
277
278        self.aliases = {}
279        device_aliases = self.settings.get_strv("device-aliases")
280        for alias in device_aliases:
281            try:
282                (device_id, device_nickname) = alias.split(":=")
283                self.aliases[device_id] = device_nickname
284            except:
285                pass # ignore malformed aliases
286
287        #destroy all widgets in this page
288        for widget in self.battery_page.get_children():
289            widget.destroy()
290
291        secondary_settings = None
292        primary_settings = None
293
294        # UPowerGlib segfaults when trying to get device. Use CSD instead
295        devices = self.csd_power_proxy.GetDevices()
296
297        have_primary = False
298        ups_as_primary = False
299
300        # first we look for a discharging UPS, which is promoted to the
301        # primary device if it's discharging. Otherwise we use the first
302        # listed laptop battery as the primary device
303
304        for device in devices:
305            if device[UP_TYPE] == UPowerGlib.DeviceKind.UPS and device[UP_STATE] == UPowerGlib.DeviceState.DISCHARGING:
306                ups_as_primary = True
307
308        for device in devices:
309            if device[UP_TYPE] == UPowerGlib.DeviceKind.LINE_POWER:
310                pass # Do nothing
311            elif device[UP_TYPE] == UPowerGlib.DeviceKind.UPS and ups_as_primary:
312                if not primary_settings:
313                    primary_settings = self.battery_page.add_section(_("Batteries"))
314                    primary_settings.add_row(self.set_device_ups_primary(device))
315                    self.show_battery_page = True
316                else:
317                    primary_settings.add_row(self.set_device_ups_primary(device))
318            elif device[UP_TYPE] == UPowerGlib.DeviceKind.BATTERY and not ups_as_primary:
319                if not have_primary:
320                    if not primary_settings:
321                        primary_settings = self.battery_page.add_section(_("Batteries"))
322                        primary_settings.add_row(self.set_device_battery_primary(device))
323                        self.show_battery_page = True
324                    have_primary = True
325                else:
326                    widget = self.set_device_battery_additional(device)
327                    if widget:
328                        primary_settings.add_row(widget)
329            else:
330                if not secondary_settings:
331                    secondary_settings = self.battery_page.add_section(_("Devices"))
332                    secondary_settings.add_row(self.add_battery_device_secondary(device))
333                    self.show_battery_page = True
334                else:
335                    secondary_settings.add_row(self.add_battery_device_secondary(device))
336
337        #show all the widgets in this page, but not the page itself
338        visible = self.battery_page.get_visible()
339        self.battery_page.show_all()
340        self.battery_page.set_visible(visible)
341
342    def set_device_ups_primary(self, device):
343        device_id = device[UP_ID]
344        percentage = device[UP_PERCENTAGE]
345        battery_level = device[UP_BATTERY_LEVEL]
346        state = device[UP_STATE]
347        time = device[UP_SECONDS]
348        vendor = device[UP_VENDOR]
349        model = device[UP_MODEL]
350        details = None
351
352        if time > 0:
353            time_string = get_timestring(time)
354
355            if state == UPowerGlib.DeviceState.DISCHARGING:
356                if percentage < 20:
357                    details = _("Caution low UPS, %s remaining") % time_string
358                else:
359                    details = _("Using UPS power - %s remaining") % time_string
360            else:
361                details = UPowerGlib.Device.state_to_string(state)
362        else:
363            if state == UPowerGlib.DeviceState.DISCHARGING:
364                if percentage < 20:
365                    details = _("Caution low UPS")
366                else:
367                    details = _("Using UPS power")
368            else:
369                details = UPowerGlib.Device.state_to_string(state)
370
371        desc = _("UPS")
372        if (model != "" or vendor != ""):
373            desc = "%s %s" % (vendor, model)
374
375        widget = self.create_battery_row(device_id, "battery", desc, percentage, battery_level, details)
376        return widget
377
378    def set_device_battery_primary(self, device):
379        device_id = device[UP_ID]
380        percentage = device[UP_PERCENTAGE]
381        battery_level = device[UP_BATTERY_LEVEL]
382        state = device[UP_STATE]
383        time = device[UP_SECONDS]
384        vendor = device[UP_VENDOR]
385        model = device[UP_MODEL]
386        details = None
387
388        if time > 0:
389            time_string = get_timestring(time)
390
391            if state == UPowerGlib.DeviceState.CHARGING or state == UPowerGlib.DeviceState.PENDING_CHARGE:
392                details = _("Charging - %s until fully charged") % time_string
393            elif state == UPowerGlib.DeviceState.DISCHARGING or state == UPowerGlib.DeviceState.PENDING_DISCHARGE:
394                if percentage < 20:
395                    details = _("Caution low battery, %s remaining") % time_string
396                else:
397                    details = _("Using battery power - %s remaining") % time_string
398            else:
399                details = UPowerGlib.Device.state_to_string(state)
400        else:
401            if state == UPowerGlib.DeviceState.CHARGING or state == UPowerGlib.DeviceState.PENDING_CHARGE:
402                details = _("Charging")
403            elif state == UPowerGlib.DeviceState.DISCHARGING or state == UPowerGlib.DeviceState.PENDING_DISCHARGE:
404                details = _("Using battery power")
405            elif state == UPowerGlib.DeviceState.FULLY_CHARGED:
406                details = _("Charging - fully charged")
407            elif state == UPowerGlib.DeviceState.EMPTY:
408                details = _("Empty")
409            else:
410                details = UPowerGlib.Device.state_to_string(state)
411
412        desc = _("Battery")
413        if (model != "" or vendor != ""):
414            desc = "%s %s" % (vendor, model)
415
416        widget = self.create_battery_row(device_id, "battery", desc, percentage, battery_level, details)
417        return widget
418
419    def set_device_battery_additional(self, device):
420        state = device[UP_STATE]
421        details = None
422
423        if state == UPowerGlib.DeviceState.FULLY_CHARGED:
424            details = _("Fully charged")
425        elif state == UPowerGlib.DeviceState.EMPTY:
426            details = _("Empty")
427
428        if details:
429            widget = SettingsWidget()
430            icon = Gtk.Image.new_from_icon_name("battery", Gtk.IconSize.DND)
431            widget.pack_start(icon, False, False, 0)
432            label = Gtk.Label(_("Secondary battery"))
433            widget.pack_start(label, False, False, 0)
434            label = Gtk.Label()
435            label.set_markup(details)
436            label.get_style_context().add_class("dim-label")
437            widget.pack_end(label, False, False, 0)
438
439            return widget
440        else:
441            return None
442
443    def add_battery_device_secondary(self, device):
444        device_id = device[UP_ID]
445        kind = device[UP_TYPE]
446        percentage = device[UP_PERCENTAGE]
447        battery_level = device[UP_BATTERY_LEVEL]
448        vendor = device[UP_VENDOR]
449        model = device[UP_MODEL]
450
451        if kind == UPowerGlib.DeviceKind.UPS:
452            icon_name = "uninterruptible-power-supply"
453            desc = _("Uninterruptible power supply")
454        elif kind == UPowerGlib.DeviceKind.MOUSE:
455            icon_name = "input-mouse"
456            desc = _("Wireless mouse")
457        elif kind == UPowerGlib.DeviceKind.KEYBOARD:
458            icon_name = "input-keyboard"
459            desc = _("Wireless Keyboard")
460        elif kind == UPowerGlib.DeviceKind.TABLET:
461            icon_name = "input-tablet"
462            desc = _("Tablet")
463        elif kind == UPowerGlib.DeviceKind.PDA:
464            icon_name = "pda"
465            desc = _("Personal digital assistant")
466        elif kind == UPowerGlib.DeviceKind.PHONE:
467            icon_name = "phone"
468            desc = _("Cellphone")
469        elif kind == UPowerGlib.DeviceKind.MEDIA_PLAYER:
470            icon_name = "multimedia-player"
471            desc = _("Media player")
472        elif kind == UPowerGlib.DeviceKind.COMPUTER:
473            icon_name = "computer"
474            desc = _("Computer")
475        else:
476            icon_name = "battery"
477            desc = (_("Battery"))
478
479        if (model != "" or vendor != ""):
480            desc = "%s %s" % (vendor, model)
481
482        widget = self.create_battery_row(device_id, icon_name, desc, percentage, battery_level)
483        return widget
484
485    def create_battery_row(self, device_id, icon_name, desc, percentage, battery_level, details=None):
486
487        if device_id in self.aliases:
488            desc = self.aliases[device_id]
489
490        widget = SettingsWidget()
491
492        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
493        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
494        label_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
495
496        image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DND)
497        entry = Gtk.Entry()
498        entry.set_text(desc)
499        entry.connect('focus-out-event', self.on_alias_changed, device_id)
500        label_box.pack_start(image, False, False, 0)
501        label_box.pack_start(entry, False, False, 0)
502        self.battery_label_size_group.add_widget(label_box)
503        hbox.pack_start(label_box, False, False, 0)
504
505        if battery_level == UPowerGlib.DeviceLevel.NONE:
506            label = Gtk.Label()
507            label.set_markup("%d%%" % int(percentage))
508            label.set_size_request(30, -1)
509            hbox.pack_start(label, False, False, 15)
510
511            level_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
512            level_bar = Gtk.LevelBar()
513            level_bar.set_mode(Gtk.LevelBarMode.DISCRETE)
514            level_bar.set_min_value(0)
515            level_bar.set_max_value(10)
516            level_bar.add_offset_value("high", 5)
517            level_bar.add_offset_value("low", 2)
518            level_box.set_valign(Gtk.Align.CENTER)
519            level_bar.set_value(round(percentage / 10))
520            level_box.pack_start(level_bar, True, True, 0)
521            hbox.pack_start(level_box, True, True, 0)
522        else:
523            status_label = Gtk.Label(self.bat_level_to_label(battery_level))
524            hbox.pack_end(status_label, False, False, 0)
525
526        vbox.pack_start(hbox, False, False, 0)
527
528        if details:
529            label = Gtk.Label()
530            label.set_markup(details)
531            label.get_style_context().add_class("dim-label")
532            label.set_halign(Gtk.Align.END)
533            vbox.pack_end(label, False, False, 0)
534
535        widget.pack_start(vbox, True, True, 0)
536
537        return widget
538
539    def bat_level_to_label(self, level):
540        if level == UPowerGlib.DeviceLevel.FULL:
541            return _("Battery full")
542        elif level == UPowerGlib.DeviceLevel.HIGH:
543            return _("Battery almost full")
544        elif level == UPowerGlib.DeviceLevel.NORMAL:
545            return _("Battery good")
546        elif level == UPowerGlib.DeviceLevel.LOW:
547            return _("Low battery")
548        elif level == UPowerGlib.DeviceLevel.CRITICAL:
549            return _("Critically low battery")
550
551    def on_alias_changed(self, entry, event, device_id):
552        self.aliases[device_id] = entry.get_text()
553        aliases = []
554        for alias in self.aliases:
555            aliases.append("%s:=%s" % (alias, self.aliases[alias]))
556        self.settings.set_strv("device-aliases", aliases)
557
558
559def get_available_options(up_client):
560    can_suspend = False
561    can_hibernate = False
562    can_hybrid_sleep = False
563
564    # Try logind first
565    try:
566        connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
567        proxy = Gio.DBusProxy.new_sync(
568            connection,
569            Gio.DBusProxyFlags.NONE,
570            None,
571            "org.freedesktop.login1",
572            "/org/freedesktop/login1",
573            "org.freedesktop.login1.Manager",
574            None)
575
576        can_suspend = proxy.CanSuspend() == "yes"
577        can_hibernate = proxy.CanHibernate() == "yes"
578        can_hybrid_sleep = proxy.CanHybridSleep() == "yes"
579    except:
580        pass
581
582    # Next try ConsoleKit
583    try:
584        connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
585        proxy = Gio.DBusProxy.new_sync(
586            connection,
587            Gio.DBusProxyFlags.NONE,
588            None,
589            "org.freedesktop.ConsoleKit",
590            "/org/freedesktop/ConsoleKit/Manager",
591            "org.freedesktop.ConsoleKit.Manager",
592            None)
593
594        can_suspend = can_suspend or (proxy.CanSuspend() == "yes")
595        can_hibernate = can_hibernate or (proxy.CanHybridSleep() == "yes")
596        can_hybrid_sleep = can_hybrid_sleep or (proxy.CanHybridSleep() == "yes")
597    except:
598        pass
599
600    def remove(options, item):
601        for option in options:
602            if option[0] == item:
603                options.remove(option)
604                break
605
606    lid_options = [
607        ("suspend", _("Suspend")),
608        ("shutdown", _("Shut down immediately")),
609        ("hibernate", _("Hibernate")),
610        ("blank", _("Lock Screen")),
611        ("nothing", _("Do nothing"))
612    ]
613
614    button_power_options = [
615        ("blank", _("Lock Screen")),
616        ("suspend", _("Suspend")),
617        ("shutdown", _("Shut down immediately")),
618        ("hibernate", _("Hibernate")),
619        ("interactive", _("Ask")),
620        ("nothing", _("Do nothing"))
621    ]
622
623    critical_options = [
624        ("shutdown", _("Shut down immediately")),
625        ("hibernate", _("Hibernate")),
626        ("nothing", _("Do nothing"))
627    ]
628
629    if not can_suspend:
630        for options in lid_options, button_power_options, critical_options:
631            remove(options, "suspend")
632
633    if not can_hibernate:
634        for options in lid_options, button_power_options, critical_options:
635            remove(options, "hibernate")
636
637    return lid_options, button_power_options, critical_options, can_suspend, can_hybrid_sleep, can_hibernate
638
639class BrightnessSlider(SettingsWidget):
640    step = 5
641
642    def __init__(self, section, proxy, label):
643        super(BrightnessSlider, self).__init__()
644        self.set_orientation(Gtk.Orientation.VERTICAL)
645        self.set_spacing(0)
646
647        self.timer = None
648        self.section = section
649        self.proxy = proxy
650
651        hbox = Gtk.Box()
652
653        self.label = Gtk.Label.new(label)
654        self.label.set_halign(Gtk.Align.CENTER)
655
656        self.min_label= Gtk.Label()
657        self.max_label = Gtk.Label()
658        self.min_label.set_alignment(1.0, 0.75)
659        self.max_label.set_alignment(1.0, 0.75)
660        self.min_label.set_margin_right(6)
661        self.max_label.set_margin_left(6)
662        self.min_label.set_markup("<i><small>0%</small></i>")
663        self.max_label.set_markup("<i><small>100%</small></i>")
664
665        step = 5
666
667        try:
668            # Keyboard backlight
669            step = proxy.GetStep()
670            self.content_widget = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 0, 100, step)
671            val = 0
672
673            while val < 100 - step:
674                val += step
675
676                self.content_widget.add_mark(val, Gtk.PositionType.BOTTOM, None)
677
678        except GLib.Error:
679            self.content_widget = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 1, 100, step)
680
681        self.step = step
682
683        self.content_widget.set_draw_value(False)
684        self.content_widget.set_digits(0)
685
686        hbox.pack_start(self.min_label, False, False, 0)
687        hbox.pack_start(self.content_widget, True, True, 0)
688        hbox.pack_start(self.max_label, False, False, 0)
689
690        self.pack_start(self.label, False, False, 0)
691        self.pack_start(hbox, True, True, 6)
692
693        self.proxy.connect("g-signal", self.on_dbus_changed)
694        self.content_widget.connect("scroll-event", self.on_scroll_event)
695        self.content_widget.connect("change-value", self.on_change_value)
696
697        self.value_changed_id = self.content_widget.connect("value-changed", self.apply_later)
698
699        self.on_dbus_changed()
700
701    def apply_later(self, *args):
702        def apply(self):
703            self.proxy.SetPercentage("(u)", self.content_widget.get_value())
704            self.timer = None
705
706        if self.timer:
707            GLib.source_remove(self.timer)
708        self.timer = GLib.timeout_add(300, apply, self)
709
710    def on_change_value(self, range, scroll_type, value, data=None):
711        # Keyboard backlights can have very few adjustment steps (for instance,
712        # 0, 50% and 100% in the case of a lenovo p51 laptop.)  It's desirable
713        # to have the adjustment ratchet to these values, otherwise you can
714        # have the slider misrepresenting the actual value.
715        #
716        # This intermediate step (change-value signal) lets us clamp the new
717        # value to an actual valid position before sending it to the proxy.
718        i = 0
719        v = round(value)
720        step = self.step
721
722        while i < 100:
723            if v > i + step:
724                i += step
725                continue
726
727            if ((i + step) - v) < (v - i):
728                v = i + step
729            else:
730                v = i
731
732            break
733
734        self.content_widget.set_value(v)
735
736        return True
737
738    def on_scroll_event(self, widget, event):
739        found, delta_x, delta_y = event.get_scroll_deltas()
740
741        # If you scroll up, delta_y < 0. This is a weird world
742        self.content_widget.set_value(widget.get_value() - delta_y * self.step)
743
744        return True
745
746    def on_dbus_changed(self, *args):
747        try:
748            brightness = self.proxy.GetPercentage()
749
750            self.content_widget.handler_block(self.value_changed_id)
751            self.content_widget.set_value(brightness)
752            self.content_widget.handler_unblock(self.value_changed_id)
753        except:
754            self.section.hide()
755
756class GSettings2ComboBox(SettingsWidget):
757    def __init__(self, label, schema, key1, key2, options, valtype="string", dep_key=None, size_group=None):
758        super(GSettings2ComboBox, self).__init__(dep_key=dep_key)
759
760        self.settings = Gio.Settings.new(schema)
761        self.key1 = key1
762        self.key2 = key2
763        self.option_map = {}
764
765        self.label = Gtk.Label.new(label)
766        if valtype == "string":
767            self.model = Gtk.ListStore(str, str)
768        else:
769            self.model = Gtk.ListStore(int, str)
770
771        selected = None
772        for option in options:
773            iter = self.model.insert_before(None, None)
774            self.model.set_value(iter, 0, option[0])
775            self.model.set_value(iter, 1, option[1])
776            self.option_map[option[0]] = iter
777
778        self.content_widget1 = Gtk.ComboBox.new_with_model(self.model)
779        renderer_text = Gtk.CellRendererText()
780        self.content_widget1.pack_start(renderer_text, True)
781        self.content_widget1.add_attribute(renderer_text, "text", 1)
782        self.content_widget1.key = key1
783
784        self.content_widget2 = Gtk.ComboBox.new_with_model(self.model)
785        renderer_text = Gtk.CellRendererText()
786        self.content_widget2.pack_start(renderer_text, True)
787        self.content_widget2.add_attribute(renderer_text, "text", 1)
788        self.content_widget2.key = key2
789
790        self.pack_start(self.label, False, False, 0)
791        self.pack_end(self.content_widget2, False, True, 0)
792        self.pack_end(self.content_widget1, False, True, 0)
793
794        self.content_widget1.connect('changed', self.on_my_value_changed)
795        self.content_widget2.connect('changed', self.on_my_value_changed)
796        self.settings.connect("changed::" + self.key1, self.on_my_setting_changed1)
797        self.settings.connect("changed::" + self.key2, self.on_my_setting_changed2)
798        self.on_my_setting_changed1()
799        self.on_my_setting_changed2()
800
801        if size_group:
802            self.add_to_size_group(size_group)
803
804    def on_my_value_changed(self, widget):
805        tree_iter = widget.get_active_iter()
806        if tree_iter != None:
807            self.settings[widget.key] = self.model[tree_iter][0]
808
809    def on_my_setting_changed1(self, *args):
810        try:
811            self.content_widget1.set_active_iter(self.option_map[self.settings[self.key1]])
812        except:
813            self.content_widget1.set_active_iter(None)
814
815    def on_my_setting_changed2(self, *args):
816        try:
817            self.content_widget2.set_active_iter(self.option_map[self.settings[self.key2]])
818        except:
819            self.content_widget2.set_active_iter(None)
820
821    def add_to_size_group(self, group):
822        group.add_widget(self.content_widget1)
823        group.add_widget(self.content_widget2)
824