new
This commit is contained in:
105 files changed
+6137
-27999
No files matched your search
+4
-4
@@ -1,4 +1,4 @@
|
||||
from .custom_image import *
|
||||
from .icon_resolver import *
|
||||
from .occlusion import *
|
||||
from .animator import *
|
||||
"""
|
||||
Modus services package.
|
||||
Contains background services and utilities for the shell.
|
||||
"""
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import cast
|
||||
|
||||
import fabric
|
||||
from fabric import Property, Service, Signal
|
||||
from gi.repository import GLib, Gtk
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import math
|
||||
from typing import Literal
|
||||
|
||||
import cairo
|
||||
import gi
|
||||
from fabric.core.service import Property
|
||||
from fabric.widgets.widget import Widget
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gdk, GdkPixbuf, Gtk # noqa: E402
|
||||
|
||||
|
||||
class CircleImage(Gtk.DrawingArea, Widget):
|
||||
"""A widget that displays an image in a circular shape with a 1:1 aspect ratio."""
|
||||
|
||||
@Property(int, "read-write")
|
||||
def angle(self) -> int:
|
||||
return self._angle
|
||||
|
||||
@angle.setter
|
||||
def angle(self, value: int):
|
||||
self._angle = value % 360
|
||||
self.queue_draw()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_file: str | None = None,
|
||||
pixbuf: GdkPixbuf.Pixbuf | None = None,
|
||||
name: str | None = None,
|
||||
visible: bool = True,
|
||||
all_visible: bool = False,
|
||||
style: str | None = None,
|
||||
tooltip_text: str | None = None,
|
||||
tooltip_markup: str | None = None,
|
||||
h_align: Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None = None,
|
||||
v_align: Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None = None,
|
||||
h_expand: bool = False,
|
||||
v_expand: bool = False,
|
||||
size: int | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
Gtk.DrawingArea.__init__(self)
|
||||
Widget.__init__(
|
||||
self,
|
||||
name=name,
|
||||
visible=visible,
|
||||
all_visible=all_visible,
|
||||
style=style,
|
||||
tooltip_text=tooltip_text,
|
||||
tooltip_markup=tooltip_markup,
|
||||
h_align=h_align,
|
||||
v_align=v_align,
|
||||
h_expand=h_expand,
|
||||
v_expand=v_expand,
|
||||
size=size,
|
||||
**kwargs,
|
||||
)
|
||||
self.size = size if size is not None else 100 # Default size if not provided
|
||||
self._angle = 0
|
||||
self._orig_image: GdkPixbuf.Pixbuf | None = None # Original image for reprocessing
|
||||
self._image: GdkPixbuf.Pixbuf | None = None
|
||||
if image_file:
|
||||
pix = GdkPixbuf.Pixbuf.new_from_file(image_file)
|
||||
self._orig_image = pix
|
||||
self._image = self._process_image(pix)
|
||||
elif pixbuf:
|
||||
self._orig_image = pixbuf
|
||||
self._image = self._process_image(pixbuf)
|
||||
self.connect("draw", self.on_draw)
|
||||
|
||||
def _process_image(self, pixbuf: GdkPixbuf.Pixbuf) -> GdkPixbuf.Pixbuf:
|
||||
"""Crop the image to a centered square and scale it to the widget’s size."""
|
||||
width, height = pixbuf.get_width(), pixbuf.get_height()
|
||||
if width != height:
|
||||
square_size = min(width, height)
|
||||
x_offset = (width - square_size) // 2
|
||||
y_offset = (height - square_size) // 2
|
||||
pixbuf = pixbuf.new_subpixbuf(x_offset, y_offset, square_size, square_size)
|
||||
else:
|
||||
square_size = width
|
||||
if square_size != self.size:
|
||||
pixbuf = pixbuf.scale_simple(self.size, self.size, GdkPixbuf.InterpType.BILINEAR)
|
||||
return pixbuf
|
||||
|
||||
def on_draw(self, widget: "CircleImage", ctx: cairo.Context):
|
||||
if self._image:
|
||||
ctx.save()
|
||||
# Create a circular clipping path
|
||||
ctx.arc(self.size / 2, self.size / 2, self.size / 2, 0, 2 * math.pi)
|
||||
ctx.clip()
|
||||
# Rotate around the center of the square image
|
||||
ctx.translate(self.size / 2, self.size / 2)
|
||||
ctx.rotate(self._angle * math.pi / 180.0)
|
||||
ctx.translate(-self.size / 2, -self.size / 2)
|
||||
Gdk.cairo_set_source_pixbuf(ctx, self._image, 0, 0)
|
||||
ctx.paint()
|
||||
ctx.restore()
|
||||
|
||||
def set_image_from_file(self, new_image_file: str):
|
||||
if not new_image_file:
|
||||
return
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(new_image_file)
|
||||
self._orig_image = pixbuf
|
||||
self._image = self._process_image(pixbuf)
|
||||
self.queue_draw()
|
||||
|
||||
def set_image_from_pixbuf(self, pixbuf: GdkPixbuf.Pixbuf):
|
||||
if not pixbuf:
|
||||
return
|
||||
self._orig_image = pixbuf
|
||||
self._image = self._process_image(pixbuf)
|
||||
self.queue_draw()
|
||||
|
||||
def set_image_size(self, size: int):
|
||||
self.size = size
|
||||
if self._orig_image:
|
||||
self._image = self._process_image(self._orig_image)
|
||||
self.queue_draw()
|
||||
@@ -99,4 +99,4 @@ class IconResolver:
|
||||
self._get_icon_from_desktop_file(desktop_file)
|
||||
if desktop_file
|
||||
else self.default_applicaiton_icon
|
||||
)
|
||||
)
|
||||
+60
-48
@@ -1,57 +1,51 @@
|
||||
# Parameters
|
||||
font_family: str = "tabler-icons"
|
||||
font_weight: str = "normal"
|
||||
font_family: str = 'tabler-icons'
|
||||
font_weight: str = 'normal'
|
||||
|
||||
span: str = f"<span font-family='{font_family}' font-weight='{font_weight}'>"
|
||||
|
||||
# Panels
|
||||
apps: str = ""
|
||||
#Panels
|
||||
apps: str = ""
|
||||
dashboard: str = ""
|
||||
chat: str = ""
|
||||
wallpapers: str = ""
|
||||
windows: str = ""
|
||||
|
||||
# Bar
|
||||
colorpicker: str = ""
|
||||
media: str = ""
|
||||
|
||||
#Toolbox
|
||||
|
||||
toolbox: str = ""
|
||||
ssfull: str = ""
|
||||
ssregion: str = ""
|
||||
sswindow: str = ""
|
||||
screenshots: str = ""
|
||||
screenrecord: str = ""
|
||||
recordings: str = ""
|
||||
ocr: str = "ﳃ"
|
||||
gamemode: str = ""
|
||||
gamemode_off: str = ""
|
||||
close : str = ""
|
||||
|
||||
# Circles
|
||||
temp: str = ""
|
||||
disk: str = ""
|
||||
battery: str = ""
|
||||
memory: str = "流"
|
||||
cpu: str = ""
|
||||
gpu: str = ""
|
||||
swap: str = ""
|
||||
|
||||
# Toolbox
|
||||
toolbox: str = ""
|
||||
ssfull: str = ""
|
||||
ssregion: str = ""
|
||||
screenshots: str = ""
|
||||
screenrecord: str = ""
|
||||
recordings: str = ""
|
||||
ocr: str = "ﳃ"
|
||||
gamemode: str = ""
|
||||
gamemode_off: str = ""
|
||||
close: str = ""
|
||||
bell: str = ""
|
||||
bell_off: str = ""
|
||||
notifications_on: str = ""
|
||||
notifications_off: str = ""
|
||||
notifications_clear: str = ""
|
||||
notifications: str = ""
|
||||
|
||||
# AIchat
|
||||
reload: str = ""
|
||||
detach: str = ""
|
||||
|
||||
# Wallpapers
|
||||
add: str = ""
|
||||
sort: str = ""
|
||||
circle: str = ""
|
||||
check_box: str = ""
|
||||
box: str = ""
|
||||
scan: str = ""
|
||||
scan_stop: str = ""
|
||||
record: str = ""
|
||||
update: str = "見"
|
||||
updated: str = ""
|
||||
|
||||
# Chevrons
|
||||
chevron_up: str = ""
|
||||
@@ -79,37 +73,32 @@ bat_low: str = "="
|
||||
bat_full: str = ""
|
||||
|
||||
|
||||
moderate_signal: str = ""
|
||||
good_signal: str = ""
|
||||
no_signal: str = ""
|
||||
weak_signal: str = ""
|
||||
# Applets
|
||||
lan: str = ""
|
||||
wifi_0: str = ""
|
||||
wifi_1: str = ""
|
||||
wifi_2: str = ""
|
||||
wifi_3: str = ""
|
||||
wifi: str = ""
|
||||
world: str = ""
|
||||
world_off: str = ""
|
||||
bluetooth: str = ""
|
||||
night: str = ""
|
||||
coffee: str = ""
|
||||
dnd: str = ""
|
||||
darkmode: str = "﹖"
|
||||
|
||||
notifications: str = ""
|
||||
|
||||
wifi_off: str = ""
|
||||
bluetooth_off: str = ""
|
||||
night_off: str = ""
|
||||
dnd_off: str = ""
|
||||
notifications_off: str = ""
|
||||
|
||||
notifications_clear: str = "";
|
||||
|
||||
download: str = ""
|
||||
upload: str = ""
|
||||
|
||||
# Bluetooth
|
||||
bluetooth_connected: str = ""
|
||||
bluetooth_disconnected: str = ""
|
||||
|
||||
|
||||
# Player
|
||||
pause: str = ""
|
||||
play: str = ""
|
||||
@@ -130,16 +119,18 @@ vol_mute: str = ""
|
||||
vol_medium: str = ""
|
||||
vol_high: str = ""
|
||||
|
||||
|
||||
mic: str = ""
|
||||
mic_muted: str = ""
|
||||
mic_mute: str = ""
|
||||
|
||||
speaker: str = "𐁅"
|
||||
headphones: str = "屮"
|
||||
mic_filled: str = "️"
|
||||
|
||||
# Overview
|
||||
circle_plus: str = ""
|
||||
|
||||
# Pins
|
||||
copy_plus: str = "ﶮ"
|
||||
paperclip: str = ""
|
||||
|
||||
# Clipboard Manager
|
||||
clipboard: str = ""
|
||||
@@ -149,7 +140,6 @@ clip_text: str = ""
|
||||
accept: str = ""
|
||||
cancel: str = ""
|
||||
trash: str = ""
|
||||
clear: str = ""
|
||||
|
||||
# Config
|
||||
config: str = ""
|
||||
@@ -161,6 +151,20 @@ spotify: str = "ﺆ"
|
||||
disc: str = "𐀾"
|
||||
disc_off: str = ""
|
||||
|
||||
# Brightness
|
||||
brightness_low: str = ""
|
||||
brightness_medium: str = ""
|
||||
brightness_high: str = ""
|
||||
|
||||
brightness: str = ""
|
||||
|
||||
# Dashboard
|
||||
widgets: str = ""
|
||||
pins: str = ""
|
||||
kanban: str = ""
|
||||
wallpapers: str = ""
|
||||
sparkles: str = ""
|
||||
|
||||
# Misc
|
||||
dot: str = ""
|
||||
palette: str = ""
|
||||
@@ -170,16 +174,24 @@ radar: str = ""
|
||||
emoji: str = ""
|
||||
keyboard: str = ""
|
||||
terminal: str = ""
|
||||
timer_off: str = ""
|
||||
timer_on: str = ""
|
||||
spy: str = ""
|
||||
|
||||
# Dice
|
||||
dice_1: str = ""
|
||||
dice_2: str = ""
|
||||
dice_3: str = ""
|
||||
dice_4: str = ""
|
||||
dice_5: str = ""
|
||||
dice_6: str = ""
|
||||
|
||||
exceptions: list[str] = ["font_family", "font_weight", "span"]
|
||||
|
||||
exceptions: list[str] = ['font_family', 'font_weight', 'span']
|
||||
|
||||
def apply_span() -> None:
|
||||
global_dict = globals()
|
||||
for key in global_dict:
|
||||
if key not in exceptions and not key.startswith("__"):
|
||||
if key not in exceptions and not key.startswith('__'):
|
||||
global_dict[key] = f"{span}{global_dict[key]}</span>"
|
||||
|
||||
|
||||
apply_span()
|
||||
@@ -2,9 +2,8 @@ import math
|
||||
from typing import cast
|
||||
|
||||
import cairo
|
||||
from gi.repository import Gtk
|
||||
|
||||
from fabric.widgets.image import Image
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class CustomImage(Image):
|
||||
+21
-25
@@ -3,62 +3,59 @@ import json
|
||||
|
||||
import config.data as data
|
||||
|
||||
|
||||
def get_current_workspace():
|
||||
"""
|
||||
Get the current workspace ID using hyprctl.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["hyprctl", "activeworkspace"], capture_output=True, text=True
|
||||
["hyprctl", "activeworkspace"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
# Assume the output similar to: "ID <number>"
|
||||
# Extracting the number from the output
|
||||
parts = result.stdout.split()
|
||||
for i, part in enumerate(parts):
|
||||
if part == "ID" and i + 1 < len(parts):
|
||||
return int(parts[i + 1])
|
||||
return int(parts[i+1])
|
||||
except Exception as e:
|
||||
print(f"Error getting current workspace: {e}")
|
||||
return -1
|
||||
|
||||
|
||||
def get_screen_dimensions():
|
||||
"""
|
||||
Get screen dimensions from hyprctl.
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (width, height) of the monitor containing the current workspace
|
||||
"""
|
||||
try:
|
||||
# Get current workspace
|
||||
workspace_id = get_current_workspace()
|
||||
|
||||
|
||||
# Get monitor information
|
||||
result = subprocess.run(
|
||||
["hyprctl", "-j", "monitors"], capture_output=True, text=True
|
||||
["hyprctl", "-j", "monitors"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
monitors = json.loads(result.stdout)
|
||||
|
||||
|
||||
# Find the monitor containing our workspace
|
||||
for monitor in monitors:
|
||||
if monitor.get("activeWorkspace", {}).get("id") == workspace_id:
|
||||
return monitor.get("width", data.CURRENT_WIDTH), monitor.get(
|
||||
"height", data.CURRENT_HEIGHT
|
||||
)
|
||||
|
||||
return monitor.get("width", data.CURRENT_WIDTH), monitor.get("height", data.CURRENT_HEIGHT)
|
||||
|
||||
# Fallback to first monitor
|
||||
if monitors:
|
||||
return monitors[0].get("width", data.CURRENT_WIDTH), monitors[0].get(
|
||||
"height", data.CURRENT_HEIGHT
|
||||
)
|
||||
return monitors[0].get("width", data.CURRENT_WIDTH), monitors[0].get("height", data.CURRENT_HEIGHT)
|
||||
except Exception as e:
|
||||
print(f"Error getting screen dimensions: {e}")
|
||||
|
||||
|
||||
# Default fallback values
|
||||
return data.CURRENT_WIDTH, data.CURRENT_HEIGHT
|
||||
|
||||
|
||||
def check_occlusion(occlusion_region, workspace=None):
|
||||
"""
|
||||
Check if a region is occupied by any window on a given workspace.
|
||||
@@ -75,14 +72,14 @@ def check_occlusion(occlusion_region, workspace=None):
|
||||
"""
|
||||
if workspace is None:
|
||||
workspace = get_current_workspace()
|
||||
|
||||
|
||||
# Handle simplified side-based format
|
||||
if isinstance(occlusion_region, tuple) and len(occlusion_region) == 2:
|
||||
side, size = occlusion_region
|
||||
if isinstance(side, str):
|
||||
# Convert side-based format to coordinates
|
||||
screen_width, screen_height = get_screen_dimensions()
|
||||
|
||||
|
||||
if side.lower() == "bottom":
|
||||
occlusion_region = (0, screen_height - size, screen_width, size)
|
||||
elif side.lower() == "top":
|
||||
@@ -91,7 +88,7 @@ def check_occlusion(occlusion_region, workspace=None):
|
||||
occlusion_region = (0, 0, size, screen_height)
|
||||
elif side.lower() == "right":
|
||||
occlusion_region = (screen_width - size, 0, size, screen_height)
|
||||
|
||||
|
||||
# Ensure occlusion_region is in the correct format (x, y, width, height)
|
||||
if not isinstance(occlusion_region, tuple) or len(occlusion_region) != 4:
|
||||
print(f"Invalid occlusion region format: {occlusion_region}")
|
||||
@@ -99,7 +96,9 @@ def check_occlusion(occlusion_region, workspace=None):
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["hyprctl", "-j", "clients"], capture_output=True, text=True
|
||||
["hyprctl", "-j", "clients"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
clients = json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
@@ -132,10 +131,7 @@ def check_occlusion(occlusion_region, workspace=None):
|
||||
win_x2, win_y2 = x + width, y + height
|
||||
|
||||
# Check for intersection between the window and occlusion region
|
||||
if not (
|
||||
win_x2 <= occ_x or win_x1 >= occ_x2 or win_y2 <= occ_y or win_y1 >= occ_y2
|
||||
):
|
||||
if not (win_x2 <= occ_x or win_x1 >= occ_x2 or win_y2 <= occ_y or win_y1 >= occ_y2):
|
||||
return True # Occlusion region is occupied
|
||||
|
||||
return False # No window overlaps the occlusion region
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import math
|
||||
import cairo
|
||||
from typing import cast
|
||||
from fabric.widgets.image import Image
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class CustomImage(Image):
|
||||
def do_render_rectangle(
|
||||
self, cr: cairo.Context, width: int, height: int, radius: int = 0
|
||||
):
|
||||
cr.move_to(radius, 0)
|
||||
cr.line_to(width - radius, 0)
|
||||
cr.arc(width - radius, radius, radius, -(math.pi / 2), 0)
|
||||
cr.line_to(width, height - radius)
|
||||
cr.arc(width - radius, height - radius, radius, 0, (math.pi / 2))
|
||||
cr.line_to(radius, height)
|
||||
cr.arc(radius, height - radius, radius, (math.pi / 2), math.pi)
|
||||
cr.line_to(0, radius)
|
||||
cr.arc(radius, radius, radius, math.pi, (3 * (math.pi / 2)))
|
||||
cr.close_path()
|
||||
|
||||
def do_draw(self, cr: cairo.Context):
|
||||
context = self.get_style_context()
|
||||
width, height = self.get_allocated_width(), self.get_allocated_height()
|
||||
cr.save()
|
||||
|
||||
self.do_render_rectangle(
|
||||
cr,
|
||||
width,
|
||||
height,
|
||||
cast(int, context.get_property("border-radius", Gtk.StateFlags.NORMAL)),
|
||||
)
|
||||
cr.clip()
|
||||
Image.do_draw(self, cr)
|
||||
|
||||
cr.restore()
|
||||
@@ -0,0 +1,359 @@
|
||||
from gi.repository import Gdk, GObject, Gtk
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from enum import Enum
|
||||
from typing import Literal, cast
|
||||
|
||||
import cairo
|
||||
import gi
|
||||
from fabric.core.service import Property
|
||||
from fabric.utils.helpers import extract_css_values, get_enum_member
|
||||
from fabric.widgets.window import Window
|
||||
from loguru import logger
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
try:
|
||||
gi.require_version("GtkLayerShell", "0.1")
|
||||
from gi.repository import GtkLayerShell
|
||||
except:
|
||||
raise ImportError(
|
||||
"looks like we don't have gtk-layer-shell installed, make sure to install it first (as well as using wayland)"
|
||||
)
|
||||
|
||||
|
||||
class WaylandWindowExclusivity(Enum):
|
||||
NONE = 1
|
||||
NORMAL = 2
|
||||
AUTO = 3
|
||||
|
||||
|
||||
class Layer(GObject.GEnum):
|
||||
BACKGROUND = 0
|
||||
BOTTOM = 1
|
||||
TOP = 2
|
||||
OVERLAY = 3
|
||||
ENTRY_NUMBER = 4
|
||||
|
||||
|
||||
class KeyboardMode(GObject.GEnum):
|
||||
NONE = 0
|
||||
EXCLUSIVE = 1
|
||||
ON_DEMAND = 2
|
||||
ENTRY_NUMBER = 3
|
||||
|
||||
|
||||
class Edge(GObject.GEnum):
|
||||
LEFT = 0
|
||||
RIGHT = 1
|
||||
TOP = 2
|
||||
BOTTOM = 3
|
||||
ENTRY_NUMBER = 4
|
||||
|
||||
|
||||
class WaylandWindow(Window):
|
||||
@Property(
|
||||
Layer,
|
||||
flags="read-write",
|
||||
default_value=Layer.TOP,
|
||||
)
|
||||
def layer(self) -> Layer: # type: ignore
|
||||
return self._layer # type: ignore
|
||||
|
||||
@layer.setter
|
||||
def layer(
|
||||
self,
|
||||
value: Literal["background", "bottom", "top", "overlay"] | Layer,
|
||||
) -> None:
|
||||
self._layer = get_enum_member(Layer, value, default=Layer.TOP)
|
||||
return GtkLayerShell.set_layer(self, self._layer)
|
||||
|
||||
@Property(int, "read-write")
|
||||
def monitor(self) -> int:
|
||||
if not (monitor := cast(Gdk.Monitor, GtkLayerShell.get_monitor(self))):
|
||||
return -1
|
||||
display = monitor.get_display() or Gdk.Display.get_default()
|
||||
for i in range(0, display.get_n_monitors()):
|
||||
if display.get_monitor(i) is monitor:
|
||||
return i
|
||||
return -1
|
||||
|
||||
@monitor.setter
|
||||
def monitor(self, monitor: int | Gdk.Monitor) -> bool:
|
||||
if isinstance(monitor, int):
|
||||
display = Gdk.Display().get_default()
|
||||
monitor = display.get_monitor(monitor)
|
||||
return (
|
||||
(GtkLayerShell.set_monitor(self, monitor), True)[1]
|
||||
if monitor is not None
|
||||
else False
|
||||
)
|
||||
|
||||
@Property(WaylandWindowExclusivity, "read-write")
|
||||
def exclusivity(self) -> WaylandWindowExclusivity:
|
||||
return self._exclusivity
|
||||
|
||||
@exclusivity.setter
|
||||
def exclusivity(
|
||||
self, value: Literal["none", "normal", "auto"] | WaylandWindowExclusivity
|
||||
) -> None:
|
||||
value = get_enum_member(
|
||||
WaylandWindowExclusivity, value, default=WaylandWindowExclusivity.NONE
|
||||
)
|
||||
self._exclusivity = value
|
||||
match value:
|
||||
case WaylandWindowExclusivity.NORMAL:
|
||||
return GtkLayerShell.set_exclusive_zone(self, True)
|
||||
case WaylandWindowExclusivity.AUTO:
|
||||
return GtkLayerShell.auto_exclusive_zone_enable(self)
|
||||
case _:
|
||||
return GtkLayerShell.set_exclusive_zone(self, False)
|
||||
|
||||
@Property(bool, "read-write", default_value=False)
|
||||
def pass_through(self) -> bool:
|
||||
return self._pass_through
|
||||
|
||||
@pass_through.setter
|
||||
def pass_through(self, pass_through: bool = False):
|
||||
self._pass_through = pass_through
|
||||
region = cairo.Region() if pass_through is True else None
|
||||
self.input_shape_combine_region(region)
|
||||
del region
|
||||
return
|
||||
|
||||
@Property(
|
||||
KeyboardMode,
|
||||
"read-write",
|
||||
default_value=KeyboardMode.NONE,
|
||||
)
|
||||
def keyboard_mode(self) -> KeyboardMode:
|
||||
return self._keyboard_mode
|
||||
|
||||
@keyboard_mode.setter
|
||||
def keyboard_mode(
|
||||
self,
|
||||
value: Literal[
|
||||
"none",
|
||||
"exclusive",
|
||||
"on-demand",
|
||||
"entry-number",
|
||||
]
|
||||
| KeyboardMode,
|
||||
):
|
||||
self._keyboard_mode = get_enum_member(
|
||||
KeyboardMode, value, default=KeyboardMode.NONE
|
||||
)
|
||||
return GtkLayerShell.set_keyboard_mode(self, self._keyboard_mode)
|
||||
|
||||
@Property(tuple[Edge, ...], "read-write")
|
||||
def anchor(self):
|
||||
return tuple(
|
||||
x
|
||||
for x in [
|
||||
Edge.TOP,
|
||||
Edge.RIGHT,
|
||||
Edge.BOTTOM,
|
||||
Edge.LEFT,
|
||||
]
|
||||
if GtkLayerShell.get_anchor(self, x)
|
||||
)
|
||||
|
||||
@anchor.setter
|
||||
def anchor(self, value: str | Iterable[Edge]) -> None:
|
||||
self._anchor = value
|
||||
if isinstance(value, (list, tuple)) and all(
|
||||
isinstance(edge, Edge) for edge in value
|
||||
):
|
||||
for edge in [
|
||||
Edge.TOP,
|
||||
Edge.RIGHT,
|
||||
Edge.BOTTOM,
|
||||
Edge.LEFT,
|
||||
]:
|
||||
if edge not in value:
|
||||
GtkLayerShell.set_anchor(self, edge, False)
|
||||
GtkLayerShell.set_anchor(self, edge, True)
|
||||
return
|
||||
elif isinstance(value, str):
|
||||
for edge, anchored in WaylandWindow.extract_edges_from_string(
|
||||
value
|
||||
).items():
|
||||
GtkLayerShell.set_anchor(self, edge, anchored)
|
||||
|
||||
return
|
||||
|
||||
@Property(tuple[int, ...], flags="read-write")
|
||||
def margin(self) -> tuple[int, ...]:
|
||||
return tuple(
|
||||
GtkLayerShell.get_margin(self, x)
|
||||
for x in [
|
||||
Edge.TOP,
|
||||
Edge.RIGHT,
|
||||
Edge.BOTTOM,
|
||||
Edge.LEFT,
|
||||
]
|
||||
)
|
||||
|
||||
@margin.setter
|
||||
def margin(self, value: str | Iterable[int]) -> None:
|
||||
for edge, mrgv in WaylandWindow.extract_margin(value).items():
|
||||
GtkLayerShell.set_margin(self, edge, mrgv)
|
||||
return
|
||||
|
||||
@Property(object, "read-write")
|
||||
def keyboard_mode(self):
|
||||
kb_mode = GtkLayerShell.get_keyboard_mode(self)
|
||||
if GtkLayerShell.get_keyboard_interactivity(self):
|
||||
kb_mode = KeyboardMode.EXCLUSIVE
|
||||
return kb_mode
|
||||
|
||||
@keyboard_mode.setter
|
||||
def keyboard_mode(
|
||||
self,
|
||||
value: Literal["none", "exclusive", "on-demand"] | KeyboardMode,
|
||||
):
|
||||
return GtkLayerShell.set_keyboard_mode(
|
||||
self,
|
||||
get_enum_member(
|
||||
KeyboardMode,
|
||||
value,
|
||||
default=KeyboardMode.NONE,
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
layer: Literal["background", "bottom", "top", "overlay"] | Layer = Layer.TOP,
|
||||
anchor: str = "",
|
||||
margin: str | Iterable[int] = "0px 0px 0px 0px",
|
||||
exclusivity: Literal["auto", "normal", "none"]
|
||||
| WaylandWindowExclusivity = WaylandWindowExclusivity.NONE,
|
||||
keyboard_mode: Literal["none", "exclusive", "on-demand"]
|
||||
| KeyboardMode = KeyboardMode.NONE,
|
||||
pass_through: bool = False,
|
||||
monitor: int | Gdk.Monitor | None = None,
|
||||
title: str = "fabric",
|
||||
type: Literal["top-level", "popup"] | Gtk.WindowType = Gtk.WindowType.TOPLEVEL,
|
||||
child: Gtk.Widget | None = None,
|
||||
name: str | None = None,
|
||||
visible: bool = True,
|
||||
all_visible: bool = False,
|
||||
style: str | None = None,
|
||||
style_classes: Iterable[str] | str | None = None,
|
||||
tooltip_text: str | None = None,
|
||||
tooltip_markup: str | None = None,
|
||||
h_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||
| Gtk.Align
|
||||
| None = None,
|
||||
v_align: Literal["fill", "start", "end", "center", "baseline"]
|
||||
| Gtk.Align
|
||||
| None = None,
|
||||
h_expand: bool = False,
|
||||
v_expand: bool = False,
|
||||
size: Iterable[int] | int | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
Window.__init__(
|
||||
self,
|
||||
title=title,
|
||||
type=type,
|
||||
child=child,
|
||||
name=name,
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
style=style,
|
||||
style_classes=style_classes,
|
||||
tooltip_text=tooltip_text,
|
||||
tooltip_markup=tooltip_markup,
|
||||
h_align=h_align,
|
||||
v_align=v_align,
|
||||
h_expand=h_expand,
|
||||
v_expand=v_expand,
|
||||
size=size,
|
||||
**kwargs,
|
||||
)
|
||||
self._layer = Layer.ENTRY_NUMBER
|
||||
self._keyboard_mode = KeyboardMode.NONE
|
||||
self._anchor = anchor
|
||||
self._exclusivity = WaylandWindowExclusivity.NONE
|
||||
self._pass_through = pass_through
|
||||
|
||||
GtkLayerShell.init_for_window(self)
|
||||
GtkLayerShell.set_namespace(self, title)
|
||||
self.connect(
|
||||
"notify::title",
|
||||
lambda *_: GtkLayerShell.set_namespace(self, self.get_title()),
|
||||
)
|
||||
if monitor is not None:
|
||||
self.monitor = monitor
|
||||
self.layer = layer
|
||||
self.anchor = anchor
|
||||
self.margin = margin
|
||||
self.keyboard_mode = keyboard_mode
|
||||
self.exclusivity = exclusivity
|
||||
self.pass_through = pass_through
|
||||
self.show_all() if all_visible is True else self.show() if visible is True else None
|
||||
|
||||
def steal_input(self) -> None:
|
||||
return GtkLayerShell.set_keyboard_interactivity(self, True)
|
||||
|
||||
def return_input(self) -> None:
|
||||
return GtkLayerShell.set_keyboard_interactivity(self, False)
|
||||
|
||||
# custom overrides
|
||||
def show(self) -> None:
|
||||
super().show()
|
||||
return self.do_handle_post_show_request()
|
||||
|
||||
def show_all(self) -> None:
|
||||
super().show_all()
|
||||
return self.do_handle_post_show_request()
|
||||
|
||||
def do_handle_post_show_request(self) -> None:
|
||||
if not self.get_children():
|
||||
logger.warning(
|
||||
"[WaylandWindow] showing an empty window is not recommended, some compositors might freak out."
|
||||
)
|
||||
self.pass_through = self._pass_through
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def extract_anchor_values(string: str) -> tuple[str, ...]:
|
||||
"""
|
||||
extracts the geometry values from a given geometry string.
|
||||
|
||||
:param string: the string containing the geometry values.
|
||||
:type string: str
|
||||
:return: a list of unique directions extracted from the geometry string.
|
||||
:rtype: list
|
||||
"""
|
||||
direction_map = {"l": "left", "t": "top", "r": "right", "b": "bottom"}
|
||||
pattern = re.compile(r"\b(left|right|top|bottom)\b", re.IGNORECASE)
|
||||
matches = pattern.findall(string)
|
||||
return tuple(set(tuple(direction_map[match.lower()[0]] for match in matches)))
|
||||
|
||||
@staticmethod
|
||||
def extract_edges_from_string(string: str) -> dict["Edge", bool]:
|
||||
anchor_values = WaylandWindow.extract_anchor_values(string.lower())
|
||||
return {
|
||||
Edge.TOP: "top" in anchor_values,
|
||||
Edge.RIGHT: "right" in anchor_values,
|
||||
Edge.BOTTOM: "bottom" in anchor_values,
|
||||
Edge.LEFT: "left" in anchor_values,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def extract_margin(input: str | Iterable[int]) -> dict["Edge", int]:
|
||||
margins = (
|
||||
extract_css_values(input.lower())
|
||||
if isinstance(input, str)
|
||||
else input
|
||||
if isinstance(input, (tuple, list)) and len(input) == 4
|
||||
else (0, 0, 0, 0)
|
||||
)
|
||||
return {
|
||||
Edge.TOP: margins[0],
|
||||
Edge.RIGHT: margins[1],
|
||||
Edge.BOTTOM: margins[2],
|
||||
Edge.LEFT: margins[3],
|
||||
}
|
||||
Reference in new issue
Block a user