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 @@
+
+
+