482 lines
17 KiB
Python
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)
|