✨ feat(switcher): add window previews using Glace for application switcher

This commit is contained in:
sankalp committed 2025-08-08 09:46:36 +05:45
1 parent 643d39b38b
commit 803b489dfa
2 files changed
+289 -13

No files matched your search

+124 -13
View File
@@ -1,6 +1,7 @@
import json
from gi.repository import Gdk
import gi
from gi.repository import Gdk, Glace
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
@@ -11,6 +12,8 @@ from fabric.widgets.label import Label
from utils.icon_resolver import IconResolver
from widgets.wayland import WaylandWindow as Window
gi.require_version("Glace", "0.1")
class ApplicationSwitcher(Window):
def __init__(self, **kwargs):
@@ -33,6 +36,12 @@ class ApplicationSwitcher(Window):
self.items_per_row = data.WINDOW_SWITCHER_ITEMS_PER_ROW
self.icon_size = 64
# Initialize Glace manager for window previews
self._manager = Glace.Manager()
self.preview_size = [150, 100] # Preview size for each window in switcher
self.glace_clients = {} # Map window addresses to Glace clients
self.window_previews = {} # Map window addresses to preview images
container = Box(
name="application-switcher-container",
orientation="v",
@@ -45,13 +54,18 @@ class ApplicationSwitcher(Window):
self.view = Box(
name="application-switcher-view",
orientation="v",
spacing=0,
spacing=12,
h_align="center",
v_align="center",
)
container.add(self.view)
self.connect("key-press-event", self.on_key_press)
self.connect("key-release-event", self.on_key_release)
# Connect to Glace manager signals to track clients
self._manager.connect("client-added", self._on_glace_client_added)
self._manager.connect("client-removed", self._on_glace_client_removed)
self.show_all()
self.hide()
@@ -68,6 +82,103 @@ class ApplicationSwitcher(Window):
self.hide()
self.ungrab_keyboard()
def _on_glace_client_added(self, _, client):
"""Handle when a Glace client is added"""
try:
# Map the client by its window address for later lookup
# We'll need to match this with Hyprland window data
client_id = client.get_id()
self.glace_clients[client_id] = client
except Exception as e:
print(f"Error adding Glace client: {e}")
def _on_glace_client_removed(self, _, client):
"""Handle when a Glace client is removed"""
try:
client_id = client.get_id()
if client_id in self.glace_clients:
del self.glace_clients[client_id]
except Exception as e:
print(f"Error removing Glace client: {e}")
def _find_glace_client_for_window(self, window):
"""Find the corresponding Glace client for a Hyprland window"""
try:
window_class = window.get("class", "").lower()
window_title = window.get("title", "")
# Try to match by app_id/class and title
for _, client in self.glace_clients.items():
try:
client_app_id = client.get_app_id()
client_title = client.get_title()
if (client_app_id and client_app_id.lower() == window_class and
client_title and client_title == window_title):
return client
except Exception:
continue
# Fallback: try to match by class only
for _, client in self.glace_clients.items():
try:
client_app_id = client.get_app_id()
if client_app_id and client_app_id.lower() == window_class:
return client
except Exception:
continue
except Exception as e:
print(f"Error finding Glace client: {e}")
return None
def create_preview_for_window(self, window):
"""Create a preview image for a specific window"""
glace_client = self._find_glace_client_for_window(window)
# Create a placeholder image first
preview_image = Image()
if glace_client:
def capture_callback(pbuf, _):
try:
scaled_pixbuf = pbuf.scale_simple(
self.preview_size[0],
self.preview_size[1],
2 # GdkPixbuf.InterpType.BILINEAR
)
preview_image.set_from_pixbuf(scaled_pixbuf)
except Exception as e:
print(f"Error setting preview image: {e}")
try:
self._manager.capture_client(
client=glace_client,
overlay_cursor=False,
callback=capture_callback,
user_data=None,
)
except Exception as e:
print(f"Error capturing client preview: {e}")
# Fallback to icon if preview fails
self._set_fallback_icon(preview_image, window)
else:
# Use icon as fallback if no Glace client found
self._set_fallback_icon(preview_image, window)
return preview_image
def _set_fallback_icon(self, image_widget, window):
"""Set a fallback icon when preview is not available"""
class_name = window.get("class", "").lower()
icon_img = self.icon_resolver.get_icon_pixbuf(class_name, self.icon_size)
if not icon_img:
icon_img = self.icon_resolver.get_icon_pixbuf(
"application-x-executable-symbolic", self.icon_size
)
image_widget.set_from_pixbuf(icon_img)
def _is_special_workspace(self, client):
"""Check if a client is in a special workspace"""
if "workspace" not in client:
@@ -135,16 +246,10 @@ class ApplicationSwitcher(Window):
self.view.add(current_row)
for i, window in enumerate(self.windows):
class_name = window.get("class", "").lower()
title = window.get("title", "")
icon_img = self.icon_resolver.get_icon_pixbuf(
class_name, self.icon_size
)
if not icon_img:
icon_img = self.icon_resolver.get_icon_pixbuf(
"application-x-executable-symbolic", self.icon_size
)
# Create preview image for this window
preview_image = self.create_preview_for_window(window)
button_content = Box(
name="switcher-button",
@@ -153,7 +258,13 @@ class ApplicationSwitcher(Window):
h_align="center",
v_align="center",
children=[
Image(pixbuf=icon_img),
Box(
name="switcher-preview-box",
style_classes=["window-basic", "sleek-border"],
children=[preview_image],
h_align="center",
v_align="center",
),
Label(
label=title[:15] + "..." if len(title) > 15 else title,
h_align="center",
@@ -186,7 +297,7 @@ class ApplicationSwitcher(Window):
except Exception as e:
print(f"Failed to update windows: {e}")
def on_key_press(self, widget, event):
def on_key_press(self, _, event):
keyval = event.keyval
state = event.state
alt_pressed = bool(state & Gdk.ModifierType.MOD1_MASK)
@@ -243,7 +354,7 @@ class ApplicationSwitcher(Window):
return False
def on_key_release(self, widget, event):
def on_key_release(self, _, event):
keyval = event.keyval
if keyval in (Gdk.KEY_Alt_L, Gdk.KEY_Alt_R):