Files
Modus/modules/dock/main.py
T

482 lines
17 KiB
Python

import json
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, GLib, Gtk
from modules.corners import MyCorner
from utils.occlusion import check_occlusion
from utils.wayland import WaylandWindow as Window
from modules.dock.components import DockComponents
class Dock(Window):
_instances = []
def __init__(self, **kwargs):
self.icon_size = 28
self.effective_occlusion_size = 36 + self.icon_size
anchor_to_set: str
revealer_transition_type: str
self.actual_dock_is_horizontal: bool
main_box_orientation_val: Gtk.Orientation
main_box_h_align_val: str
dock_wrapper_orientation_val: Gtk.Orientation
self.actual_dock_is_horizontal = not data.VERTICAL
if self.actual_dock_is_horizontal:
anchor_to_set = "bottom"
revealer_transition_type = "slide-up"
main_box_orientation_val = Gtk.Orientation.VERTICAL
main_box_h_align_val = "center"
dock_wrapper_orientation_val = Gtk.Orientation.HORIZONTAL
else:
# Use DOCK_POSITION directly instead of BAR_POSITION
if data.DOCK_POSITION == "Left":
anchor_to_set = "left"
revealer_transition_type = "slide-right"
elif data.DOCK_POSITION == "Right":
anchor_to_set = "right"
revealer_transition_type = "slide-left"
else:
anchor_to_set = "right"
revealer_transition_type = "slide-left"
main_box_orientation_val = Gtk.Orientation.HORIZONTAL
main_box_h_align_val = "end" if anchor_to_set == "right" else "start"
dock_wrapper_orientation_val = Gtk.Orientation.VERTICAL
super().__init__(
name="dock-window",
layer="top",
anchor=anchor_to_set,
margin="0px 0px 0px 0px", # Set all margins to 0
exclusivity="auto" if not data.DOCK_AUTO_HIDE else "none",
**kwargs,
)
Dock._instances.append(self)
self.conn = get_hyprland_connection()
self.hide_id = None
self._arranger_handler = None
self._drag_in_progress = False
self.always_occluded = data.DOCK_ALWAYS_OCCLUDED
self.is_mouse_over_dock_area = False
self._prevent_occlusion = False
self.view = Box(name="viewport", spacing=4)
self.wrapper = Box(
name="dock",
children=[self.view],
style_classes=["left"]
if data.DOCK_POSITION == "Left"
else ["right"]
if data.DOCK_POSITION == "Right"
else [],
)
self.wrapper.set_orientation(dock_wrapper_orientation_val)
self.view.set_orientation(dock_wrapper_orientation_val)
if dock_wrapper_orientation_val == Gtk.Orientation.VERTICAL:
self.wrapper.add_style_class("vertical")
else:
self.wrapper.remove_style_class("vertical")
match data.DOCK_THEME:
case "Pills":
self.wrapper.add_style_class("pills")
case "Dense":
self.wrapper.add_style_class("dense")
case "Edge":
self.wrapper.add_style_class("edge")
case _:
self.wrapper.add_style_class("pills")
self.dock_eventbox = EventBox()
self.dock_eventbox.add(self.wrapper)
self.dock_eventbox.connect("enter-notify-event", self._on_dock_enter)
self.dock_eventbox.connect("leave-notify-event", self._on_dock_leave)
# Create components using DockComponents
self.components = DockComponents(
orientation_val="h" if not data.VERTICAL else "v", dock_instance=self
)
# Add components based on position
if self.actual_dock_is_horizontal: # Bottom dock
self.view.add(self.components)
elif data.DOCK_POSITION == "Left":
self.view.add(self.components)
else: # Right position
self.view.add(self.components)
self.corner_left = Box()
self.corner_right = Box()
self.corner_top = Box()
self.corner_bottom = Box()
if self.actual_dock_is_horizontal:
self.corner_left = Box(
name="dock-corner-left",
orientation=Gtk.Orientation.VERTICAL,
h_align="start",
children=[Box(v_expand=True, v_align="fill"), MyCorner("bottom-right")],
)
self.corner_right = Box(
name="dock-corner-right",
orientation=Gtk.Orientation.VERTICAL,
h_align="end",
children=[Box(v_expand=True, v_align="fill"), MyCorner("bottom-left")],
)
self.dock_full = Box(
name="dock-full",
orientation=Gtk.Orientation.HORIZONTAL,
h_expand=True,
h_align="fill",
children=[self.corner_left, self.dock_eventbox, self.corner_right],
)
else:
if anchor_to_set == "right":
self.corner_top = Box(
name="dock-corner-top",
orientation=Gtk.Orientation.HORIZONTAL,
v_align="start",
children=[
Box(h_expand=True, h_align="fill"),
MyCorner("bottom-right"),
],
)
self.corner_bottom = Box(
name="dock-corner-bottom",
orientation=Gtk.Orientation.HORIZONTAL,
v_align="end",
children=[
Box(h_expand=True, h_align="fill"),
MyCorner("top-right"),
],
)
else:
self.corner_top = Box(
name="dock-corner-top",
orientation=Gtk.Orientation.HORIZONTAL,
v_align="start",
children=[
MyCorner("bottom-left"),
Box(h_expand=True, h_align="fill"),
],
)
self.corner_bottom = Box(
name="dock-corner-bottom",
orientation=Gtk.Orientation.HORIZONTAL,
v_align="end",
children=[MyCorner("top-left"), Box(h_expand=True, h_align="fill")],
)
self.dock_full = Box(
name="dock-full",
orientation=Gtk.Orientation.VERTICAL,
v_expand=True,
v_align="fill",
margin=0, # Add explicit margin=0
children=[self.corner_top, self.dock_eventbox, self.corner_bottom],
)
self.dock_revealer = Revealer(
name="dock-revealer",
transition_type=revealer_transition_type,
transition_duration=250,
child_revealed=False,
child=self.dock_full,
)
self.hover_activator = EventBox()
# Adjust hover activator size based on position and dock position
if self.actual_dock_is_horizontal:
# Bottom dock
self.hover_activator.set_size_request(-1, 1)
else:
# Vertical dock (Left or Right)
self.hover_activator.set_size_request(1, -1)
self.hover_activator.connect("enter-notify-event", self._on_hover_enter)
self.hover_activator.connect("leave-notify-event", self._on_hover_leave)
# Create main box with correct child order based on position
if self.actual_dock_is_horizontal:
# Bottom dock
self.main_box = Box(
orientation=main_box_orientation_val,
children=[self.hover_activator, self.dock_revealer],
h_align=main_box_h_align_val,
)
else:
# Vertical dock
if data.DOCK_POSITION == "Left":
# Left dock - revealer first, then hover activator
self.main_box = Box(
orientation=main_box_orientation_val,
children=[self.dock_revealer, self.hover_activator],
h_align=main_box_h_align_val,
)
else:
# Right dock - hover activator first, then revealer
self.main_box = Box(
orientation=main_box_orientation_val,
children=[self.hover_activator, self.dock_revealer],
h_align=main_box_h_align_val,
)
self.add(self.main_box)
if data.DOCK_THEME in ["Edge", "Dense"]:
for corner in [
self.corner_left,
self.corner_right,
self.corner_top,
self.corner_bottom,
]:
corner.set_visible(False)
if not data.DOCK_ENABLED:
self.set_visible(False)
if self.always_occluded:
self.dock_full.add_style_class("occluded")
if self.conn.ready:
GLib.timeout_add(250, self.check_occlusion_state)
else:
self.conn.connect(
"event::ready",
lambda *args: GLib.timeout_add(250, self.check_occlusion_state),
)
self.conn.connect("event::workspace", self.check_hide)
GLib.timeout_add_seconds(1, self.check_config_change)
def _on_hover_enter(self, *args):
self.is_mouse_over_dock_area = True
if self.hide_id:
GLib.source_remove(self.hide_id)
self.hide_id = None
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
def _on_hover_leave(self, *args):
self.is_mouse_over_dock_area = False
self.delay_hide()
def _on_dock_enter(self, widget, event):
self.is_mouse_over_dock_area = True
if self.hide_id:
GLib.source_remove(self.hide_id)
self.hide_id = None
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
return True
def _on_dock_leave(self, widget, event):
if event.detail == Gdk.NotifyType.INFERIOR:
return False
self.is_mouse_over_dock_area = False
self.delay_hide()
if self.always_occluded:
self.dock_full.add_style_class("occluded")
return True
def delay_hide(self):
if self.hide_id:
GLib.source_remove(self.hide_id)
self.hide_id = GLib.timeout_add(250, self.hide_dock_if_not_hovered)
def hide_dock_if_not_hovered(self):
self.hide_id = None
if (
not self.is_mouse_over_dock_area
and not self._drag_in_progress
and not self._prevent_occlusion
):
if self.always_occluded:
self.dock_revealer.set_reveal_child(False)
else:
occlusion_region = (
("bottom", self.effective_occlusion_size)
if self.actual_dock_is_horizontal
else ("right", self.effective_occlusion_size)
)
if check_occlusion(occlusion_region) or not self.view.get_children():
self.dock_revealer.set_reveal_child(False)
return False
def check_hide(self, *args):
if (
self.is_mouse_over_dock_area
or self._drag_in_progress
or self._prevent_occlusion
):
return
clients = self.get_clients()
current_ws = self.get_workspace()
ws_clients = [w for w in clients if w["workspace"]["id"] == current_ws]
if not self.always_occluded:
if not ws_clients:
if not self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(True)
self.dock_full.remove_style_class("occluded")
elif any(
not w.get("floating") and not w.get("fullscreen") for w in ws_clients
):
self.check_occlusion_state()
else:
if not self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(True)
self.dock_full.remove_style_class("occluded")
else:
if self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(False)
self.dock_full.add_style_class("occluded")
def get_clients(self):
try:
return json.loads(self.conn.send_command("j/clients").reply.decode())
except json.JSONDecodeError:
return []
def get_focused(self):
try:
return json.loads(
self.conn.send_command("j/activewindow").reply.decode()
).get("address", "")
except json.JSONDecodeError:
return ""
def get_workspace(self):
try:
return json.loads(
self.conn.send_command("j/activeworkspace").reply.decode()
).get("id", 0)
except json.JSONDecodeError:
return 0
def check_occlusion_state(self):
if (
self.is_mouse_over_dock_area
or self._drag_in_progress
or self._prevent_occlusion
):
if not self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
return True
if not data.DOCK_AUTO_HIDE:
if not self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
return True
if self.always_occluded:
if self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(False)
self.dock_full.add_style_class("occluded")
return True
occlusion_region = (
("bottom", self.effective_occlusion_size)
if self.actual_dock_is_horizontal
else ("right", self.effective_occlusion_size)
)
is_occluded_by_window = check_occlusion(occlusion_region)
is_empty = not self.view.get_children()
if is_occluded_by_window or is_empty:
if self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(False)
self.dock_full.add_style_class("occluded")
else:
if not self.dock_revealer.get_reveal_child():
self.dock_revealer.set_reveal_child(True)
self.dock_full.remove_style_class("occluded")
return True
def _update_size(self):
width, _ = self.view.get_preferred_width()
self.set_size_request(width, -1)
return False
def check_config_change(self):
new_always_occluded = data.DOCK_ALWAYS_OCCLUDED
if self.always_occluded != new_always_occluded:
self.always_occluded = new_always_occluded
self.check_occlusion_state()
return True
def check_config_change_immediate(self):
previous_always_occluded = self.always_occluded
self.always_occluded = data.DOCK_ALWAYS_OCCLUDED
if previous_always_occluded != self.always_occluded:
self.check_occlusion_state()
return False
@staticmethod
def notify_config_change():
for dock_instance in Dock._instances:
GLib.idle_add(dock_instance.check_config_change_immediate)
@staticmethod
def update_visibility(visible):
for dock in Dock._instances:
dock.set_visible(visible)
if visible:
GLib.idle_add(dock.check_occlusion_state)
else:
if (
hasattr(dock, "dock_revealer")
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)