diff --git a/extension.js b/extension.js index d25c83a..3350170 100644 --- a/extension.js +++ b/extension.js @@ -96,10 +96,10 @@ export default class SearchLightExt extends Extension { case 'border-radius': break; case 'shortcut-search': - this._updateShortcut(); + if (this._shortcutsGrabbed) this._updateShortcut(); break; case 'secondary-shortcut-search': - this._updateShortcut2(); + if (this._shortcutsGrabbed) this._updateShortcut2(); break; case 'window-effect': { this._updateWindowEffect(); @@ -163,11 +163,17 @@ export default class SearchLightExt extends Extension { this._updateShortcut(); this._updateShortcut2(); + this._shortcutsGrabbed = true; this._updateCss(); this._useAnimations = this._settings.get_boolean('use-animations'); this._animationSpeed = this._settings.get_double('animation-speed'); + this._focusWindowId = global.display.connect( + 'notify::focus-window', + this._onFocusWindowChanged.bind(this), + ); + Main.overview.connectObject( 'overview-showing', this._onOverviewShowing.bind(this), @@ -222,6 +228,11 @@ export default class SearchLightExt extends Extension { } disable() { + if (this._focusWindowId) { + global.display.disconnect(this._focusWindowId); + this._focusWindowId = null; + } + this._hiTimer?.shutdown(); this._loTimer?.shutdown(); this._hiTimer = null; @@ -894,6 +905,43 @@ export default class SearchLightExt extends Extension { this._style.build('custom-search-light', styles); } + _isFocusedAppBlacklisted() { + let dominated = this.blacklist_apps || []; + if (dominated.length === 0) return false; + + let focusWindow = global.display.focus_window; + if (!focusWindow) return false; + + let tracker = Shell.WindowTracker.get_default(); + let app = tracker.get_window_app(focusWindow); + if (!app) return false; + + let appId = app.get_id(); + return dominated.includes(appId); + } + + _onFocusWindowChanged() { + if (this._isFocusedAppBlacklisted()) { + this._ungrabShortcuts(); + } else { + this._regrabShortcuts(); + } + } + + _ungrabShortcuts() { + if (this._shortcutsGrabbed === false) return; + this._shortcutsGrabbed = false; + this.accel.unlisten(); + this.accel2.unlisten(); + } + + _regrabShortcuts() { + if (this._shortcutsGrabbed === true) return; + this._shortcutsGrabbed = true; + this._updateShortcut(); + this._updateShortcut2(); + } + _toggle_search_light() { if (this._inOverview) return; if (!this._visible) { diff --git a/preferences/keys.js b/preferences/keys.js index d814065..1e2547f 100644 --- a/preferences/keys.js +++ b/preferences/keys.js @@ -115,6 +115,10 @@ export const SettingsKeys = () => { default_value: 100, widget_type: 'scale', }, + 'blacklist-apps': { + default_value: [], + widget_type: 'shortcut', + }, }); return settingsKeys; diff --git a/prefs.js b/prefs.js index 1b4b496..a6c59f3 100644 --- a/prefs.js +++ b/prefs.js @@ -3,6 +3,8 @@ import Gdk from 'gi://Gdk'; import Gtk from 'gi://Gtk'; import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import Adw from 'gi://Adw'; import { ShortcutSettingWidget } from './shortcuts.js'; @@ -103,7 +105,7 @@ export default class Preferences extends ExtensionPreferences { } addButtonEvents(window, builder, settings) { - // + this._setupBlacklist(window, builder, settings); } fillPreferencesWindow(window) { @@ -163,6 +165,236 @@ export default class Preferences extends ExtensionPreferences { this.updateMonitors(); } + _setupBlacklist(window, builder, settings) { + let group = builder.get_object('blacklist-group'); + let addBtn = builder.get_object('blacklist-add-btn'); + + this._blacklistGroup = group; + this._blacklistSettings = settings; + this._blacklistWindow = window; + this._blacklistRows = []; + this._refreshBlacklistRows(); + + addBtn.connect('clicked', () => { + this._openAppChooser(); + }); + } + + _refreshBlacklistRows() { + this._blacklistRows.forEach((row) => { + this._blacklistGroup.remove(row); + }); + this._blacklistRows = []; + + let apps = this._blacklistSettings.get_strv('blacklist-apps'); + apps.forEach((appId) => { + let appInfo = Gio.DesktopAppInfo.new(appId); + let label = appInfo ? appInfo.get_display_name() : appId; + + let row = new Adw.ActionRow({ + title: label, + subtitle: appId, + }); + + if (appInfo && appInfo.get_icon()) { + let icon = new Gtk.Image({ + gicon: appInfo.get_icon(), + pixel_size: 32, + margin_end: 8, + }); + row.add_prefix(icon); + } + + let removeBtn = new Gtk.Button({ + icon_name: 'list-remove-symbolic', + valign: Gtk.Align.CENTER, + css_classes: ['flat'], + }); + removeBtn.connect('clicked', () => { + let current = this._blacklistSettings.get_strv('blacklist-apps'); + current = current.filter((id) => id !== appId); + this._blacklistSettings.set_strv('blacklist-apps', current); + this._refreshBlacklistRows(); + }); + + row.add_suffix(removeBtn); + this._blacklistGroup.add(row); + this._blacklistRows.push(row); + }); + } + + _getRunningAppIds() { + let runningCmds = new Set(); + try { + let procDir = Gio.File.new_for_path('/proc'); + let enumerator = procDir.enumerate_children( + 'standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, + null, + ); + let info; + while ((info = enumerator.next_file(null)) !== null) { + let name = info.get_name(); + if (!/^\d+$/.test(name)) continue; + try { + let cmdlineFile = Gio.File.new_for_path(`/proc/${name}/cmdline`); + let [ok, contents] = cmdlineFile.load_contents(null); + if (ok) { + let cmdline = new TextDecoder().decode(contents).split('\0')[0]; + let basename = cmdline.split('/').pop(); + if (basename) runningCmds.add(basename); + } + } catch (e) { + continue; + } + } + } catch (e) { + return []; + } + + let runningIds = []; + let allApps = Gio.AppInfo.get_all(); + for (let app of allApps) { + if (!app.should_show()) continue; + let exe = app.get_executable(); + if (exe && runningCmds.has(exe.split('/').pop())) { + runningIds.push(app.get_id()); + } + } + return runningIds; + } + + _openAppChooser() { + let dialog = new Adw.Window({ + modal: true, + transient_for: this._blacklistWindow, + title: 'Select Application', + default_width: 400, + default_height: 500, + }); + + let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); + let headerBar = new Adw.HeaderBar(); + box.append(headerBar); + + let searchEntry = new Gtk.SearchEntry({ + placeholder_text: 'Filter applications\u2026', + margin_start: 12, + margin_end: 12, + margin_top: 6, + margin_bottom: 6, + }); + box.append(searchEntry); + + let loadingLabel = new Gtk.Label({ + label: 'Loading application list\u2026', + vexpand: true, + valign: Gtk.Align.CENTER, + css_classes: ['dim-label'], + }); + box.append(loadingLabel); + + let scrolled = new Gtk.ScrolledWindow({ + vexpand: true, + visible: false, + hscrollbar_policy: Gtk.PolicyType.NEVER, + }); + let listBox = new Gtk.ListBox({ + selection_mode: Gtk.SelectionMode.NONE, + }); + listBox.set_filter_func((row) => { + let text = searchEntry.get_text().toLowerCase(); + if (!text) return true; + if (!row._appName) return false; + return row._appName.toLowerCase().includes(text); + }); + scrolled.set_child(listBox); + box.append(scrolled); + + searchEntry.connect('search-changed', () => { + listBox.invalidate_filter(); + }); + + dialog.set_content(box); + dialog.present(); + + GLib.timeout_add(GLib.PRIORITY_DEFAULT, 80, () => { + this._populateAppChooser(dialog, listBox); + loadingLabel.visible = false; + scrolled.visible = true; + return GLib.SOURCE_REMOVE; + }); + } + + _populateAppChooser(dialog, listBox) { + let allApps = Gio.AppInfo.get_all() + .filter((app) => app.should_show()) + .sort((a, b) => a.get_display_name().localeCompare(b.get_display_name())); + + let current = this._blacklistSettings.get_strv('blacklist-apps'); + let runningIds = this._getRunningAppIds(); + + let runningApps = allApps.filter( + (app) => runningIds.includes(app.get_id()) && !current.includes(app.get_id()), + ); + let otherApps = allApps.filter( + (app) => !runningIds.includes(app.get_id()) && !current.includes(app.get_id()), + ); + + let appendAppRow = (appInfo) => { + let appId = appInfo.get_id(); + let row = new Adw.ActionRow({ + title: appInfo.get_display_name(), + subtitle: appId, + }); + row._appName = appInfo.get_display_name(); + + let gicon = appInfo.get_icon(); + if (gicon) { + row.add_prefix(new Gtk.Image({ gicon, pixel_size: 32, margin_end: 8 })); + } + + let addBtn = new Gtk.Button({ + icon_name: 'list-add-symbolic', + valign: Gtk.Align.CENTER, + css_classes: ['flat'], + }); + addBtn.connect('clicked', () => { + let cur = this._blacklistSettings.get_strv('blacklist-apps'); + if (!cur.includes(appId)) { + cur.push(appId); + this._blacklistSettings.set_strv('blacklist-apps', cur); + this._refreshBlacklistRows(); + } + dialog.close(); + }); + + row.add_suffix(addBtn); + row.set_activatable_widget(addBtn); + listBox.append(row); + }; + + runningApps.forEach(appendAppRow); + + if (runningApps.length > 0 && otherApps.length > 0) { + let separatorRow = new Gtk.ListBoxRow({ + selectable: false, + activatable: false, + }); + separatorRow.set_child( + new Gtk.Separator({ + orientation: Gtk.Orientation.HORIZONTAL, + margin_top: 4, + margin_bottom: 4, + }), + ); + separatorRow._appName = ''; + listBox.append(separatorRow); + } + + otherApps.forEach(appendAppRow); + } + updateMonitors() { let monitors = this._monitorsConfig.monitors; let count = monitors.length; diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 51cff3e..1c617a9 100644 Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ diff --git a/schemas/org.gnome.shell.extensions.search-light.gschema.xml b/schemas/org.gnome.shell.extensions.search-light.gschema.xml index 57cbfab..4c1d11c 100644 --- a/schemas/org.gnome.shell.extensions.search-light.gschema.xml +++ b/schemas/org.gnome.shell.extensions.search-light.gschema.xml @@ -132,5 +132,10 @@ Animation speed Change the speed of the appear and disappear window effect. + + + Blacklisted applications + List of application IDs where the shortcut is disabled when they have focus. + diff --git a/ui/general.ui b/ui/general.ui index 111bd38..e679b7c 100644 --- a/ui/general.ui +++ b/ui/general.ui @@ -120,6 +120,25 @@ + + + Application Blacklist + Applications where the shortcut is disabled when focused. + + + Blacklisted Apps + Add applications to suppress the search shortcut (e.g. games). + + + list-add-symbolic + center + Add application + + + + + +