fix(services/spotlight): improve MPRIS artwork handling and scope keyword searches to single plugin

- mpris: native MediaArt cache lookup, dedupe play/pause emission, safe
  cleanup, artwork polling for already-playing browser players, drop
  os.system seek fallback
- spotlight: scope plugin keyword and detect() matches to a single plugin
  via search_single instead of global search; register clipboard keywords
  (clip/clipboard)
This commit is contained in:
sankalp committed 2026-07-13 22:20:19 +05:45
1 parent 1fc7edd483
commit 54ff85ffc9
43 files changed
+1227 -637

No files matched your search

+1 -6
View File
@@ -657,12 +657,6 @@ class BluetoothConnections(Box):
device_slot = BluetoothDeviceSlot(device)
self.other_devices.add(device_slot)
for slot in existing_paired.values():
pass
for slot in existing_other.values():
pass
if not self._destroyed:
has_paired_devices = len(paired_devices) > 0
has_other_devices = len(other_devices) > 0
@@ -679,6 +673,7 @@ class BluetoothConnections(Box):
def start_device_monitoring(self):
"""Start periodic monitoring for device changes"""
self.stop_device_monitoring()
self.refresh_timer = GLib.timeout_add_seconds(5, self.periodic_device_refresh)
def stop_device_monitoring(self):
+5 -17
View File
@@ -7,7 +7,7 @@ from fabric.widgets.label import Label
from fabric.widgets.overlay import Overlay
from services.brightness import Brightness
from services.modus import is_caffeine_active, toggle_caffeine
from services.inhibit import get_inhibit_service
from services.network import NetworkClient
from shared.widgets.flat_scale import FlatScale
from shared.window.applet_window import AppletWindow
@@ -133,11 +133,8 @@ class ModusControlCenter(AppletWindow):
h_expand=True,
)
# Only connect brightness controls if brightness service is available
if get_brightness_service().max_screen > 0:
pass
else:
# Disable brightness scale if no backlight device available
# Disable brightness scale if no backlight device is available
if get_brightness_service().max_screen <= 0:
self.brightness_scale.set_sensitive(False)
self._mpris_manager = get_shared_mpris_manager()
@@ -722,12 +719,8 @@ class ModusControlCenter(AppletWindow):
self.per_app_volume_center_box.set_size_request(300, -1)
def _check_initial_states(self):
self.caffeine_mode = is_caffeine_active()
self.caffeine_mode = get_inhibit_service().active
self.flight_mode = False
GLib.timeout_add(100, self._update_initial_labels)
def _update_initial_labels(self):
return False
def set_dont_disturb(self, *_):
self.focus_mode = not self.focus_mode
@@ -770,7 +763,7 @@ class ModusControlCenter(AppletWindow):
def toggle_caffeine(self, *_):
try:
self.caffeine_mode = toggle_caffeine()
self.caffeine_mode = get_inhibit_service().toggle()
self.caffeine_icon.dynamic_file(
"applets/caffeine-on.svg"
if self.caffeine_mode
@@ -1200,11 +1193,6 @@ class ModusControlCenter(AppletWindow):
self._cleanup_processes()
self._disconnect_signals_when_hidden()
# Force garbage collection
import gc
gc.collect()
logger.debug("Complete cleanup finished successfully")
except Exception as e:
+40 -36
View File
@@ -11,6 +11,43 @@ from shared.widgets.flat_scale import FlatScale
# Local imports
from utils.roam import audio_service
# Mappings used to resolve an app's icon/display name from its description or
# name. Defined once at module level to avoid rebuilding on every refresh.
_APP_ICON_MAPPING = {
"spotify": "spotify",
"firefox": "firefox",
"chromium": "chromium-browser",
"chrome": "google-chrome",
"vlc": "vlc",
"discord": "discord",
"steam": "steam",
"zen": "zen-browser",
"code": "vscode",
"visual studio code": "vscode",
"telegram": "telegram-desktop",
"pulse": "audio-card",
"pipewire": "audio-card",
"alsa": "audio-card",
"sink": "audio-speakers",
"source": "audio-input-microphone",
"youtube": "youtube",
"music": "rhythmbox",
"media": "multimedia-player",
}
_APP_SPECIAL_NAMES = {
"spotify": "Spotify",
"firefox": "Firefox",
"chromium": "Chromium",
"chrome": "Chrome",
"vlc": "VLC",
"discord": "Discord",
"steam": "Steam",
"zen": "Zen Browser",
"code": "VS Code",
"telegram": "Telegram",
}
class PerAppVolumeControl(Box):
"""Per-application volume control widget"""
@@ -125,27 +162,7 @@ class PerAppVolumeControl(Box):
# Check description first as it's more reliable
search_text = app_description if app_description else app_name
icon_mapping = {
"spotify": "spotify",
"firefox": "firefox",
"chromium": "chromium-browser",
"chrome": "google-chrome",
"vlc": "vlc",
"discord": "discord",
"steam": "steam",
"zen": "zen-browser",
"code": "vscode",
"visual studio code": "vscode",
"telegram": "telegram-desktop",
"pulse": "audio-card",
"pipewire": "audio-card",
"alsa": "audio-card",
"sink": "audio-speakers",
"source": "audio-input-microphone",
"youtube": "youtube",
"music": "rhythmbox",
"media": "multimedia-player",
}
icon_mapping = _APP_ICON_MAPPING
# Check if any key in mapping is contained in search text
for key, icon in icon_mapping.items():
@@ -167,22 +184,9 @@ class PerAppVolumeControl(Box):
return "Unknown"
# Handle common app names specially
special_names = {
"spotify": "Spotify",
"firefox": "Firefox",
"chromium": "Chromium",
"chrome": "Chrome",
"vlc": "VLC",
"discord": "Discord",
"steam": "Steam",
"zen": "Zen Browser",
"code": "VS Code",
"telegram": "Telegram",
}
name_lower = name.lower()
if name_lower in special_names:
return special_names[name_lower]
if name_lower in _APP_SPECIAL_NAMES:
return _APP_SPECIAL_NAMES[name_lower]
# Default: capitalize first letter
return name.capitalize()
+1
View File
@@ -631,6 +631,7 @@ class WifiConnections(Box):
def start_network_monitoring(self):
"""Start periodic monitoring for network changes"""
self.stop_network_monitoring()
self.refresh_timer = GLib.timeout_add_seconds(5, self.periodic_network_refresh)
def stop_network_monitoring(self):
+8 -6
View File
@@ -56,6 +56,10 @@ from .layout import DockLayout
_PIXBUF_CACHE_MAX = 64
# Terminal/interpreter executables whose name alone should not force a match
# against unrelated desktop apps during dock item resolution.
_COMMON_EXECUTABLES = ("kitty", "bash", "sh", "zsh", "python", "python3")
class DockCanvas(Gtk.DrawingArea):
def __init__(self, parent_window):
@@ -136,9 +140,6 @@ class DockCanvas(Gtk.DrawingArea):
self._desktop_apps = []
return self._desktop_apps
def _refresh_desktop_apps(self) -> None:
self._desktop_apps = []
def _canvas_height(self) -> int:
size = self._base_icon_size()
return int(
@@ -921,7 +922,9 @@ class DockCanvas(Gtk.DrawingArea):
if command_line:
launch_app(command_line)
elif hasattr(app_info, "launch"):
app_info.launch()
from globalmenu.launch import launch_desktop_app
launch_desktop_app(app_info)
else:
logger.error(
f"[DockCanvas] Cannot determine launch command: {app_info}"
@@ -976,8 +979,7 @@ class DockCanvas(Gtk.DrawingArea):
if app.executable:
exe = os.path.basename(app.executable).lower()
if exe in expanded:
common = ["kitty", "bash", "sh", "zsh", "python", "python3"]
if exe in common:
if exe in _COMMON_EXECUTABLES:
app_names = [
getattr(app, "name", ""),
getattr(app, "display_name", ""),
+2 -2
View File
@@ -257,12 +257,12 @@ def initialize():
),
)
lockscreen.show()
return lock, lockscreen
def main():
set_process_name("lockscreen")
initialize()
lockscreen = LockScreen(GtkSessionLock.Lock())
_lock, lockscreen = initialize()
global app
app = Application("lock", lockscreen)
+1 -19
View File
@@ -21,7 +21,7 @@ from services.modus import notification_service
from shared.widgets.clipping_box import ClippingBox
from shared.widgets.custom_image import CustomImage
from shared.widgets.customrevealer import SlideRevealer
from utils.functions import escape_markup_text, parse_timeout_string
from utils.functions import escape_markup_text
from utils.roam import modus_service
from utils.icon_resolver import IconResolver
@@ -1071,9 +1071,6 @@ class ModusNoti(Window):
# Preload assets immediately for optimal caching and display performance
preload_notification_assets(notification)
# Implement smart queue management for smooth transitions
GLib.get_monotonic_time() / 1000
# If queue is getting full, remove oldest notifications smoothly
if len(self.notification_queue) >= self.MAX_QUEUE_SIZE:
# Remove oldest notification from queue (not current showing one)
@@ -1271,21 +1268,6 @@ class ModusNoti(Window):
def update_config(self, new_config):
"""Update notification configuration dynamically."""
try:
# Handle notification timeout changes
if "notification_timeout" in new_config:
timeout_str = new_config["notification_timeout"]
# Parse the timeout string to milliseconds
parse_timeout_string(timeout_str)
# Handle ignored apps changes
if "notification_ignored_apps" in new_config:
new_config["notification_ignored_apps"]
# Handle limited apps history changes
if "notification_limited_apps_history" in new_config:
new_config["notification_limited_apps_history"]
# Update the current config
self._current_config.update(new_config)
+4 -10
View File
@@ -178,13 +178,9 @@ class ExpandableNotificationGroup(Box):
self.add(self.collapsed_eventbox)
# Create expanded state (hidden initially)
self.create_expanded_state()
def _get_notification_pixbuf_for_group(self, cached_notification):
"""Get notification pixbuf at 64x64 - matches app icon size for consistency"""
notification = cached_notification._notification
getattr(notification, "id", None)
pixbuf = None
# First try to get cached notification image using stored cache key
@@ -372,10 +368,6 @@ class ExpandableNotificationGroup(Box):
self.expanded_container.set_visible(False)
return False # Don't repeat timeout
def _complete_collapse(self):
"""Complete the collapse animation - no longer needed but kept for compatibility"""
return False # Don't repeat timeout
def close_all(self, *args):
"""Close all notifications in this group with proper cache cleanup"""
# Close all notifications in this group
@@ -462,7 +454,6 @@ class NotificationCenterWidget(NotificationWidget):
def _get_notification_pixbuf(self, notification):
"""Get notification pixbuf at 64x64 - matches app icon size for consistency"""
getattr(notification, "id", None)
pixbuf = None
# First try to get cached notification image using stored cache key
@@ -819,8 +810,11 @@ class NotificationCenter(AppletWindow):
group_widget.remove(child)
child.destroy()
# Recreate content
# Recreate content (collapsed + expanded states)
group_widget.create_collapsed_state()
group_widget.create_expanded_state()
group_widget.collapsed_eventbox.set_visible(True)
group_widget.expanded_container.set_visible(False)
group_widget.show_all()
except Exception as e:
-6
View File
@@ -69,12 +69,6 @@ class Panel(Window):
self.tray.connect("add", self._update_tray_visibility)
self.tray.connect("remove", self._update_tray_visibility)
self.indicators = Box(
name="indicators",
orientation="h",
spacing=4,
)
self._spotlight_toggle_cb = None
self.search = Button(
name="panel-button",
+27 -7
View File
@@ -334,9 +334,24 @@ class MicrophoneIndicator(Box):
self._connect_mic_signals()
def _connect_mic_signals(self):
if self._audio.microphone:
self._audio.microphone.connect("changed", self._on_mic_stream_changed)
self._sync_state()
mic = self._audio.microphone
if mic is None:
return
# Disconnect from a previously-connected device if it changed, and
# avoid stacking duplicate handlers on the same device.
old = getattr(self, "_mic_device", None)
if old is not None and old is not mic:
try:
old.disconnect_by_func(self._on_mic_stream_changed)
except Exception:
pass
try:
mic.disconnect_by_func(self._on_mic_stream_changed)
except Exception:
pass
mic.connect("changed", self._on_mic_stream_changed)
self._mic_device = mic
self._sync_state()
def _on_mic_device_changed(self, *_):
self._connect_mic_signals()
@@ -371,8 +386,13 @@ class MicrophoneIndicator(Box):
self._hide_timer_id = 0
try:
self._audio.disconnect_by_func(self._on_mic_device_changed)
if self._audio.microphone:
self._audio.microphone.disconnect_by_func(self._on_mic_stream_changed)
except Exception as e:
logger.warning(f"[NotchIndicators] mic disconnect: {e}")
except Exception:
pass
old = getattr(self, "_mic_device", None)
target = old if old is not None else self._audio.microphone
if target is not None:
try:
target.disconnect_by_func(self._on_mic_stream_changed)
except Exception as e:
logger.warning(f"[NotchIndicators] mic disconnect: {e}")
super().destroy()
+6
View File
@@ -188,6 +188,12 @@ class Notch(Box):
def _on_player_vanish(self, _manager, name: str):
if name in self._music_services:
self._music_services.remove(name)
try:
svc = self._mpris.get_player_service(name)
if svc is not None:
svc.disconnect_by_func(self._on_any_artwork)
except Exception:
pass
self._apply_stack_state()
# ── Scroll ────────────────────────────────────────────────────────────────
+13
View File
@@ -257,6 +257,19 @@ class ScreenCaptureWindow(Window):
_handler_id[0] = screen_capture_service.connect("screenshot-taken", _on_taken)
def _safe_disconnect():
if _handler_id[0] is not None:
try:
screen_capture_service.disconnect(_handler_id[0])
except Exception:
pass
_handler_id[0] = None
return False
# Safety net: disconnect if the service never emits (e.g. capture failed
# silently), otherwise the one-shot handler would leak on every shot.
GLib.timeout_add_seconds(10, _safe_disconnect)
ok = screen_capture_service.screenshot(
target,
output_dir=output_dir,
-2
View File
@@ -113,8 +113,6 @@ class PluginManager:
if entry.module_name:
self._loader.unload_module(entry.module_name)
from pathlib import Path
fp = Path(entry.path) if entry.path else None
if fp and fp.exists():
source = entry.source
+36 -4
View File
@@ -75,7 +75,7 @@ class SearchPipeline:
self._current_token.cancel()
with self._lock:
for t in self._pending_threads:
t.join(timeout=0.5)
t.join(timeout=0.1)
self._pending_threads.clear()
for source_id in self._pending_idle_ids:
GLib.source_remove(source_id)
@@ -112,13 +112,33 @@ class SearchPipeline:
results = self._safe_search(instance, query, token)
self._stamp_results(results, entry)
if not token.is_cancelled:
source_id = GLib.idle_add(lambda: callback(results))
def _fire():
with self._lock:
try:
self._pending_idle_ids.remove(source_id)
except ValueError:
pass
callback(results)
return False
source_id = GLib.idle_add(_fire)
with self._lock:
self._pending_idle_ids.append(source_id)
except Exception as e:
logger.error(f"[SearchPipeline] Error searching {entry.id}: {e}")
if not token.is_cancelled:
source_id = GLib.idle_add(lambda: callback([]))
def _fire_err():
with self._lock:
try:
self._pending_idle_ids.remove(source_id)
except ValueError:
pass
callback([])
return False
source_id = GLib.idle_add(_fire_err)
with self._lock:
self._pending_idle_ids.append(source_id)
@@ -143,6 +163,8 @@ class SearchPipeline:
remaining = len(searchable)
def _on_all_done():
if token.is_cancelled:
return
merged = self._merge_results(collected)
callback(merged)
@@ -151,7 +173,17 @@ class SearchPipeline:
with results_lock:
remaining -= 1
if remaining <= 0:
source_id = GLib.idle_add(_on_all_done)
def _fire_all_done():
with self._lock:
try:
self._pending_idle_ids.remove(source_id)
except ValueError:
pass
_on_all_done()
return False
source_id = GLib.idle_add(_fire_all_done)
with self._lock:
self._pending_idle_ids.append(source_id)
+58 -35
View File
@@ -1,4 +1,3 @@
import gc
import os
from fabric.core import Signal
@@ -98,7 +97,7 @@ class Spotlight(Box):
self.children = self.header, self.scrolled_clip
# Search
@debounce(200)
@debounce(300)
def on_entry_changed(self, entry: Entry, *_):
if getattr(entry, "_ignore_change", False):
entry._ignore_change = False
@@ -128,7 +127,11 @@ class Spotlight(Box):
self.header_icon.set_from_icon_name(
getattr(inst, "icon", "") or "system-search-symbolic"
)
self.plugin_service.pipeline.search(text, self._on_search_results)
# Scope to the detected plugin only — a global search would
# mix in unrelated plugins' results.
self.plugin_service.pipeline.search_single(
pe, text, self._on_search_results
)
return
self.header_icon.set_from_icon_name("system-search-symbolic")
@@ -225,7 +228,27 @@ class Spotlight(Box):
self._deactivate_keyword_plugin()
def _on_search_results(self, results: list[SearchResult]):
self._clear_viewport()
new_ids = [r.id for r in results]
old_ids = list(self._result_widgets.keys())
if new_ids == old_ids:
self._current_results = results
return
for rid in set(old_ids) - set(new_ids):
self._remove_result_widget(rid)
for child in list(self.viewport.children):
self.viewport.remove(child)
for result in results:
widget = self._result_widgets.get(result.id)
if not widget:
widget = self._render_result(result)
self._result_widgets[result.id] = widget
widget.connect("enter-notify-event", self.on_slot_enter)
self.viewport.add(widget)
self._current_results = results
if not results:
@@ -234,15 +257,19 @@ class Spotlight(Box):
return
self._auto_start_refresh_timer(results)
for result in results:
widget = self._render_result(result)
self._result_widgets[result.id] = widget
self.viewport.add(widget)
widget.connect("enter-notify-event", self.on_slot_enter)
self._post_arrange()
def _remove_result_widget(self, result_id: str):
widget = self._result_widgets.pop(result_id, None)
if not widget:
return
try:
widget.disconnect_by_func(self.on_slot_enter)
except (TypeError, ValueError):
pass
self.viewport.remove(widget)
widget.destroy()
# Rendering
def _render_result(self, result: SearchResult) -> Button:
rt = result.render_type
@@ -298,7 +325,7 @@ class Spotlight(Box):
style_classes="app-slot",
child=Box(orientation="h", spacing=12, children=children),
tooltip_text=r.subtitle,
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_calc(self, r: SearchResult) -> Button:
@@ -330,7 +357,7 @@ class Spotlight(Box):
),
],
),
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_emoji(self, r: SearchResult) -> Button:
@@ -368,7 +395,7 @@ class Spotlight(Box):
],
),
tooltip_text=f"Click to copy {emoji_char}",
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_power(self, r: SearchResult) -> Button:
@@ -399,7 +426,7 @@ class Spotlight(Box):
],
),
tooltip_text=f"Click to {r.title.lower()} the system",
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_wallpaper(self, r: SearchResult) -> Button:
@@ -415,7 +442,7 @@ class Spotlight(Box):
return Button(
style_classes="app-slot wallpaper",
child=child_widget,
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_clipboard_text(self, r: SearchResult) -> Button:
@@ -436,7 +463,7 @@ class Spotlight(Box):
],
),
tooltip_text=r.subtitle,
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_clipboard_image(self, r: SearchResult) -> Button:
@@ -465,7 +492,7 @@ class Spotlight(Box):
return Button(
style_classes="app-slot wallpaper clipboard-image",
child=child_widget,
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _render_default(self, r: SearchResult) -> Button:
@@ -497,7 +524,7 @@ class Spotlight(Box):
return Button(
style_classes="app-slot",
child=Box(orientation="h", spacing=12, children=children),
on_clicked=lambda *_: self._activate_result(r),
on_clicked=lambda *_: self._activate_result_by_id(r.id),
)
def _activate_result(self, result: SearchResult):
@@ -505,19 +532,23 @@ class Spotlight(Box):
result.action()
self.launched()
def _activate_result_by_id(self, result_id: str):
for r in self._current_results:
if r.id == result_id:
self._activate_result(r)
return
# Viewport management
def _clear_viewport(self):
old_results = self._current_results
self._current_results = []
self._result_widgets.clear()
for child in self.viewport.children:
for child in list(self.viewport.children):
try:
child.disconnect_by_func(self.on_slot_enter)
except (TypeError, ValueError):
pass
self.viewport.remove(child)
child.destroy()
del old_results
self._result_widgets.clear()
def update_result_subtitle(self, result_id: str, text: str) -> None:
"""Update subtitle label of a rendered result in-place (no re-render)."""
@@ -528,9 +559,6 @@ class Spotlight(Box):
if not child:
return
# Walk: Button → main Box → title_box (last Box) → subtitle Label (2nd)
from fabric.widgets.box import Box
from fabric.widgets.label import Label
boxes = [c for c in child.get_children() if isinstance(c, Box)]
if not boxes:
return
@@ -551,12 +579,6 @@ class Spotlight(Box):
self.scrolled_window.show()
self.scrolled_window.animate_size(new_height)
for i, slot in enumerate(self.viewport.children, start=1):
if i > 8:
break
slot.set_style(f"animation-duration: {round(i * 300)}ms;")
slot.add_style_class("shine")
if self.viewport.children:
self.set_selected_index(0)
@@ -660,15 +682,17 @@ class SpotlightWindow(Window):
def close_spotlight(self):
self.spotlight_box.plugin_service.pipeline.cancel()
self.spotlight_box._stop_refresh_timer()
self.spotlight_box.header_entry.set_text("")
debounce_id = getattr(
self.spotlight_box, "_debounce_timer_on_entry_changed", None
)
if debounce_id:
GLib.source_remove(debounce_id)
self.spotlight_box._debounce_timer_on_entry_changed = 0
self.spotlight_box._stop_refresh_timer()
self.spotlight_box.header_entry.set_text("")
self.spotlight_box._clear_viewport()
self.spotlight_box.scrolled_window.animate_size(0)
self.spotlight_box.scrolled_window.hide()
@@ -676,7 +700,6 @@ class SpotlightWindow(Window):
self.spotlight_box.header_icon.set_from_icon_name("system-search-symbolic")
self.spotlight_box._deactivate_keyword_plugin()
self.spotlight_box.plugin_service.manager.release_memory_all()
gc.collect()
self.hide()
def toggle(
+16 -1
View File
@@ -1,3 +1,4 @@
import time
from typing import Any
from fabric.utils import DesktopApp, get_desktop_applications
@@ -17,6 +18,8 @@ class ApplicationPlugin(SpotlightPlugin):
def __init__(self, context):
super().__init__(context)
self._desktop_apps: list[DesktopApp] = []
self._apps_loaded_at: float = 0.0
self._apps_ttl: float = 1.0
def initialize(self) -> None:
self._desktop_apps = get_desktop_applications()
@@ -28,6 +31,16 @@ class ApplicationPlugin(SpotlightPlugin):
if not query:
return []
# Re-scan (throttled) so newly installed .desktop files appear
# without a restart, while avoiding a disk scan on every keystroke.
now = time.monotonic()
if now - self._apps_loaded_at > self._apps_ttl:
try:
self._desktop_apps = get_desktop_applications()
self._apps_loaded_at = now
except Exception:
pass
results: list[SearchResult] = []
for app in self._desktop_apps:
display_name = getattr(app, "display_name", "") or ""
@@ -60,7 +73,9 @@ class ApplicationPlugin(SpotlightPlugin):
@staticmethod
def _launch_app(app: DesktopApp) -> None:
app.launch()
from globalmenu.launch import launch_desktop_app
launch_desktop_app(app)
PLUGIN = ApplicationPlugin
+6 -1
View File
@@ -13,6 +13,7 @@ class ClipboardPlugin(SpotlightPlugin):
id = "clipboard"
name = "Clipboard"
icon = "edit-paste-symbolic"
keywords = ["clip", "clipboard"]
searchable = True
priority = 80
@@ -45,7 +46,11 @@ class ClipboardPlugin(SpotlightPlugin):
def search(self, query: str, token: Any) -> list[SearchResult]:
q = query.strip()
if q.lower().startswith("clip "):
if q.lower().startswith("clipboard "):
q = q[10:].strip()
elif q.lower() == "clipboard":
q = ""
elif q.lower().startswith("clip "):
q = q[5:].strip()
elif q.lower() == "clip":
q = ""
-1
View File
@@ -36,7 +36,6 @@ class EmojiPlugin(SpotlightPlugin):
return
cldr = self._load_cldr_annotations()
self._emoji_data = cldr or {}
logger.info(f"[Emoji] Loaded {len(self._emoji_data)} emojis")
def detect(self, text: str) -> bool:
s = text.strip()
+1 -3
View File
@@ -1,4 +1,3 @@
import gc
import os
import random
from typing import Any
@@ -8,7 +7,7 @@ from shared.data import WALLPAPERS_THUMBNAILS_SIZE
from utils.functions import fuzzy_filter
from window.spotlight.api import SearchResult, SpotlightPlugin
MAX_THUMBS_PER_SEARCH = 5
MAX_THUMBS_PER_SEARCH = 3
class WallpaperPlugin(SpotlightPlugin):
@@ -28,7 +27,6 @@ class WallpaperPlugin(SpotlightPlugin):
def cleanup(self) -> None:
self._wallpaper_service = None
gc.collect()
def release_memory(self) -> None:
if self._wallpaper_service:
-7
View File
@@ -612,13 +612,6 @@ class ApplicationSwitcher(Window):
if addr in self._addr_to_idx:
self._capture.capture_by_handle(addr, 300, 168, False)
def _refill_and_pump(self):
# Called when the switcher becomes visible or windows change
if not self.get_visible():
return False
self._pump_capture_queue()
return False
def grab_keyboard(self):
try:
display = Gdk.Display.get_default()