|
2 | 2 |
|
3 | 3 | import gi |
4 | 4 | gi.require_version('Notify', '0.7') |
5 | | -from gi.repository import Gio, Notify |
| 5 | +from gi.repository import Gio, Notify, Gtk, Pango |
| 6 | +import re |
6 | 7 |
|
7 | 8 | from bin.SettingsWidgets import SidePage |
8 | 9 | from xapp.GSettingsWidgets import * |
@@ -50,11 +51,16 @@ def on_module_selected(self): |
50 | 51 | page = SettingsPage() |
51 | 52 | self.sidePage.add_widget(page) |
52 | 53 |
|
53 | | - settings = page.add_section(_("Notification settings")) |
| 54 | + settings = page.add_section() |
54 | 55 |
|
55 | 56 | switch = GSettingsSwitch(_("Enable notifications"), "org.cinnamon.desktop.notifications", "display-notifications") |
56 | 57 | settings.add_row(switch) |
57 | 58 |
|
| 59 | + button = Button(_("Application notifications"), self.open_app_settings) |
| 60 | + settings.add_reveal_row(button, "org.cinnamon.desktop.notifications", "display-notifications") |
| 61 | + |
| 62 | + settings = page.add_reveal_section(_("Notification settings"), "org.cinnamon.desktop.notifications", "display-notifications") |
| 63 | + |
58 | 64 | switch = GSettingsSwitch(_("Remove notifications after their timeout is reached"), "org.cinnamon.desktop.notifications", "remove-old") |
59 | 65 | settings.add_reveal_row(switch, "org.cinnamon.desktop.notifications", "display-notifications") |
60 | 66 |
|
@@ -86,3 +92,146 @@ def on_module_selected(self): |
86 | 92 | def send_test(self, widget): |
87 | 93 | n = Notify.Notification.new(_("This is a test notification"), content, "dialog-warning") |
88 | 94 | n.show() |
| 95 | + |
| 96 | + def open_app_settings(self, widget): |
| 97 | + win = AppNotificationsWindow(widget.get_toplevel()) |
| 98 | + |
| 99 | +PER_APP_SCHEMA = "org.cinnamon.desktop.notifications.application" |
| 100 | +PER_APP_BASE_PATH = "/org/cinnamon/desktop/notifications/application/" |
| 101 | + |
| 102 | +class AppNotificationRow(Gtk.ListBoxRow): |
| 103 | + def __init__(self, app_info, parent_settings): |
| 104 | + super().__init__() |
| 105 | + self.parent_settings = parent_settings |
| 106 | + self.set_activatable(True) |
| 107 | + self.set_selectable(False) |
| 108 | + self.set_can_focus(True) |
| 109 | + |
| 110 | + self.app_name = app_info.get_name().lower() |
| 111 | + |
| 112 | + # Sanitise app ID for GSettings path (this should remain the same as in ui/messageTray.js) |
| 113 | + # 1. Convert to lower case. |
| 114 | + # 2. Replace any one or more consecutive characters that is not a lowercase letter or a digit with a hyphen. |
| 115 | + # 3. Trim any leading or trailing hyphens. |
| 116 | + app_id = app_info.get_id().lower().replace(".desktop", "") |
| 117 | + self.settings_id = re.sub(r'[^a-z0-9]+', '-', app_id).strip('-') |
| 118 | + path = f"{PER_APP_BASE_PATH}{self.settings_id}/" |
| 119 | + |
| 120 | + self.settings = Gio.Settings.new_with_path(PER_APP_SCHEMA, path) |
| 121 | + |
| 122 | + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) |
| 123 | + hbox.set_margin_start(8) |
| 124 | + hbox.set_margin_end(8) |
| 125 | + hbox.set_margin_top(4) |
| 126 | + hbox.set_margin_bottom(4) |
| 127 | + |
| 128 | + # Icon |
| 129 | + gicon = app_info.get_icon() |
| 130 | + if not gicon: |
| 131 | + gicon = Gio.ThemedIcon.new("application-x-executable") |
| 132 | + icon = Gtk.Image.new_from_gicon(gicon, Gtk.IconSize.DND) |
| 133 | + icon.set_pixel_size(32) |
| 134 | + hbox.pack_start(icon, False, False, 0) |
| 135 | + |
| 136 | + # Labels |
| 137 | + name_label = Gtk.Label(label=app_info.get_name(), xalign=0) |
| 138 | + name_label.set_ellipsize(Pango.EllipsizeMode.END) |
| 139 | + hbox.pack_start(name_label, True, True, 0) |
| 140 | + |
| 141 | + # Switch |
| 142 | + self.switch = Gtk.Switch() |
| 143 | + self.switch.set_active(self.settings.get_boolean("enabled")) |
| 144 | + self.settings.bind("enabled", self.switch, "active", Gio.SettingsBindFlags.DEFAULT) |
| 145 | + self.settings.connect("changed::enabled", self.update_index) |
| 146 | + hbox.pack_start(self.switch, False, False, 0) |
| 147 | + |
| 148 | + self.add(hbox) |
| 149 | + |
| 150 | + def update_index(self, settings, key): |
| 151 | + current_children = list(self.parent_settings.get_strv("application-children")) |
| 152 | + |
| 153 | + if self.settings.get_boolean("enabled"): |
| 154 | + # Since 'true' is the default, we can remove the custom setting from dconf |
| 155 | + if self.settings_id in current_children: |
| 156 | + current_children.remove(self.settings_id) |
| 157 | + self.parent_settings.set_strv("application-children", current_children) |
| 158 | + self.settings.reset("enabled") |
| 159 | + else: |
| 160 | + if self.settings_id not in current_children: |
| 161 | + current_children.append(self.settings_id) |
| 162 | + self.parent_settings.set_strv("application-children", current_children) |
| 163 | + |
| 164 | + def toggle_switch(self): |
| 165 | + self.switch.set_active(not self.switch.get_active()) |
| 166 | + |
| 167 | +class AppNotificationsWindow(Gtk.Dialog): |
| 168 | + def __init__(self, parent): |
| 169 | + super().__init__(title=_("Application Notifications"), transient_for=parent) |
| 170 | + self.set_modal(True) |
| 171 | + self.set_destroy_with_parent(True) |
| 172 | + self.set_default_size(430, 480) |
| 173 | + self.set_border_width(10) |
| 174 | + |
| 175 | + frame = Gtk.Frame() |
| 176 | + frame.set_border_width(6) |
| 177 | + frame.set_shadow_type(Gtk.ShadowType.IN) |
| 178 | + inner_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) |
| 179 | + self.search_entry = Gtk.SearchEntry() |
| 180 | + self.search_entry.set_margin_start(16) |
| 181 | + self.search_entry.set_margin_end(16) |
| 182 | + self.search_entry.connect("search-changed", self.on_search_changed) |
| 183 | + scrolled = Gtk.ScrolledWindow() |
| 184 | + scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) |
| 185 | + |
| 186 | + self.listbox = Gtk.ListBox() |
| 187 | + self.listbox.set_selection_mode(Gtk.SelectionMode.NONE) |
| 188 | + self.listbox.connect("row-activated", self.on_row_activated) |
| 189 | + self.listbox.set_filter_func(self.filter_func) |
| 190 | + |
| 191 | + self.parent_settings = Gio.Settings.new("org.cinnamon.desktop.notifications") |
| 192 | + apps = Gio.AppInfo.get_all() |
| 193 | + # Filter for unique apps that are not hidden |
| 194 | + seen_ids = set() |
| 195 | + for app in sorted(apps, key=lambda x: x.get_name()): |
| 196 | + app_id = app.get_id() |
| 197 | + if app.should_show() and app_id not in seen_ids and not app_id.startswith("cinnamon-settings-"): |
| 198 | + row = AppNotificationRow(app, self.parent_settings) |
| 199 | + self.listbox.add(row) |
| 200 | + seen_ids.add(app_id) |
| 201 | + |
| 202 | + scrolled.add(self.listbox) |
| 203 | + inner_vbox.pack_start(self.search_entry, False, False, 6) |
| 204 | + inner_vbox.pack_start(scrolled, True, True, 0) |
| 205 | + frame.add(inner_vbox) |
| 206 | + content_area = self.get_content_area() |
| 207 | + content_area.pack_start(frame, True, True, 0) |
| 208 | + |
| 209 | + reset_button = Gtk.Button(label=_("Reset All")) |
| 210 | + reset_button.connect("clicked", self.on_reset_all_clicked) |
| 211 | + self.add_action_widget(reset_button, Gtk.ResponseType.NONE) |
| 212 | + |
| 213 | + self.show_all() |
| 214 | + |
| 215 | + def on_row_activated(self, listbox, row): |
| 216 | + row.toggle_switch() |
| 217 | + |
| 218 | + def filter_func(self, row): |
| 219 | + search_text = self.search_entry.get_text().lower() |
| 220 | + if not search_text: |
| 221 | + return True |
| 222 | + return search_text in row.app_name |
| 223 | + |
| 224 | + def on_search_changed(self, entry): |
| 225 | + self.listbox.invalidate_filter() |
| 226 | + |
| 227 | + def on_reset_all_clicked(self, button): |
| 228 | + overridden_apps = self.parent_settings.get_strv("application-children") |
| 229 | + if not overridden_apps: |
| 230 | + return |
| 231 | + |
| 232 | + for app_id in overridden_apps: |
| 233 | + path = f"{PER_APP_BASE_PATH}{app_id}/" |
| 234 | + app_settings = Gio.Settings.new_with_path(PER_APP_SCHEMA, path) |
| 235 | + app_settings.reset("enabled") |
| 236 | + |
| 237 | + self.parent_settings.set_strv("application-children", []) |
0 commit comments