🔧 chore(dock): update config and dock.json, add pinned apps to dock instance, connect drag signals

This commit is contained in:
sankalp committed 2025-06-11 15:38:29 +05:45
1 parent a3c423c8b8
commit d1c2f65297
6 files changed
+225 -1155

No files matched your search

+2 -2
View File
@@ -36,8 +36,8 @@
"prefix_restart_inspector": "SUPER CTRL ALT",
"suffix_restart_inspector": "B",
"wallpapers_dir": "/home/sankalp/Pictures/wallpapers",
"dock_position": "Bottom",
"vertical": false,
"dock_position": "Left",
"vertical": true,
"dock_enabled": true,
"dock_auto_hide": true,
"dock_always_occluded": false,
+16 -1
View File
@@ -1,3 +1,18 @@
{
"pinned_apps": []
"pinned_apps": [
{
"name": "kitty",
"display_name": "kitty",
"window_class": null,
"executable": "kitty",
"command_line": "kitty"
},
{
"name": "Visual Studio Code",
"display_name": "Visual Studio Code",
"window_class": "Code",
"executable": "/usr/bin/code",
"command_line": "/usr/bin/code %F"
}
]
}
-1040
View File
File diff suppressed because it is too large. Load diff
+144 -104
View File
@@ -11,6 +11,7 @@ from gi.repository import Gdk, GLib, Gtk
import config.data as data
from utils.icon_resolver import IconResolver
def read_config():
"""Read and return the full configuration from the JSON file, handling missing file."""
config_path = get_relative_path("../../config/dock.json")
@@ -47,15 +48,31 @@ def read_config():
config_data = {"pinned_apps": []}
return config_data
def createSurfaceFromWidget(widget: Gtk.Widget) -> cairo.ImageSurface:
alloc = widget.get_allocation()
surface = cairo.ImageSurface(
cairo.Format.ARGB32,
alloc.width,
alloc.height,
)
cr = cairo.Context(surface)
cr.set_source_rgba(255, 255, 255, 0)
cr.rectangle(0, 0, alloc.width, alloc.height)
cr.fill()
widget.draw(cr)
return surface
class Applications(Box):
def __init__(self, orientation_val="h", **kwargs):
def __init__(self, orientation_val="h", dock_instance=None, **kwargs):
super().__init__(
name="applications-dock",
orientation=orientation_val,
spacing=4,
**kwargs
name="applications-dock", orientation=orientation_val, spacing=4, **kwargs
)
# Store reference to the dock instance
self.dock_instance = dock_instance
self.config = read_config()
self.conn = get_hyprland_connection()
self.icon_resolver = IconResolver()
@@ -65,10 +82,10 @@ class Applications(Box):
self._all_apps = get_desktop_applications()
self.app_identifiers = self._build_app_identifiers_map()
self._drag_in_progress = False
# Initialize with empty content
self.update_applications()
# Connect to Hyprland events for live updates
if self.conn.ready:
self.update_applications()
@@ -78,10 +95,10 @@ class Applications(Box):
# Connect to window events to update in real-time
for ev in ("activewindow", "openwindow", "closewindow", "changefloatingmode"):
self.conn.connect(f"event::{ev}", lambda *args: self.update_applications())
# Set up periodic refresh for config changes
GLib.timeout_add_seconds(1, self.check_config_change)
def _build_app_identifiers_map(self):
app_map = {}
for app in self._all_apps:
@@ -92,25 +109,31 @@ class Applications(Box):
if app.name:
app_map[app.name.lower()] = app
return app_map
def find_app_by_key(self, key):
if not key:
return None
key_lower = key.lower() if isinstance(key, str) else ""
return self.app_identifiers.get(key_lower)
def find_app(self, app_identifier):
if not app_identifier:
return None
if isinstance(app_identifier, dict):
for key in ["window_class", "executable", "command_line", "name", "display_name"]:
for key in [
"window_class",
"executable",
"command_line",
"name",
"display_name",
]:
if key in app_identifier and app_identifier[key]:
app = self.find_app_by_key(app_identifier[key])
if app:
return app
return None
return self.find_app_by_key(app_identifier)
def create_button(self, app_identifier, instances):
desktop_app = self.find_app(app_identifier)
icon_img = None
@@ -165,23 +188,23 @@ class Applications(Box):
[Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
Gdk.DragAction.MOVE,
)
# Add drag destination capabilities
button.drag_dest_set(
Gtk.DestDefaults.ALL,
[Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
Gdk.DragAction.MOVE,
)
# Connect drag signals
button.connect("drag-begin", self.on_drag_begin)
button.connect("drag-data-get", self.on_drag_data_get)
button.connect("drag-data-received", self.on_drag_data_received)
button.connect("drag-end", self.on_drag_end)
button.connect("button-press-event", self.on_button_press)
return button
def handle_app(self, app_identifier, instances, desktop_app=None):
if not instances:
if not desktop_app:
@@ -215,7 +238,7 @@ class Applications(Box):
address = instances[0].get("address")
if address:
exec_shell_command(f"hyprctl dispatch focuswindow address:{address}")
def update_pinned_apps_file(self):
config_path = get_relative_path("../../config/dock.json")
try:
@@ -225,7 +248,7 @@ class Applications(Box):
except Exception as e:
logging.error(f"Failed to write dock config: {e}")
return False
def check_config_change(self):
new_config = read_config()
if new_config.get("pinned_apps", []) != self.config.get("pinned_apps", []):
@@ -234,20 +257,20 @@ class Applications(Box):
self.update_app_map()
self.update_applications()
return True
def update_app_map(self):
self._all_apps = get_desktop_applications()
self.app_identifiers = self._build_app_identifiers_map()
def _normalize_window_class(self, class_name):
# Implementation from dock.py
return class_name.lower()
def update_applications(self):
# Clear existing children
for child in self.get_children():
self.remove(child)
# Get running windows from Hyprland
clients = []
try:
@@ -256,7 +279,7 @@ class Applications(Box):
clients = json.loads(clients_data.decode("utf-8"))
except Exception as e:
logging.error(f"Failed to get clients: {e}")
# Group clients by window class
running_windows = {}
for client in clients:
@@ -265,11 +288,11 @@ class Applications(Box):
if class_name not in running_windows:
running_windows[class_name] = []
running_windows[class_name].append(client)
# Create pinned app buttons
pinned_buttons = []
used_window_classes = set()
for app_data_item in self.pinned:
app = self.find_app(app_data_item)
instances = []
@@ -277,12 +300,18 @@ class Applications(Box):
possible_identifiers = []
if isinstance(app_data_item, dict):
for key in ["window_class", "executable", "command_line", "name", "display_name"]:
for key in [
"window_class",
"executable",
"command_line",
"name",
"display_name",
]:
if key in app_data_item and app_data_item[key]:
possible_identifiers.append(app_data_item[key].lower())
elif isinstance(app_data_item, str):
possible_identifiers.append(app_data_item.lower())
# Try to match with running windows
for class_name, window_instances in running_windows.items():
for identifier in possible_identifiers:
@@ -292,18 +321,18 @@ class Applications(Box):
break
if matched_class:
break
if matched_class:
used_window_classes.add(matched_class)
pinned_buttons.append(self.create_button(app_data_item, instances))
# Create buttons for non-pinned running apps
open_buttons = []
for class_name, instances in running_windows.items():
if class_name not in used_window_classes:
app = self.find_app_by_key(class_name)
if app:
app_data_obj = {
"name": app.name,
@@ -315,19 +344,19 @@ class Applications(Box):
identifier = app_data_obj
else:
identifier = class_name
open_buttons.append(self.create_button(identifier, instances))
# Add all buttons to the dock
children = pinned_buttons
if pinned_buttons and open_buttons:
separator_orientation = (
Gtk.Orientation.VERTICAL
if self.get_orientation() == Gtk.Orientation.HORIZONTAL
else Gtk.Orientation.HORIZONTAL
)
children.append(
Box(
orientation=separator_orientation,
@@ -338,19 +367,19 @@ class Applications(Box):
name="dock-separator",
)
)
children.extend(open_buttons)
# Add all children to the dock
for child in children:
self.add(child)
# Show or hide based on whether there are any applications
if len(children) > 0:
self.show_all()
else:
self.hide()
return True
def on_button_press(self, widget, event):
@@ -358,18 +387,21 @@ class Applications(Box):
if event.button == 3: # Right mouse button
app_identifier = widget.app_identifier
instances = widget.instances
# Check if app is already pinned
is_pinned = False
for pinned_app in self.pinned:
if (isinstance(app_identifier, dict) and isinstance(pinned_app, dict) and
app_identifier.get("name") == pinned_app.get("name")):
if (
isinstance(app_identifier, dict)
and isinstance(pinned_app, dict)
and app_identifier.get("name") == pinned_app.get("name")
):
is_pinned = True
break
elif app_identifier == pinned_app:
is_pinned = True
break
# If not pinned, add to pinned apps
if not is_pinned:
if hasattr(widget, "desktop_app") and widget.desktop_app:
@@ -384,7 +416,7 @@ class Applications(Box):
self.pinned.append(app_data_obj)
else:
self.pinned.append(app_identifier)
self.config["pinned_apps"] = self.pinned
self.update_pinned_apps_file()
self.update_applications()
@@ -393,17 +425,11 @@ class Applications(Box):
def on_drag_begin(self, widget, drag_context):
self._drag_in_progress = True
Gtk.drag_set_icon_surface(drag_context, self.create_surface_from_widget(widget))
def create_surface_from_widget(self, widget):
# Create a surface from the widget for drag icon
allocation = widget.get_allocation()
surface = cairo.ImageSurface(
cairo.FORMAT_ARGB32, allocation.width, allocation.height
)
ctx = cairo.Context(surface)
widget.draw(ctx)
return surface
Gtk.drag_set_icon_surface(drag_context, createSurfaceFromWidget(widget))
# Prevent dock from hiding during drag
if self.dock_instance:
self.dock_instance.prevent_hiding(True)
def on_drag_data_get(self, widget, drag_context, data_obj, info, time):
# Find the index of the button being dragged
@@ -417,61 +443,75 @@ class Applications(Box):
source_index = int(data_obj.get_text())
except (TypeError, ValueError):
return
children = self.get_children()
target_index = children.index(widget)
if source_index != target_index:
# Reorder the pinned apps
if source_index < len(self.pinned) and target_index < len(self.pinned):
item = self.pinned.pop(source_index)
self.pinned.insert(target_index, item)
self.config["pinned_apps"] = self.pinned
self.update_pinned_apps_file()
self.update_applications()
def on_drag_end(self, widget, drag_context):
if not hasattr(self, "_drag_in_progress") or not self._drag_in_progress:
return
# Get pointer position
display = widget.get_display()
seat = display.get_default_seat()
pointer = seat.get_pointer()
screen, x, y = pointer.get_position()
# Get widget position and size
widget_alloc = widget.get_allocation()
widget_x, widget_y = widget.translate_coordinates(self, 0, 0)
if widget_x is None: # Widget might not be in the same window
widget_x, widget_y = 0, 0
widget_width, widget_height = widget_alloc.width, widget_alloc.height
# Check if pointer is outside the dock area
if not (widget_x <= x <= widget_x + widget_width and
widget_y <= y <= widget_y + widget_height):
app_id_dragged = widget.app_identifier
instances_dragged = widget.instances
# Remove pinned app if it's in the pinned list
app_index_dragged = -1
for i, pinned_app_item in enumerate(self.pinned):
if isinstance(app_id_dragged, dict) and isinstance(pinned_app_item, dict):
if app_id_dragged.get("name") == pinned_app_item.get("name"):
app_index_dragged = i
break
elif app_id_dragged == pinned_app_item:
app_index_dragged = i
if source_index != target_index:
# Find separator index to determine pinned vs unpinned sections
separator_index = -1
for i, child in enumerate(children):
if child.get_name() == "dock-separator":
separator_index = i
break
if app_index_dragged >= 0:
self.pinned.pop(app_index_dragged)
# Check if we're dragging from unpinned to pinned section
cross_section_drag = separator_index != -1 and (
(source_index > separator_index and target_index < separator_index)
)
# If dragging from unpinned to pinned, add to pinned apps
if cross_section_drag:
source_widget = children[source_index]
app_identifier = source_widget.app_identifier
# Add to pinned apps if not already pinned
if hasattr(source_widget, "desktop_app") and source_widget.desktop_app:
app = source_widget.desktop_app
app_data_obj = {
"name": app.name,
"display_name": app.display_name,
"window_class": app.window_class,
"executable": app.executable,
"command_line": app.command_line,
}
self.pinned.insert(target_index, app_data_obj)
else:
self.pinned.insert(target_index, app_identifier)
self.config["pinned_apps"] = self.pinned
self.update_pinned_apps_file()
self.update_applications()
elif instances_dragged:
address = instances_dragged[0].get("address")
if address:
exec_shell_command(f"hyprctl dispatch focuswindow address:{address}")
else:
# Regular reordering within the same section
if source_index < separator_index and target_index < separator_index:
# Reorder within pinned section
item = self.pinned.pop(source_index)
self.pinned.insert(target_index, item)
self.config["pinned_apps"] = self.pinned
self.update_pinned_apps_file()
self.update_applications()
elif source_index > separator_index and target_index > separator_index:
# Reorder within unpinned section - just reorder the widgets
# since unpinned apps aren't stored in config
child_to_move = children.pop(source_index)
children.insert(target_index, child_to_move)
# Update the UI
for child in self.get_children():
self.remove(child)
for child in children:
self.add(child)
self.show_all()
def on_drag_end(self, widget, drag_context):
if not self._drag_in_progress:
return
# Process drag end...
self._drag_in_progress = False
# Allow dock to hide again
if self.dock_instance:
self.dock_instance.prevent_hiding(False)
+29 -6
View File
@@ -14,11 +14,22 @@ from modules.dock.applications import Applications
class DockComponents(Box):
def __init__(self, orientation_val="h", **kwargs):
def __init__(self, orientation_val="h", dock_instance=None, **kwargs):
super().__init__(
name="system-tray", orientation=orientation_val, spacing=4, **kwargs
name="dock-components",
orientation=orientation_val,
spacing=8,
**kwargs
)
self.dock_instance = dock_instance
# Create applications with dock reference
self.applications = Applications(
orientation_val=orientation_val,
dock_instance=dock_instance
)
# Initialize component visibility from data
self.component_visibility = data.DOCK_COMPONENTS_VISIBILITY
@@ -45,9 +56,21 @@ class DockComponents(Box):
name="tray",
orientation="h" if not data.VERTICAL else "v",
)
self.applications = Applications(
orientation_val=orientation_val
)
# Add methods to connect drag signals
self.connect_drag_signals()
def connect_drag_signals(self):
# Method to connect drag signals from applications to parent
pass
def set_drag_callback(self, on_drag_begin_cb, on_drag_end_cb):
"""Set callbacks for drag operations from applications component"""
# Connect to applications drag signals
self.applications.connect("drag-begin",
lambda widget, context: on_drag_begin_cb())
self.applications.connect("drag-end",
lambda widget, context: on_drag_end_cb())
# Add components based on position
if data.DOCK_POSITION == "Right":
+34 -2
View File
@@ -12,7 +12,6 @@ from utils.wayland import WaylandWindow as Window
from modules.dock.components import DockComponents
class Dock(Window):
_instances = []
@@ -107,7 +106,14 @@ class Dock(Window):
# Create components using DockComponents
self.components = DockComponents(
orientation_val="h" if not data.VERTICAL else "v"
orientation_val="h" if not data.VERTICAL else "v",
dock_instance=self
)
# Connect drag signals from applications component
self.components.set_drag_callback(
on_drag_begin_cb=self.on_app_drag_begin,
on_drag_end_cb=self.on_app_drag_end
)
# Add components based on position
@@ -454,3 +460,29 @@ class Dock(Window):
and dock.dock_revealer.get_reveal_child()
):
dock.dock_revealer.set_reveal_child(False)
def on_app_drag_begin(self):
"""Called when application drag begins"""
self._drag_in_progress = True
# Ensure dock is visible during drag
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
def on_app_drag_end(self):
"""Called when application drag ends"""
self._drag_in_progress = False
# Check if we should hide the dock
GLib.idle_add(self.check_occlusion_state)
def prevent_hiding(self, prevent=True):
"""Prevent the dock from hiding"""
self._prevent_occlusion = prevent
if prevent:
# Force dock to be visible
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
else:
# Check if we should hide
GLib.idle_add(self.check_occlusion_state)