diff --git a/example_cliphist.py b/example_cliphist.py
deleted file mode 100644
index 8371974..0000000
--- a/example_cliphist.py
+++ /dev/null
@@ -1,505 +0,0 @@
-import os
-import re
-import subprocess
-import sys
-import tempfile
-
-from fabric.utils import idle_add, remove_handler
-from fabric.utils.helpers import get_relative_path
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.image import Image
-from fabric.widgets.label import Label
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from gi.repository import Gdk, GdkPixbuf, GLib
-
-import modules.icons as icons
-
-
-class ClipHistory(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="clip-history",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.tmp_dir = tempfile.mkdtemp(prefix="cliphist-")
- self.image_cache = {}
-
- self.notch = kwargs["notch"]
- self.selected_index = -1
- self._arranger_handler = 0
- self.clipboard_items = []
- self._loading = False
- self._pending_updates = False
-
- self.viewport = Box(name="viewport", spacing=4, orientation="v")
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Clipboard History...",
- h_expand=True,
- h_align="fill",
- notify_text=self.filter_items,
- on_activate=lambda entry, *_: self.use_selected_item(),
- on_key_press_event=self.on_search_entry_key_press,
- )
- self.search_entry.props.xalign = 0.5
-
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- h_expand=True,
- v_expand=True,
- h_align="fill",
- v_align="fill",
- child=self.viewport,
- propagate_width=False,
- propagate_height=False,
- )
-
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- Button(
- name="clear-button",
- child=Label(name="clear-label", markup=icons.trash),
- on_clicked=lambda *_: self.clear_history(),
- ),
- self.search_entry,
- Button(
- name="close-button",
- child=Label(name="close-label", markup=icons.cancel),
- tooltip_text="Exit",
- on_clicked=lambda *_: self.close()
- ),
- ],
- )
-
- self.history_box = Box(
- name="launcher-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.scrolled_window,
- ],
- )
-
- self.add(self.history_box)
- self.show_all()
-
- def close(self):
- """Close the clipboard history panel"""
- self.viewport.children = []
- self.selected_index = -1
- self.notch.close_notch()
-
- def open(self):
- """Open the clipboard history panel and load items"""
- if self._loading:
- return
- self._loading = True
- self.search_entry.set_text("")
- self.search_entry.grab_focus()
-
- GLib.idle_add(self._load_clipboard_items_thread)
-
- def _load_clipboard_items_thread(self):
- """Worker for loading clipboard items, now runs in main loop via idle_add"""
- try:
- result = subprocess.run(
- ["cliphist", "list"],
- capture_output=True,
- check=True
- )
- # Decode stdout with error handling
- stdout_str = result.stdout.decode('utf-8', errors='replace')
- lines = stdout_str.strip().split('\n')
- new_items = []
- for line in lines:
- if not line or " 1 else "0"
- content = parts[1] if len(parts) > 1 else item
-
-
- display_text = content.strip()
- if len(display_text) > 100:
- display_text = display_text[:97] + "..."
-
-
- is_image = self.is_image_data(content)
-
- if is_image:
-
- button = Button(
- name="slot-button",
- child=Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Image(name="clip-icon", h_align="start"),
- Label(
- name="clip-label",
- label="[Image]",
- ellipsization="end",
- v_align="center",
- h_align="start",
- h_expand=True,
- ),
- ],
- ),
- tooltip_text="Image in clipboard",
- on_clicked=lambda *_, id=item_id: self.paste_item(id),
- )
-
- self._load_image_preview_async(item_id, button)
- else:
-
- button = self.create_text_item_button(item_id, display_text)
-
-
- button.connect("key-press-event", lambda widget, event, id=item_id: self.on_item_key_press(widget, event, id))
-
-
- button.set_can_focus(True)
- button.add_events(Gdk.EventMask.KEY_PRESS_MASK)
-
- return button
-
- def _load_image_preview_async(self, item_id, button):
- """Load image preview using GLib.idle_add instead of threads"""
- def load_image():
- try:
- if item_id in self.image_cache:
- pixbuf = self.image_cache[item_id]
- else:
- result = subprocess.run(
- ["cliphist", "decode", item_id],
- capture_output=True,
- check=True
- )
- loader = GdkPixbuf.PixbufLoader()
- loader.write(result.stdout)
- loader.close()
- pixbuf = loader.get_pixbuf()
- width, height = pixbuf.get_width(), pixbuf.get_height()
- max_size = 72
- if width > height:
- new_width = max_size
- new_height = int(height * (max_size / width))
- else:
- new_height = max_size
- new_width = int(width * (max_size / height))
- pixbuf = pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
- self.image_cache[item_id] = pixbuf
- self._update_image_button(button, pixbuf)
- except Exception as e:
- print(f"Error loading image preview: {e}", file=sys.stderr)
- return False
- GLib.idle_add(load_image)
-
- def _update_image_button(self, button, pixbuf):
- """Update the button with the loaded image preview"""
- box = button.get_child()
- if box and len(box.get_children()) > 0:
- image_widget = box.get_children()[0]
- if isinstance(image_widget, Image):
- image_widget.set_from_pixbuf(pixbuf)
-
- def create_text_item_button(self, item_id, display_text):
- """Create a button for a text clipboard item"""
- return Button(
- name="slot-button",
- child=Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Label(
- name="clip-icon",
- markup=icons.clip_text,
- h_align="start",
- ),
- Label(
- name="clip-label",
- label=display_text,
- ellipsization="end",
- v_align="center",
- h_align="start",
- h_expand=True,
- ),
- ],
- ),
- tooltip_text=display_text,
- on_clicked=lambda *_: self.paste_item(item_id),
- )
-
- def is_image_data(self, content):
- """Determine if clipboard content is likely an image"""
-
- return (
- content.startswith("data:image/") or
- content.startswith("\x89PNG") or
- content.startswith("GIF8") or
- content.startswith("\xff\xd8\xff") or
- re.match(r'^\s*
visible_bottom:
-
- new_value = y + height - page_size
- adj.set_value(new_value)
- return False
- GLib.idle_add(scroll)
-
- def use_selected_item(self):
- """Use (paste) the selected clipboard item"""
- children = self.viewport.get_children()
- if not children or self.selected_index == -1 or self.selected_index >= len(self.clipboard_items):
- return
-
-
- item_line = self.clipboard_items[self.selected_index]
- item_id = item_line.split('\t', 1)[0]
- self.paste_item(item_id)
-
- def delete_selected_item(self):
- """Delete the selected clipboard item"""
- children = self.viewport.get_children()
- if not children or self.selected_index == -1:
- return
-
-
- item_line = self.clipboard_items[self.selected_index]
- item_id = item_line.split('\t', 1)[0]
- self.delete_item(item_id)
-
- def on_item_key_press(self, widget, event, item_id):
- """Handle key press events on clipboard items"""
- if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
-
- self.paste_item(item_id)
- return True
- return False
-
- def __del__(self):
- """Clean up temporary files on destruction"""
- try:
- if hasattr(self, 'tmp_dir') and os.path.exists(self.tmp_dir):
- import shutil
- shutil.rmtree(self.tmp_dir)
- self.image_cache.clear()
- except Exception as e:
- print(f"Error cleaning up temporary files: {e}", file=sys.stderr)
diff --git a/test_clipboard_search.py b/test_clipboard_search.py
deleted file mode 100644
index ba1ac23..0000000
--- a/test_clipboard_search.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test script to verify clipboard search improvements.
-"""
-
-import sys
-import os
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-def test_clipboard_search():
- """Test clipboard search functionality."""
- try:
- from modules.launcher.plugins.clipboard import ClipboardPlugin
-
- print("Testing clipboard search improvements...")
-
- # Create plugin instance
- plugin = ClipboardPlugin()
- plugin.initialize()
-
- print("ā
Plugin initialized successfully")
-
- # Test empty query (should show all items up to max_results)
- print("\nš Testing empty query...")
- results = plugin.query("")
- print(f"Found {len(results)} results for empty query")
-
- # Test specific search
- print("\nš Testing search query 'test'...")
- results = plugin.query("test")
- print(f"Found {len(results)} results for 'test' query")
-
- # Show relevance scores
- if results:
- print("Relevance scores:")
- for i, result in enumerate(results[:5]): # Show first 5
- print(f" {i+1}. {result.relevance:.2f} - {result.title[:50]}...")
-
- # Test image handling
- print("\nš¼ļø Testing image handling...")
- image_results = [r for r in results if "Image from clipboard" in r.title]
- if image_results:
- print(f"Found {len(image_results)} image results")
- for img_result in image_results[:3]: # Show first 3
- has_icon = hasattr(img_result, 'icon') and img_result.icon is not None
- print(f" - {img_result.subtitle} (Icon loaded: {has_icon})")
- else:
- print("No image results found (normal if no images in clipboard)")
-
- print("\nā
Clipboard search test completed!")
- print("The plugin now:")
- print(" - Searches through ALL clipboard items (not just first 20)")
- print(" - Sorts results by relevance (exact matches first)")
- print(" - Shows up to 50 results (increased from 20)")
- print(" - Limits results only after filtering and sorting")
- print(" - FOREVER image caching (like example_cliphist.py)")
- print(" - Better image detection (supports more formats)")
- print(" - Immediate image loading when cached")
- print(" - No cache expiration - images stay cached until restart")
-
- plugin.cleanup()
-
- except Exception as e:
- print(f"ā Error testing clipboard search: {e}")
- import traceback
- traceback.print_exc()
-
-if __name__ == "__main__":
- test_clipboard_search()
diff --git a/test_image_cache.py b/test_image_cache.py
deleted file mode 100644
index a7b7a97..0000000
--- a/test_image_cache.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test script to verify image caching works forever like example_cliphist.py.
-"""
-
-import sys
-import os
-import time
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-def test_image_cache():
- """Test image caching functionality."""
- try:
- from modules.launcher.plugins.clipboard import ClipboardPlugin
-
- print("Testing image cache forever functionality...")
-
- # Create plugin instance
- plugin = ClipboardPlugin()
- plugin.initialize()
-
- print("ā
Plugin initialized successfully")
-
- # Test image caching
- print("\nš¼ļø Testing image cache...")
-
- # First query - should load images and cache them
- print("First query (loading images)...")
- results1 = plugin.query("")
- image_results1 = [r for r in results1 if "Image from clipboard" in r.title]
-
- if image_results1:
- print(f"Found {len(image_results1)} image results")
-
- # Check cache size
- cache_size_before = len(plugin.image_cache)
- print(f"Image cache size: {cache_size_before}")
-
- # Wait a moment
- time.sleep(1)
-
- # Second query - should use cached images
- print("\nSecond query (should use cache)...")
- results2 = plugin.query("")
- image_results2 = [r for r in results2 if "Image from clipboard" in r.title]
-
- cache_size_after = len(plugin.image_cache)
- print(f"Image cache size after second query: {cache_size_after}")
-
- # Check if images have icons (meaning they're loaded)
- cached_images = 0
- for img_result in image_results2:
- if hasattr(img_result, 'icon') and img_result.icon is not None:
- cached_images += 1
-
- print(f"Images with loaded icons: {cached_images}/{len(image_results2)}")
-
- if cached_images > 0:
- print("ā
Images are being cached and reused!")
- else:
- print("ā ļø No cached images found")
-
- else:
- print("No image results found (normal if no images in clipboard)")
-
- print("\nā
Image cache test completed!")
- print("Cache behavior:")
- print(" - Images are cached forever (no expiration)")
- print(" - Cache survives multiple queries")
- print(" - Images load immediately when cached")
- print(" - Cache only clears on plugin restart")
-
- plugin.cleanup()
-
- except Exception as e:
- print(f"ā Error testing image cache: {e}")
- import traceback
- traceback.print_exc()
-
-if __name__ == "__main__":
- test_image_cache()
diff --git a/test_mouse_navigation.py b/test_mouse_navigation.py
deleted file mode 100644
index e83bbf2..0000000
--- a/test_mouse_navigation.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test script to verify mouse navigation works in the launcher.
-"""
-
-import sys
-import os
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-from modules.launcher.main import Launcher
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, GLib
-
-def test_mouse_navigation():
- """Test mouse navigation functionality."""
- print("Testing mouse navigation in launcher...")
-
- # Create launcher instance
- launcher = Launcher()
-
- # Show launcher with a trigger to get some results
- launcher.show_launcher("app")
-
- print("Launcher opened with 'app' trigger")
- print("You should be able to:")
- print("1. Hover over result items to see selection change (without scrolling)")
- print("2. Click on result items to activate them")
- print("3. Use keyboard navigation (arrow keys) which should scroll")
- print("Press Ctrl+C to exit test")
-
- try:
- Gtk.main()
- except KeyboardInterrupt:
- print("\nTest completed!")
- launcher.close_launcher()
-
-if __name__ == "__main__":
- test_mouse_navigation()