1#!/usr/local/bin/python3.8 2 3import sys 4import json 5import dbus 6import gi 7gi.require_version('Gtk', '3.0') 8from gi.repository import Gtk, Gdk 9 10from SettingsWidgets import SidePage 11from xapp.GSettingsWidgets import * 12 13 14class Monitor: 15 def __init__(self): 16 self.top = -1 17 self.bottom = -1 18 self.right = -1 19 self.left = -1 20 21 22class PanelSettingsPage(SettingsPage): 23 def __init__(self, panel_id, settings, position): 24 super(PanelSettingsPage, self).__init__() 25 self.set_margin_top(0) 26 self.set_margin_bottom(0) 27 self.panel_id = panel_id 28 self.settings = settings 29 30 center_switcher_label = _("Center Zone") 31 32 if position in ("top", "bottom"): 33 dimension_text = _("Panel height:") 34 left_switcher_label = _("Left Zone") 35 right_switcher_label = _("Right Zone") 36 else: 37 dimension_text = _("Panel width:") 38 left_switcher_label = _("Top Zone") 39 right_switcher_label = _("Bottom Zone") 40 41 def can_show(vlist, possible): 42 for item in vlist: 43 if item.split(":")[0] == panel_id: 44 return item.split(":")[1] != "false" 45 46 section = SettingsSection(_("Panel Visibility")) 47 self.add(section) 48 49 self.size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL) 50 51 options = [["true", _("Auto hide panel")], ["false", _("Always show panel")], ["intel", _("Intelligently hide panel")]] 52 widget = PanelComboBox(_("Auto-hide panel"), "org.cinnamon", "panels-autohide", self.panel_id, options, size_group=self.size_group) 53 section.add_row(widget) 54 55 widget = PanelSpinButton(_("Show delay"), "org.cinnamon", "panels-show-delay", self.panel_id, _("milliseconds"), 0, 2000, 50, 200)#, dep_key="org.cinnamon/panels-autohide") 56 section.add_reveal_row(widget, "org.cinnamon", "panels-autohide", check_func=can_show) 57 58 widget = PanelSpinButton(_("Hide delay"), "org.cinnamon", "panels-hide-delay", self.panel_id, _("milliseconds"), 0, 2000, 50, 200)#, dep_key="org.cinnamon/panels-autohide") 59 section.add_reveal_row(widget, "org.cinnamon", "panels-autohide", check_func=can_show) 60 61 section = SettingsSection(_("Customize")) 62 self.add(section) 63 64 widget = PanelRange(dimension_text, "org.cinnamon", "panels-height", self.panel_id, _("Smaller"), _("Larger"), mini=20, maxi=60, show_value=True) 65 widget.set_rounding(0) 66 section.add_row(widget) 67 68 section = SettingsSection(_("Panel appearance")) 69 self.add(section) 70 71 zone_switcher = SettingsWidget() 72 zone_switcher.fill_row() 73 74 switcher_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, border_width=5) 75 zones_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) 76 77 stack = Gtk.Stack(transition_type=Gtk.StackTransitionType.SLIDE_LEFT_RIGHT, transition_duration=150) 78 switcher = Gtk.StackSwitcher(stack=stack, halign=Gtk.Align.CENTER) 79 80 section.add_row(switcher_box) 81 switcher_box.get_parent().set_activatable(False) 82 83 switcher_box.pack_start(switcher, False, False, 0) 84 zones_box.pack_start(stack, False, False, 0) 85 86 zone_infos = [ 87 [left_switcher_label, "left"], 88 [center_switcher_label, "center"], 89 [right_switcher_label, "right"] 90 ]; 91 92 for [zone, label] in (["left", left_switcher_label], 93 ["center", center_switcher_label], 94 ["right", right_switcher_label]): 95 page = self.create_zone_page(zone) 96 page.show_all() 97 98 stack.add_titled(page, zone, label) 99 100 section.add_row(zones_box) 101 zones_box.get_parent().set_activatable(False) 102 103 stack.set_visible_child_name("left") 104 105 self.show_all() 106 107 def create_zone_page(self, zone): 108 zone_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 109 110 text_options = [ 111 [0, _("Allow theme to determine font size")] 112 ] 113 114 points = 6.0 115 while points <= 16.0: 116 text_options.append([points, "%.1fpt" % points]) 117 points += 0.5 118 119 widget = PanelJSONComboBox(_("Font size"), 120 "org.cinnamon", "panel-zone-text-sizes", 121 self.panel_id, zone, text_options, valtype=float, size_group=self.size_group) 122 zone_page.pack_start(widget, False, False, 0) 123 124 fullcolor_options = [ 125 [-1, _("Scale to panel size exactly")], 126 [0, _("Scale to panel size optimally")], 127 [16, '16px'], 128 [22, '22px'], 129 [24, '24px'], 130 [32, '32px'], 131 [48, '48px'] 132 ] 133 134 widget = PanelJSONComboBox(_("Colored icon size"), 135 "org.cinnamon", "panel-zone-icon-sizes", 136 self.panel_id, zone, fullcolor_options, valtype=int, size_group=self.size_group) 137 zone_page.pack_start(widget, False, False, 0) 138 139 widget = PanelJSONSpinButton(_("Symbolic icon size"), 140 "org.cinnamon", "panel-zone-symbolic-icon-sizes", 141 self.panel_id, zone, _("px"), 10, 50, 1, 0) 142 zone_page.pack_start(widget, False, False, 0) 143 144 return zone_page 145 146class Module: 147 name = "panel" 148 category = "prefs" 149 comment = _("Manage Cinnamon panel settings") 150 151 def __init__(self, content_box): 152 keywords = _("panel, height, bottom, top, autohide, size, layout") 153 self.sidePage = SidePage(_("Panel"), "cs-panel", keywords, content_box, module=self) 154 155 def on_module_selected(self): 156 if not self.loaded: 157 print("Loading Panel module") 158 159 self.settings = Gio.Settings.new("org.cinnamon") 160 161 try: 162 if len(sys.argv) > 2 and sys.argv[1] == "panel": 163 self.panel_id = sys.argv[2] 164 else: 165 self.panel_id = self.settings.get_strv("panels-enabled")[0].split(":")[0] 166 except: 167 self.panel_id = "" 168 169 self.panels = [] 170 171 self.previous_button = Gtk.Button(_("Previous panel")) 172 self.next_button = Gtk.Button(_("Next panel")) 173 174 controller = SettingsWidget() 175 controller.fill_row() 176 controller.pack_start(self.previous_button, False, False, 0) 177 controller.pack_end(self.next_button, False, False, 0) 178 self.previous_button.connect("clicked", self.on_previous_panel) 179 self.next_button.connect("clicked", self.on_next_panel) 180 181 self.revealer = SettingsRevealer() 182 183 page = SettingsPage() 184 page.add(controller) 185 page.set_margin_bottom(0) 186 self.revealer.add(page) 187 self.sidePage.add_widget(self.revealer) 188 189 self.config_stack = Gtk.Stack() 190 self.config_stack.set_transition_duration(150) 191 self.revealer.add(self.config_stack) 192 193 page = SettingsPage() 194 self.sidePage.add_widget(page) 195 section = page.add_section(_("General Panel Options")) 196 197 buttons = SettingsWidget() 198 self.add_panel_button = Gtk.Button(label=_("Add new panel")) 199 200 buttons.pack_start(self.add_panel_button, False, False, 2) 201 toggle_button = Gtk.ToggleButton(label=_("Panel edit mode")) 202 203 self.settings.bind("panel-edit-mode", toggle_button, "active", Gio.SettingsBindFlags.DEFAULT) 204 buttons.pack_end(toggle_button, False, False, 2) 205 section.add_row(buttons) 206 207 section.add_row(GSettingsSwitch(_("Allow the pointer to pass through the edges of panels"), "org.cinnamon", "no-adjacent-panel-barriers")) 208 209 self.add_panel_button.set_sensitive(False) 210 211 self.settings.connect("changed::panels-enabled", self.on_panel_list_changed) 212 213 self.proxy = None 214 215 try: 216 Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None, 217 "org.Cinnamon", "/org/Cinnamon", "org.Cinnamon", None, self._on_proxy_ready, None) 218 except dbus.exceptions.DBusException as e: 219 print(e) 220 self.proxy = None 221 222 self.on_panel_list_changed() 223 224 def _on_proxy_ready (self, object, result, data=None): 225 self.proxy = Gio.DBusProxy.new_for_bus_finish(result) 226 227 if not self.proxy.get_name_owner(): 228 self.proxy = None 229 230 if self.proxy: 231 self.revealer.connect("unmap", self.restore_panels) 232 self.revealer.connect("destroy", self.restore_panels) 233 234 self.add_panel_button.connect("clicked", self.on_add_panel) 235 236 if self.panel_id is not None: 237 self.proxy.highlightPanel('(ib)', int(self.panel_id), True) 238 239 def on_add_panel(self, widget): 240 if self.proxy: 241 self.proxy.addPanelQuery() 242 243 def on_previous_panel(self, widget): 244 if self.panel_id and self.proxy: 245 self.proxy.highlightPanel('(ib)', int(self.panel_id), False) 246 247 current = self.panels.index(self.current_panel) 248 249 if current - 1 >= 0: 250 self.current_panel = self.panels[current - 1] 251 self.panel_id = self.current_panel.panel_id 252 else: 253 self.current_panel = self.panels[len(self.panels) - 1] 254 self.panel_id = self.current_panel.panel_id 255 256 self.config_stack.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT) 257 258 if self.proxy: 259 self.proxy.highlightPanel('(ib)', int(self.panel_id), True) 260 261 self.config_stack.set_visible_child(self.current_panel) 262 263 def on_next_panel(self, widget): 264 if self.panel_id and self.proxy: 265 self.proxy.highlightPanel('(ib)', int(self.panel_id), False) 266 267 current = self.panels.index(self.current_panel) 268 269 if current + 1 < len(self.panels): 270 self.current_panel = self.panels[current + 1] 271 self.panel_id = self.current_panel.panel_id 272 else: 273 self.current_panel = self.panels[0] 274 self.panel_id = self.current_panel.panel_id 275 276 self.config_stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT) 277 278 if self.proxy: 279 self.proxy.highlightPanel('(ib)', int(self.panel_id), True) 280 281 self.config_stack.set_visible_child(self.current_panel) 282 283 def on_panel_list_changed(self, *args): 284 if len(self.panels) > 0: 285 for panel in self.panels: 286 panel.destroy() 287 288 self.panels = [] 289 monitor_layout = [] 290 291 panels = self.settings.get_strv("panels-enabled") 292 n_mons = Gdk.Screen.get_default().get_n_monitors() 293 294 for i in range(n_mons): 295 monitor_layout.append(Monitor()) 296 297 current_found = False 298 for panel in panels: 299 panel_id, monitor_id, position = panel.split(":") 300 monitor_id = int(monitor_id) 301 panel_page = PanelSettingsPage(panel_id, self.settings, position) 302 self.config_stack.add_named(panel_page, panel_id) 303 304 # we may already have a current panel id from the command line or if 305 # if the panels-enabled key changed since everything was loaded 306 if panel_id == self.panel_id: 307 current_found = True 308 self.current_panel = panel_page 309 self.config_stack.set_visible_child(panel_page) 310 311 # we don't currently show panels on monitors that aren't attached 312 # if we decide to change this behavior, we should probably give some visual indication 313 # that the panel is on a detached monitor 314 if monitor_id < n_mons: 315 if "top" in position: 316 monitor_layout[monitor_id].top = panel_page 317 elif "bottom" in position: 318 monitor_layout[monitor_id].bottom = panel_page 319 elif "left" in position: 320 monitor_layout[monitor_id].left = panel_page 321 else: 322 monitor_layout[monitor_id].right = panel_page 323 324 # Index the panels for the next/previous buttons 325 for monitor in monitor_layout: 326 for panel_page in (monitor.top, monitor.bottom, monitor.left, monitor.right): 327 if panel_page != -1: 328 self.panels.append(panel_page) 329 330 # if there are no panels, there's no point in showing the stack 331 if len(self.panels) == 0: 332 self.next_button.hide() 333 self.previous_button.hide() 334 self.config_stack.hide() 335 self.add_panel_button.set_sensitive(True) 336 self.current_panel = None 337 self.panel_id = None 338 return 339 340 self.config_stack.show() 341 self.next_button.show() 342 self.previous_button.show() 343 344 # Disable the panel switch buttons if there's only one panel 345 if len(self.panels) == 1: 346 self.next_button.set_sensitive(False) 347 self.previous_button.set_sensitive(False) 348 else: 349 self.next_button.set_sensitive(True) 350 self.previous_button.set_sensitive(True) 351 352 if not current_found: 353 self.current_panel = self.panels[0] 354 self.panel_id = self.current_panel.panel_id 355 self.config_stack.set_visible_child(self.current_panel) 356 357 self.revealer.set_reveal_child(len(self.panels) != 0) 358 359 # If all panel positions are full, we want to disable the add button 360 can_add = False 361 for monitor in monitor_layout: 362 if -1 in (monitor.top, monitor.bottom, monitor.left, monitor.right): 363 can_add = True 364 break 365 366 self.add_panel_button.set_sensitive(can_add) 367 368 try: 369 current_idx = self.panels.index(self.panel_id) 370 except: 371 current_idx = 0 372 373 if self.proxy: 374 self.proxy.highlightPanel('(ib)', int(self.panel_id), True) 375 376 def restore_panels(self, widget): 377 self.proxy.destroyDummyPanels() 378 if self.panel_id: 379 self.proxy.highlightPanel('(ib)', int(self.panel_id), False) 380 381class PanelWidgetBackend(object): 382 def connect_to_settings(self, schema, key): 383 self.key = key 384 self.settings = Gio.Settings.new(schema) 385 self.settings_changed_id = self.settings.connect("changed::"+self.key, self.on_setting_changed) 386 self.connect("destroy", self.on_destroy) 387 self.on_setting_changed() 388 389 # unless we have a binding direction get, we need to connect the handlers after hooking up the settings 390 # this is different from the GSettingsBackend because we cant use a bind here due to the complicated nature 391 # of the getting and setting 392 if self.bind_dir is None or (self.bind_dir & Gio.SettingsBindFlags.GET == 0): 393 self.connect_widget_handlers() 394 395 def set_value(self, value): 396 vals = self.settings[self.key] 397 newvals = [] 398 for val in vals: 399 if val.split(":")[0] == self.panel_id: 400 newvals.append(self.panel_id + ":" + self.stringify(value)) 401 else: 402 newvals.append(val) 403 self.settings[self.key] = newvals 404 405 def get_value(self): 406 vals = self.settings[self.key] 407 for val in vals: 408 [pid, value] = val.split(":") 409 if pid == self.panel_id: 410 return self.unstringify(value) 411 412 def stringify(self, value): 413 return str(value) 414 415 def on_destroy(self, *args): 416 self.settings.disconnect(self.settings_changed_id) 417 418class PanelSwitch(Switch, PanelWidgetBackend): 419 def __init__(self, label, schema, key, panel_id, *args, **kwargs): 420 self.panel_id = panel_id 421 super(PanelSwitch, self).__init__(label, *args, **kwargs) 422 423 self.connect_to_settings(schema, key) 424 425 def stringify(self, value): 426 return "true" if value else "false" 427 428 def unstringify(self, value): 429 return value != "false" 430 431 def on_setting_changed(self, *args): 432 value = self.get_value() 433 if value != self.content_widget.get_active(): 434 self.content_widget.set_active(value) 435 436 def connect_widget_handlers(self, *args): 437 self.content_widget.connect("notify::active", self.on_my_value_changed) 438 439 def on_my_value_changed(self, *args): 440 active = self.content_widget.get_active() 441 if self.get_value() != active: 442 self.set_value(active) 443 444class PanelSpinButton(SpinButton, PanelWidgetBackend): 445 def __init__(self, label, schema, key, panel_id, *args, **kwargs): 446 self.panel_id = panel_id 447 super(PanelSpinButton, self).__init__(label, *args, **kwargs) 448 449 self.content_widget.set_value(0) 450 451 self.connect_to_settings(schema, key) 452 453 def get_range(self): 454 return None 455 456 # We use integer directly here because that is all the panel currently uses. 457 # If that changes in the future, we will need to fix this. 458 def stringify(self, value): 459 return str(int(value)) 460 461 def unstringify(self, value): 462 return int(value) 463 464 def on_setting_changed(self, *args): 465 value = self.get_value() 466 if value is not None and value != int(self.content_widget.get_value()): 467 self.content_widget.set_value(value) 468 469class PanelJSONSpinButton(SpinButton, PanelWidgetBackend): 470 def __init__(self, label, schema, key, panel_id, zone, *args, **kwargs): 471 self.panel_id = panel_id 472 self.zone = zone 473 super(PanelJSONSpinButton, self).__init__(label, *args, **kwargs) 474 475 self.connect_to_settings(schema, key) 476 477 def get_range(self): 478 return 479 480 # We use integer directly here because that is all the panel currently uses. 481 # If that changes in the future, we will need to fix this. 482 def stringify(self, value): 483 return str(int(value)) 484 485 def unstringify(self, value): 486 return int(value) 487 488 def on_setting_changed(self, *args): 489 self.content_widget.set_value(self.get_value()) 490 491 def set_value(self, value): 492 vals = json.loads(self.settings[self.key]) 493 for obj in vals: 494 if obj['panelId'] != int(self.panel_id): 495 continue 496 for key, val in obj.items(): 497 if key == self.zone: 498 obj[key] = int(value) 499 break 500 501 self.settings[self.key] = json.dumps(vals) 502 503 def get_value(self): 504 vals = self.settings[self.key] 505 vals = json.loads(vals) 506 for obj in vals: 507 if obj['panelId'] != int(self.panel_id): 508 continue 509 for key, val in obj.items(): 510 if key == self.zone: 511 return int(val) 512 return 0 # prevent warnings if key is reset 513 514class PanelComboBox(ComboBox, PanelWidgetBackend): 515 def __init__(self, label, schema, key, panel_id, *args, **kwargs): 516 self.panel_id = panel_id 517 super(PanelComboBox, self).__init__(label, *args, **kwargs) 518 519 self.connect_to_settings(schema, key) 520 521 def stringify(self, value): 522 return value 523 524 def unstringify(self, value): 525 return value 526 527class PanelJSONComboBox(ComboBox, PanelWidgetBackend): 528 def __init__(self, label, schema, key, panel_id, zone, *args, **kwargs): 529 self.panel_id = panel_id 530 self.zone = zone 531 super(PanelJSONComboBox, self).__init__(label, *args, **kwargs) 532 533 self.connect_to_settings(schema, key) 534 535 def stringify(self, value): 536 return value 537 538 def unstringify(self, value): 539 return value 540 541 def set_value(self, value): 542 vals = json.loads(self.settings[self.key]) 543 for obj in vals: 544 if obj['panelId'] != int(self.panel_id): 545 continue 546 for key, val in obj.items(): 547 if key == self.zone: 548 obj[key] = self.valtype(value) 549 break 550 551 self.settings[self.key] = json.dumps(vals) 552 553 def get_value(self): 554 vals = self.settings[self.key] 555 vals = json.loads(vals) 556 for obj in vals: 557 if obj['panelId'] != int(self.panel_id): 558 continue 559 for key, val in obj.items(): 560 if key == self.zone: 561 return self.valtype(val) 562 563class PanelRange(Range, PanelWidgetBackend): 564 def __init__(self, label, schema, key, panel_id, *args, **kwargs): 565 self.panel_id = panel_id 566 super(PanelRange, self).__init__(label, *args, **kwargs) 567 self.connect_to_settings(schema, key) 568 569 def get_range(self): 570 return None 571 572 # We use integer directly here because that is all the panel currently uses. 573 # If that changes in the future, we will need to fix this. 574 def stringify(self, value): 575 return str(int(value)) 576 577 def unstringify(self, value): 578 return int(value) 579 580 def on_setting_changed(self, *args): 581 value = self.get_value() 582 if value != int(self.bind_object.get_value()): 583 self.bind_object.set_value(value) 584