[SCRIPT] Updated

This commit is contained in:
t
2025-03-31 20:14:11 +02:00
parent 810f54cb65
commit 314a27eb42
472 changed files with 11721 additions and 921 deletions
+53 -27
View File
@@ -27,38 +27,41 @@ $launchpad = nwggrid
# Cursor
env = XCURSOR_SIZE,24
env = XCURSOR_THEME,Adwaita
env = XCURSOR_THEME,macOS
env = HYPRCURSOR_SIZE,24
env = HYPRCURSOR_THEME,macOS
# Nvidia
env = LIBVA_DRIVER_NAME,nvidia
env = XDG_SESSION_TYPE,wayland
env = GBM_BACKEND,nvidia-drm
env = __GLX_VENDOR_LIBRARY_NAME,nvidia
env = NVD_BACKEND,direct
# env = NVD_BACKEND,direct
# Electron
env = ELECTRON_OZONE_PLATFORM_HINT,auto
# env = ELECTRON_OZONE_PLATFORM_HINT,auto
# Firefox
env = MOZ_DISABLE_RDD_SANDBOX,1
env = EGL_PLATFORM,wayland
env = MOZ_ENABLE_WAYLAND,1
# env = MOZ_DISABLE_RDD_SANDBOX,1
# env = EGL_PLATFORM,wayland
# env = MOZ_ENABLE_WAYLAND,1
# For qt apps
env = QT_QPA_PLATFORM,wayland
env = QT_QPA_PLATFORMTHEME,qt5ct
# env = QT_QPA_PLATFORM,wayland
# env = QT_QPA_PLATFORMTHEME,qt5ct
env = GTK_THEME,Material
env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_DESKTOP,Hyprland
env = bitdepth,10
#-----------------------------------------------------------------------------------------------------------
# EXEC
#-----------------------------------------------------------------------------------------------------------
exec-once = ignis init
exec-once=/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = telegram-desktop -startintray
exec-once = nm-applet
@@ -70,14 +73,16 @@ exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
exec-once = fcitx5
exec-once = hyprswitch init --show-title &
exec-once = hyprswitch init --show-title --size-factor 5.5 --workspaces-per-row 5 &
#-----------------------------------------------------------------------------------------------------------
# INPUT
#-----------------------------------------------------------------------------------------------------------
cursor {
no_hardware_cursors = true
no_hardware_cursors = false
use_cpu_buffer = true
warp_on_change_workspace = true
}
input {
@@ -122,15 +127,15 @@ decoration {
blur {
enabled = true
size = 12
size = 12 # 12
passes = 4
new_optimizations = true
}
shadow {
enabled = true
range = 30
render_power = 4
enabled = false # true
range = 30 # 30
render_power = 4 # 4
color = rgb(000000)
}
# old shadow settings
@@ -145,13 +150,16 @@ decoration {
#-----------------------------------------------------------------------------------------------------------
animations {
# animation = NAME, ONOFF, SPEED, CURVE [, STYLE]
enabled = yes
# bezier = quart, 0.25, 1, 0.5, 1
bezier = quart, 0.25, 1, 0.5, 1
animation = windows, 1, 6, quart, slide
animation = windows, 1, 4, quart, slide
# animation = windows, 1, 4, quart, gnomed # hyprland 0.48.0 introduces gnomed
animation = border, 1, 6, quart
animation = borderangle, 1, 6, quart
# animation = borderangle, 1, 6, quart
animation = fade, 1, 6, quart
animation = workspaces, 1, 6, quart
}
@@ -165,6 +173,13 @@ dwindle {
preserve_split = yes
}
#-----------------------------------------------------------------------------------------------------------
# MASTER
#-----------------------------------------------------------------------------------------------------------
master {
new_status = master
}
#-----------------------------------------------------------------------------------------------------------
# MISC
@@ -182,24 +197,33 @@ render {
explicit_sync = true
}
#-----------------------------------------------------------------------------------------------------------
# EXPERIMENTAL
#-----------------------------------------------------------------------------------------------------------
# experimental {
# hdr = false
# }
#----------------------b-------------------------------------------------------------------------------------
# WINDOW RULES
#-----------------------------------------------------------------------------------------------------------
windowrule = float,^(pavucontrol)$
windowrule = pin,^(pavucontrol)$
windowrule = size 900 500,^(pavucontrol)$
# windowrule = float,^(pavucontrol)$
# windowrule = pin,^(pavucontrol)$
# windowrule = size 900 500,^(pavucontrol)$
#windowrule = float,^(kitty)$
#windowrule = size 640 400,^(kitty)$
windowrule = float,class:^(Material Settings)$
windowrulev2 = opacity 0.0 override, class:^(xwaylandvideobridge)$
windowrulev2 = noanim, class:^(xwaylandvideobridge)$
windowrulev2 = noinitialfocus, class:^(xwaylandvideobridge)$
windowrulev2 = maxsize 1 1, class:^(xwaylandvideobridge)$
windowrulev2 = noblur, class:^(xwaylandvideobridge)$
# windowrulev2 = opacity 0.0 override, class:^(xwaylandvideobridge)$
# windowrulev2 = noanim, class:^(xwaylandvideobridge)$
# windowrulev2 = noinitialfocus, class:^(xwaylandvideobridge)$
# windowrulev2 = maxsize 1 1, class:^(xwaylandvideobridge)$
# windowrulev2 = noblur, class:^(xwaylandvideobridge)$
# windowrulev2 = nofocus, class:^(xwaylandvideobridge)$
layerrule = blur,^(ignis_BAR.*)$
layerrule = noanim,^(ignis_NOTIFICATION_POPUP.*)$
@@ -308,6 +332,8 @@ bind = ,XF86AudioPrev, exec, playerctl previous
bind = CONTROL SHIFT, ESCAPE, exec, missioncenter
# hyprswitch
bind = $mod, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=mod=$reverse --sort-recent && hyprswitch dispatch
bind = $mod $reverse, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=mod=$reverse --sort-recent && hyprswitch dispatch -r
bind = $mod, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=key=$reverse && hyprswitch dispatch
bind = $mod $reverse, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=key=$reverse && hyprswitch dispatch -r
windowrulev2 = suppressevent maximize, class:.* # You'll probably like this.
+325
View File
@@ -0,0 +1,325 @@
#-----------------------------------------------------------------------------------------------------------
# IMPORTING FILE WITH BORDER COLOR
#-----------------------------------------------------------------------------------------------------------
source=~/.cache/ignis/material/dark_colors-hyprland.conf
source = ~/.config/hypr/monitors.conf
# Monitor primary
workspace = name:1, monitor:DP-3
exec-once = hyprctl dispatch workspace 1
# Set programs that you use
$terminal = kitty
$fileManager = nautilus
#$menu = tofi-drun --config .config/tofi/config | xargs hyprctl dispatch exec -- # wofi --show drun
$browser = zen-browser
#$shareclip = kdeconnect-cli --device=347E8D05994A40EEBFCFFBCE700FDA6B --send-clipboard
$key = tab
$mod = alt
$reverse = grave
$launchpad = nwggrid
#-----------------------------------------------------------------------------------------------------------
# ENVIRONMENT
#-----------------------------------------------------------------------------------------------------------
# Cursor
env = XCURSOR_SIZE,24
# env = XCURSOR_THEME,Adwaita
# Nvidia
env = LIBVA_DRIVER_NAME,nvidia
env = XDG_SESSION_TYPE,wayland
env = GBM_BACKEND,nvidia-drm
env = __GLX_VENDOR_LIBRARY_NAME,nvidia
env = NVD_BACKEND,direct
# Electron
env = ELECTRON_OZONE_PLATFORM_HINT,auto
# Firefox
env = MOZ_DISABLE_RDD_SANDBOX,1
env = EGL_PLATFORM,wayland
env = MOZ_ENABLE_WAYLAND,1
# For qt apps
env = QT_QPA_PLATFORM,wayland
env = QT_QPA_PLATFORMTHEME,qt5ct
env = GTK_THEME,Material
env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_DESKTOP,Hyprland
#-----------------------------------------------------------------------------------------------------------
# EXEC
#-----------------------------------------------------------------------------------------------------------
exec-once = ignis init
exec-once=/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = telegram-desktop -startintray
exec-once = nm-applet
exec-once = blueman-applet
exec-once = /usr/lib/polkit-kde-authentication-agent-1
# exec-once = swww-daemon
# exec-once = swww img /home/timo/.config/hypr/wallpaper/wallpaper.jpg
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
exec-once = fcitx5
exec-once = hyprswitch init --show-title &
#-----------------------------------------------------------------------------------------------------------
# INPUT
#-----------------------------------------------------------------------------------------------------------
cursor {
no_hardware_cursors = false
use_cpu_buffer = true
warp_on_change_workspace = true
}
input {
kb_layout = de
kb_variant =
kb_model =
kb_options = grp:win_space_toggle
kb_rules =
accel_profile = flat
follow_mouse = 1
touchpad {
natural_scroll = no
}
#scroll_factor = 2
sensitivity = 0
}
#-----------------------------------------------------------------------------------------------------------
# GENERAL
#-----------------------------------------------------------------------------------------------------------
general {
gaps_in = 5
gaps_out = 20
border_size = 2
col.active_border = $primary
col.inactive_border = rgb(000000)
resize_on_border=true
layout = dwindle
}
#-----------------------------------------------------------------------------------------------------------
# DECORATION
#-----------------------------------------------------------------------------------------------------------
decoration {
rounding = 15
blur {
enabled = true
size = 12 # 12
passes = 4
new_optimizations = true
}
shadow {
enabled = false # true
range = 30 # 30
render_power = 4 # 4
color = rgb(000000)
}
# old shadow settings
# drop_shadow = yes
# shadow_range = 30
# shadow_render_power = 4
# col.shadow = rgb(000000)
}
#-----------------------------------------------------------------------------------------------------------
# ANIMATIONS
#-----------------------------------------------------------------------------------------------------------
animations {
enabled = yes
# bezier = quart, 0.25, 1, 0.5, 1
bezier = quart, 0.25, 1, 0.5, 1
animation = windows, 1, 6, quart, slide
animation = border, 1, 6, quart
# animation = borderangle, 1, 6, quart
animation = fade, 1, 6, quart
animation = workspaces, 1, 6, quart
}
#-----------------------------------------------------------------------------------------------------------
# WINDOW LAYOUTS
#-----------------------------------------------------------------------------------------------------------
dwindle {
pseudotile = yes
preserve_split = yes
}
#-----------------------------------------------------------------------------------------------------------
# MISC
#-----------------------------------------------------------------------------------------------------------
misc {
disable_hyprland_logo=true
}
#-----------------------------------------------------------------------------------------------------------
# RENDER
#-----------------------------------------------------------------------------------------------------------
render {
explicit_sync = true
}
#-----------------------------------------------------------------------------------------------------------
# EXPERIMENTAL
#-----------------------------------------------------------------------------------------------------------
experimental {
hdr = false
}
#----------------------b-------------------------------------------------------------------------------------
# WINDOW RULES
#-----------------------------------------------------------------------------------------------------------
windowrule = float,^(pavucontrol)$
windowrule = pin,^(pavucontrol)$
windowrule = size 900 500,^(pavucontrol)$
#windowrule = float,^(kitty)$
#windowrule = size 640 400,^(kitty)$
windowrule = float,class:^(Material Settings)$
windowrulev2 = opacity 0.0 override, class:^(xwaylandvideobridge)$
windowrulev2 = noanim, class:^(xwaylandvideobridge)$
windowrulev2 = noinitialfocus, class:^(xwaylandvideobridge)$
windowrulev2 = maxsize 1 1, class:^(xwaylandvideobridge)$
windowrulev2 = noblur, class:^(xwaylandvideobridge)$
windowrulev2 = nofocus, class:^(xwaylandvideobridge)$
layerrule = blur,^(ignis_BAR.*)$
layerrule = noanim,^(ignis_NOTIFICATION_POPUP.*)$
layerrule = noanim,^(ignis_CONTROL_CENTER.*)$
#-----------------------------------------------------------------------------------------------------------
# BINDS
#-----------------------------------------------------------------------------------------------------------
# Key modifier
$mainMod = SUPER
# Binds to control windows
bind = $mainMod, Q, killactive,
bind = $mainMod SHIFT, M, exit, # force quit Hyprland
bind = $mainMod, T, togglefloating,
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
bind = ,F11, fullscreen, 0
bind = $mainMod, C, centerwindow
bind = $mainMod, D, pin
# Used to switch windows left, right, ... with another windows, also move to another screen
bind = $mainMod SHIFT, left, movewindow, l
bind = $mainMod SHIFT, right, movewindow, r
bind = $mainMod SHIFT, up, movewindow, u
bind = $mainMod SHIFT, down, movewindow, d
# Rsize but not working after reboot
# bind = $mainMod CTRL, left, resizeactive, -50 0
# bind = $mainMod CTRL, up, resizeactive, 0 50
# bind = $mainMod CTRL, down, resizeactive, 0 -50
# bind = $mainMod CTRL, right, resizeactive, 50 0
# ignis
bind = $mainMod, SPACE, exec, ignis toggle ignis_LAUNCHER
bind = $mainMod, M, exec, ignis toggle ignis_POWERMENU
bind = ALT, F4, exec, ignis toggle ignis_POWERMENU
bind = $mainMod, R, exec, ~/.config/ignis/scripts/recording.py start
bind = $mainMod SHIFT, R, exec, ~/.config/ignis/scripts/recording.py continue
bind = $mainMod SHIFT, T, exec, ~/.config/ignis/scripts/recording.py stop
bind = $mainMod SHIFT, P, exec, ~/.config/ignis/scripts/recording.py pause
# Launch apps
bind = $mainMod, Return, exec, $terminal
bind = $mainMod, L, exec, hyprlock
bind = $mainMod, E, exec, $fileManager
bind = $mainMod SHIFT, S, exec, grimblast --notify --freeze copysave area
bind = $mainMod, S, exec, grimblast --notify --freeze copysave output
bind = ,PRINT, exec, grimblast --notify --freeze copysave output
bind = $mainMod, B, exec, $browser
bind = $mainMod SHIFT, SPACE, exec, $launchpad
# Focus control
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Workspace switching
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to workspace
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Switch between existing workspaces by scrolling the mouse
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move and resize window
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Media binds
# bind = ,xf86audiomute, exec, pactl -- set-sink-mute @DEFAULT_SINK@ toggle
bind = ,XF86AudioRaiseVolume, exec, pamixer -i 5 && ignis open ignis_OSD
bind = ,XF86AudioLowerVolume, exec, pamixer -d 5 && ignis open ignis_OSD
bind = ,XF86AudioMute, exec, pamixer -t && ignis open ignis_OSD
bind = ,xf86monbrightnessdown, exec, blight -d backlight/amdgpu_bl1 set -25.5
bind = ,xf86monbrightnessup, exec, blight -d backlight/amdgpu_bl1 set +25.5
bind = ,XF86AudioPlay, exec, playerctl play-pause
bind = ,XF86AudioPause, exec, playerctl play-pause
bind = ,XF86AudioNext, exec, playerctl next
bind = ,XF86AudioPrev, exec, playerctl previous
# taskmgr
bind = CONTROL SHIFT, ESCAPE, exec, missioncenter
# hyprswitch
bind = $mod, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=mod=$reverse --sort-recent && hyprswitch dispatch
bind = $mod $reverse, $key, exec, hyprswitch gui --mod-key $mod --key $key --close mod-key-release --reverse-key=mod=$reverse --sort-recent && hyprswitch dispatch -r
+78
View File
@@ -0,0 +1,78 @@
$rosewater = rgb(f5e0dc)
$rosewaterAlpha = f5e0dc
$flamingo = rgb(f2cdcd)
$flamingoAlpha = f2cdcd
$pink = rgb(f5c2e7)
$pinkAlpha = f5c2e7
$mauve = rgb(cba6f7)
$mauveAlpha = cba6f7
$red = rgb(f38ba8)
$redAlpha = f38ba8
$maroon = rgb(eba0ac)
$maroonAlpha = eba0ac
$peach = rgb(fab387)
$peachAlpha = fab387
$yellow = rgb(f9e2af)
$yellowAlpha = f9e2af
$green = rgb(a6e3a1)
$greenAlpha = a6e3a1
$teal = rgb(94e2d5)
$tealAlpha = 94e2d5
$sky = rgb(89dceb)
$skyAlpha = 89dceb
$sapphire = rgb(74c7ec)
$sapphireAlpha = 74c7ec
$blue = rgb(89b4fa)
$blueAlpha = 89b4fa
$lavender = rgb(b4befe)
$lavenderAlpha = b4befe
$text = rgb(cdd6f4)
$textAlpha = cdd6f4
$subtext1 = rgb(bac2de)
$subtext1Alpha = bac2de
$subtext0 = rgb(a6adc8)
$subtext0Alpha = a6adc8
$overlay2 = rgb(9399b2)
$overlay2Alpha = 9399b2
$overlay1 = rgb(7f849c)
$overlay1Alpha = 7f849c
$overlay0 = rgb(6c7086)
$overlay0Alpha = 6c7086
$surface2 = rgb(585b70)
$surface2Alpha = 585b70
$surface1 = rgb(45475a)
$surface1Alpha = 45475a
$surface0 = rgb(313244)
$surface0Alpha = 313244
$base = rgb(1e1e2e)
$baseAlpha = 1e1e2e
$mantle = rgb(181825)
$mantleAlpha = 181825
$crust = rgb(11111b)
$crustAlpha = 11111b
+4 -4
View File
@@ -1,5 +1,5 @@
# Generated by nwg-displays on 2025-01-26 at 00:56:09. Do not edit manually.
monitor=HDMI-A-1,3840x2160@60.0,-2048x0,1.0,mirror,DP-3,bitdepth,10
monitor=DP-2,2560x1080@60.0,644x-1080,1.0,mirror,10
monitor=DP-3,3840x2160@60.0,0x0,1.0,bitdepth,8 # bitdepth 8 for screensharing
monitor=HDMI-A-1,3840x2160@60.0,0x1080,1.0,mirror,DP-3,bitdepth,10
monitor=DP-2,2560x1080@60.0,4484x0,1.0
monitor=DP-3,3840x2160@60.0,3840x1080,1.0,bitdepth,10
env = XWAYLAND_PRIMARY_OUTPUT,DP-3
Binary file not shown.
Binary file not shown.
View File
+23
View File
@@ -0,0 +1,23 @@
import os
from ignis.utils import Utils
EXPECT_VERSION = ["0", "2"]
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
SKIP_VER_CHECK = os.getenv("SKIP_VER_CHECK")
BYPASS_VER_MESSAGE = "To bypass this check set SKIP_VER_CHECK=1 env var."
def check_version() -> None:
if SKIP_VER_CHECK == "1":
return
VERSION = Utils.get_ignis_version().replace("dev0", "").split(".")
if int(VERSION[0]) < int(EXPECT_VERSION[0]) or int(VERSION[1]) < int(
EXPECT_VERSION[1]
):
print(
f"ERROR: My dotfiles requires at least Ignis v{'.'.join(EXPECT_VERSION)}, current version: v{'.'.join(VERSION)}. {BYPASS_VER_MESSAGE}"
)
exit(1)
+32
View File
@@ -0,0 +1,32 @@
from check_version import check_version
check_version()
from ignis.utils import Utils # noqa: E402
from ignis.app import IgnisApp # noqa: E402
from modules.control_center import control_center # noqa: E402
from modules.bar import bar # noqa: E402
from modules.notification_popup import notification_popup # noqa: E402
from modules.osd import OSD # noqa: E402
from modules.powermenu import powermenu # noqa: E402
from modules.launcher import launcher # noqa: E402
Utils.exec_sh("gsettings set org.gnome.desktop.interface gtk-theme Material")
Utils.exec_sh("gsettings set org.gnome.desktop.interface icon-theme Papirus")
Utils.exec_sh(
'gsettings set org.gnome.desktop.interface font-name "JetBrains Mono Regular 11"'
)
Utils.exec_sh("hyprctl reload")
app = IgnisApp.get_default()
app.apply_css(Utils.get_current_dir() + "/style.scss")
control_center()
for monitor in range(Utils.get_n_monitors()):
bar(monitor)
launcher()
for monitor in range(Utils.get_n_monitors()):
notification_popup(monitor)
powermenu()
OSD()
Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

@@ -0,0 +1,25 @@
from ignis.widgets import Widget
from .workspaces import workspaces
from .kb_layout import kb_layout
from .clock import clock
from .pinned_apps import pinned_apps
from .tray import tray
from .battery import battery_widget
# from .wallpaper_engine import wallpaper_engine_widget
def bar(monitor: int) -> Widget.Window:
return Widget.Window(
anchor=["left", "top", "right"],
exclusivity="exclusive",
monitor=monitor,
namespace=f"ignis_BAR_{monitor}",
layer="top",
kb_mode="none",
child=Widget.CenterBox(
css_classes=["bar-widget"],
start_widget=Widget.Box(child=[workspaces()]),
center_widget=Widget.Box(child=[pinned_apps()]),
end_widget=Widget.Box(child=[tray(), kb_layout(), battery_widget(), clock(monitor)]),
), # wallpaper_engine_widget(),
css_classes=["unset"],
)
@@ -0,0 +1,35 @@
from ignis.widgets import Widget
from ignis.services.upower import UPowerService, UPowerDevice
upower = UPowerService.get_default()
def battery_item(device: UPowerDevice) -> Widget.Box:
return Widget.Box(
css_classes=["battery-item"],
setup=lambda self: device.connect("removed", lambda x: self.unparent()),
child=[
Widget.Icon(
icon_name=device.bind("icon_name"), css_classes=["battery-icon"]
),
Widget.Label(
label=device.bind("percent", lambda x: f"{int(x)}%"),
css_classes=["battery-percent"],
),
Widget.Scale(
min=0,
max=100,
value=device.bind("percent"),
sensitive=False,
css_classes=["battery-scale"],
),
],
)
def battery_widget() -> Widget.Box:
return Widget.Box(
setup=lambda self: upower.connect(
"battery-added", lambda x, device: self.append(battery_item(device))
),
)
@@ -0,0 +1,41 @@
import datetime
from ignis.widgets import Widget
from ignis.app import IgnisApp
from ignis.utils import Utils
from ignis.variable import Variable
from .indicator import status_icons
app = IgnisApp.get_default()
current_time = Variable(
value=Utils.Poll(1000, lambda x: datetime.datetime.now().strftime("%H:%M:%S")).bind(
"output"
)
)
def clock(monitor):
window: Widget.Window = app.get_window("ignis_CONTROL_CENTER") # type: ignore
def on_click(x):
if window.monitor == monitor:
window.visible = not window.visible
else:
window.set_monitor(monitor)
window.visible = True
return Widget.Button(
child=Widget.Box(
child=[
status_icons(),
Widget.Label(
label=current_time.bind("value"),
),
]
),
css_classes=window.bind(
"visible",
lambda value: ["clock", "unset", "active"] if value else ["clock", "unset"],
),
on_click=on_click,
)
@@ -0,0 +1,92 @@
from ignis.widgets import Widget
from ignis.services.network import NetworkService
from ignis.services.notifications import NotificationService
from ignis.services.recorder import RecorderService
from ignis.services.audio import AudioService
network = NetworkService.get_default()
notifications = NotificationService.get_default()
recorder = RecorderService.get_default()
audio = AudioService.get_default()
def indicator_icon(**kwargs):
return Widget.Icon(style="margin-right: 0.5rem;", css_classes=["unset"], **kwargs)
def wifi_icon():
def check_visible(*args) -> bool:
if len(network.wifi.devices) > 0:
if network.ethernet.is_connected:
if network.wifi.is_connected:
return True
else:
return False
else:
return True
else:
return False
icon = indicator_icon(image=network.wifi.bind("icon-name"))
icon.visible = network.wifi.bind("devices", check_visible)
icon.visible = network.ethernet.bind("is_connected", check_visible)
icon.visible = network.wifi.bind("is_connected", check_visible)
return icon
def ethernet_icon():
def check_visible(*args) -> bool:
if len(network.ethernet.devices) > 0:
if network.wifi.is_connected:
if network.ethernet.is_connected:
return True
else:
return False
else:
return True
else:
return False
icon = indicator_icon(image=network.ethernet.bind("icon_name"))
icon.visible = network.ethernet.bind("devices", check_visible)
icon.visible = network.wifi.bind("is_connected", check_visible)
icon.visible = network.ethernet.bind("is_connected", check_visible)
return icon
def dnd_icon():
return indicator_icon(
image="notification-disabled-symbolic",
visible=notifications.bind("dnd"),
)
def recorder_icon():
def check_state(icon: Widget.Icon) -> None:
if recorder.is_paused:
icon.remove_css_class("active")
else:
icon.add_css_class("active")
icon = indicator_icon(
image="media-record-symbolic",
visible=recorder.bind("active"),
)
icon.add_css_class("record-indicator")
recorder.connect("notify::is-paused", lambda x, y: check_state(icon))
return icon
def volume_icon():
return indicator_icon(
image=audio.speaker.bind("icon_name"),
)
def status_icons():
return Widget.Box(
child=[recorder_icon(), wifi_icon(), ethernet_icon(), volume_icon(), dnd_icon()]
)
@@ -0,0 +1,21 @@
from ignis.widgets import Widget
from ignis.exceptions import HyprlandIPCNotFoundError
from ignis.services.hyprland import HyprlandService
try:
hyprland = HyprlandService.get_default()
def kb_layout():
return Widget.Button(
css_classes=["kb-layout", "unset"],
on_click=lambda x: hyprland.switch_kb_layout(),
child=Widget.Label(
label=hyprland.bind(
"kb_layout", transform=lambda value: value[:2].lower()
)
),
)
except HyprlandIPCNotFoundError:
def kb_layout():
return
@@ -0,0 +1,51 @@
from ignis.widgets import Widget
from ignis.app import IgnisApp
from ignis.services.applications import ApplicationsService
applications = ApplicationsService.get_default()
app = IgnisApp.get_default()
class AppItem(Widget.Button):
def __init__(self, app):
menu = Widget.PopoverMenu(
items=[
Widget.MenuItem(label="Launch", on_activate=lambda x: app.launch()),
Widget.Separator(),
]
+ [
Widget.MenuItem(
label=i.name, on_activate=lambda x, action=i: action.launch()
)
for i in app.actions
]
+ [
Widget.Separator(),
Widget.MenuItem(label="Unpin", on_activate=lambda x: app.unpin()),
]
)
super().__init__(
child=Widget.Box(child=[Widget.Icon(image=app.icon, pixel_size=32), menu]),
on_click=lambda x: app.launch(),
on_right_click=lambda x: menu.popup(),
css_classes=["pinned-app", "unset"],
)
def launcher_button():
return Widget.Button(
child=Widget.Icon(image="start-here-symbolic", pixel_size=32),
on_click=lambda x: app.toggle_window("ignis_LAUNCHER"),
css_classes=["pinned-app", "unset"],
)
def pinned_apps():
return Widget.Box(
child=applications.bind(
"pinned",
transform=lambda value: [AppItem(app) for app in value]
+ [launcher_button()],
)
)
@@ -0,0 +1,37 @@
from ignis.widgets import Widget
from ignis.services.system_tray import SystemTrayService
system_tray = SystemTrayService.get_default()
class TrayItem(Widget.Button):
def __init__(self, item):
if item.menu:
menu = item.menu.copy()
else:
menu = None
super().__init__(
child=Widget.Box(
child=[
Widget.Icon(image=item.bind("icon"), pixel_size=24),
menu,
]
),
tooltip_text=item.bind("tooltip"),
on_click=lambda x: item.activate(),
on_right_click=lambda x: menu.popup() if menu else None,
css_classes=["tray-item", "unset"],
)
item.connect("removed", lambda x: self.unparent())
def tray():
return Widget.Box(
css_classes=["tray"],
setup=lambda self: system_tray.connect(
"added", lambda x, item: self.append(TrayItem(item))
),
spacing=10,
)
@@ -0,0 +1,25 @@
from ignis.widgets import Widget
from ignis.exceptions import HyprlandIPCNotFoundError
from ignis.services.hyprland import HyprlandService
import os
def toggle_engine():
os.system("cd /home/timo/.config/hypr/linux-wallpaperengine && ./linux-wallpaperengine 2657260174 --screen-root DP-3")
try:
hyprland = HyprlandService.get_default()
def wallpaper_engine_widget():
return Widget.Button(
css_classes=["kb-layout", "unset"],
on_click=lambda x: os.system("cd /home/timo/.config/hypr/linux-wallpaperengine && ./linux-wallpaperengine 2657260174 --screen-root DP-3"),
child=Widget.Label(
label=hyprland.bind(
"kb_layout", transform=lambda value: value[:2].lower()
)
),
)
except HyprlandIPCNotFoundError:
def kb_layout():
return
@@ -0,0 +1,46 @@
from ignis.widgets import Widget
from ignis.exceptions import HyprlandIPCNotFoundError
from ignis.services.hyprland import HyprlandService
class WorkspaceButton(Widget.Button):
def __init__(self, workspace: dict) -> None:
super().__init__(
css_classes=["workspace", "unset"],
on_click=lambda x, id=workspace["id"]: hyprland.switch_to_workspace(id),
halign="start",
valign="center",
)
if workspace["id"] == hyprland.active_workspace["id"]:
self.add_css_class("active")
try:
hyprland = HyprlandService.get_default()
def scroll_workspaces(direction: str) -> None:
current = hyprland.active_workspace["id"]
if direction == "up":
target = current - 1
hyprland.switch_to_workspace(target)
else:
target = current + 1
if target == 11:
return
hyprland.switch_to_workspace(target)
def workspaces():
return Widget.EventBox(
on_scroll_up=lambda x: scroll_workspaces("up"),
on_scroll_down=lambda x: scroll_workspaces("down"),
css_classes=["workspaces"],
child=hyprland.bind(
"workspaces",
transform=lambda value: [WorkspaceButton(i) for i in value],
),
)
except HyprlandIPCNotFoundError:
def workspaces():
return
@@ -0,0 +1,62 @@
from ignis.widgets import Widget
from ignis.app import IgnisApp
from .volume import volume_control
from .quick_settings import quick_settings
from .user import user
from .media import media
from .notification_center import notification_center
from .brightness import brightness_slider
app = IgnisApp.get_default()
def control_center_widget() -> Widget.Box:
return Widget.Box(
vertical=True,
css_classes=["control-center"],
child=[
Widget.Box(
vertical=True,
css_classes=["control-center-widget"],
child=[
quick_settings(),
volume_control(),
brightness_slider(),
user(),
media(),
],
),
notification_center(),
],
)
def control_center() -> Widget.RevealerWindow:
revealer = Widget.Revealer(
transition_type="slide_left",
child=control_center_widget(),
transition_duration=300,
reveal_child=True,
)
box = Widget.Box(
child=[
Widget.Button(
vexpand=True,
hexpand=True,
css_classes=["unset"],
on_click=lambda x: app.close_window("ignis_CONTROL_CENTER"),
),
revealer,
],
)
return Widget.RevealerWindow(
visible=False,
popup=True,
kb_mode="on_demand",
layer="top",
css_classes=["unset"],
anchor=["top", "right", "bottom", "left"],
namespace="ignis_CONTROL_CENTER",
child=box,
revealer=revealer,
)
@@ -0,0 +1,27 @@
from ignis.services.backlight import BacklightService
from ignis.widgets import Widget
backlight = BacklightService.get_default()
def brightness_slider() -> Widget.Scale:
return Widget.Box(
visible=backlight.bind("available"),
hexpand=True,
style="margin-top: 0.25rem;",
child=[
Widget.Icon(
image="display-brightness-symbolic",
css_classes=["material-slider-icon"],
pixel_size=18,
),
Widget.Scale(
min=0,
max=backlight.max_brightness,
hexpand=True,
value=backlight.bind("brightness"),
css_classes=["material-slider"],
on_change=lambda x: backlight.set_brightness(x.value),
),
],
)
@@ -0,0 +1,14 @@
from services.material import MaterialService
from .qs_button import QSButton
material = MaterialService.get_default()
def dark_mode_button() -> QSButton:
return QSButton(
label="Dark",
icon_name="night-light-symbolic",
on_activate=lambda x: material.set_dark_mode(True),
on_deactivate=lambda x: material.set_dark_mode(False),
active=material.bind("dark-mode"),
)
@@ -0,0 +1,19 @@
from .qs_button import QSButton
from ignis.services.notifications import NotificationService
notifications = NotificationService.get_default()
def dnd_button() -> QSButton:
return QSButton(
label=notifications.bind("dnd", lambda value: "Silent" if value else "Noisy"),
icon_name=notifications.bind(
"dnd",
transform=lambda value: "notification-disabled-symbolic"
if value
else "notification-symbolic",
),
on_activate=lambda x: notifications.set_dnd(not notifications.dnd),
on_deactivate=lambda x: notifications.set_dnd(not notifications.dnd),
active=notifications.bind("dnd"),
)
@@ -0,0 +1,100 @@
from typing import List
from ignis.widgets import Widget
from ignis.utils import Utils
from .qs_button import QSButton
from ignis.services.network import NetworkService, EthernetDevice
network = NetworkService.get_default()
class EthernetConnectionItem(Widget.Button):
def __init__(self, device: EthernetDevice):
super().__init__(
css_classes=["ethernet-connection-item", "unset"],
on_click=lambda x: device.disconnect_from()
if device.is_connected
else device.connect_to(),
child=Widget.Box(
child=[
Widget.Icon(image="network-wired-symbolic"),
Widget.Label(
label=device.name,
ellipsize="end",
max_width_chars=20,
halign="start",
css_classes=["ethernet-connection-label"],
),
Widget.Button(
child=Widget.Label(
label=device.bind(
"is_connected",
lambda value: "Disconnect" if value else "Connect",
)
),
css_classes=["ethernet-connection-item-connect-label", "unset"],
halign="end",
hexpand=True,
),
]
),
)
def ethernet_control() -> List[QSButton]:
networks_list = Widget.Revealer(
transition_duration=300,
transition_type="slide_down",
child=Widget.Box(
vertical=True,
css_classes=["control-center-menu"],
child=[
Widget.Box(
css_classes=["ethernet-header-box"],
child=[
Widget.Icon(icon_name="network-wired-symbolic", pixel_size=28),
Widget.Label(
label="Wired connections",
css_classes=["ethernet-header-label"],
),
],
),
Widget.Box(
vertical=True,
child=network.ethernet.bind(
"devices",
lambda value: [EthernetConnectionItem(i) for i in value],
),
),
Widget.Separator(css_classes=["ethernet-network-list-separator"]),
Widget.Button(
css_classes=["ethernet-connection-item", "unset"],
style="margin-bottom: 0;",
on_click=lambda x: Utils.exec_sh_async("nm-connection-editor"),
child=Widget.Box(
child=[
Widget.Icon(image="preferences-system-symbolic"),
Widget.Label(
label="Network Settings",
halign="start",
css_classes=["ethernet-connection-label"],
),
]
),
),
],
),
)
if len(network.ethernet.devices) > 0:
return [
QSButton(
label="Wired",
icon_name="network-wired-symbolic",
on_activate=lambda x: networks_list.toggle(),
on_deactivate=lambda x: networks_list.toggle(),
content=networks_list,
active=network.ethernet.bind("is_connected"),
)
]
else:
return []
@@ -0,0 +1,202 @@
import os
import ignis
from ignis.widgets import Widget
from ignis.services.mpris import MprisService, MprisPlayer
from ignis.utils import Utils
from services.material import MaterialService
from ignis.app import IgnisApp
from ignis.exceptions import StylePathNotFoundError
mpris = MprisService.get_default()
app = IgnisApp.get_default()
material = MaterialService.get_default()
MEDIA_TEMPLATE = Utils.get_current_dir() + "/../../scss/media.scss"
MEDIA_SCSS_CACHE_DIR = ignis.CACHE_DIR + "/media" # type: ignore
MEDIA_ART_FALLBACK = Utils.get_current_dir() + "/../../misc/media-art-fallback.png"
os.makedirs(MEDIA_SCSS_CACHE_DIR, exist_ok=True)
PLAYER_ICONS = {"spotify": "󰓇", "firefox": "󰈹", "chrome": "󰊯", None: ""}
class Player(Widget.Revealer):
def __init__(self, player: MprisPlayer) -> None:
self._player = player
self._colors_path = f"{MEDIA_SCSS_CACHE_DIR}/{self._player.desktop_entry}.scss"
player.connect("closed", lambda x: self.destroy())
player.connect("notify::art-url", lambda x, y: self.load_colors())
self.load_colors()
super().__init__(
transition_type="slide_down",
reveal_child=False,
css_classes=[self.get_css("media")],
child=Widget.Overlay(
child=Widget.Box(css_classes=[self.get_css("media-image")]),
overlays=[
Widget.Box(
hexpand=True,
vexpand=True,
css_classes=[self.get_css("media-image-gradient")],
),
Widget.Label(
label=self.get_player_icon(),
halign="start",
valign="start",
css_classes=[self.get_css("media-player-icon")],
),
Widget.Box(
vertical=True,
hexpand=True,
css_classes=[self.get_css("media-content")],
child=[
Widget.Box(
vexpand=True,
valign="center",
child=[
Widget.Box(
hexpand=True,
vertical=True,
child=[
Widget.Label(
ellipsize="end",
label=player.bind("title"),
max_width_chars=30,
halign="start",
css_classes=[
self.get_css("media-title")
],
),
Widget.Label(
label=player.bind("artist"),
max_width_chars=30,
ellipsize="end",
halign="start",
css_classes=[
self.get_css("media-artist")
],
),
],
),
Widget.Button(
child=Widget.Icon(
image=player.bind(
"playback_status",
lambda value: "media-playback-pause-symbolic"
if value == "Playing"
else "media-playback-start-symbolic",
),
pixel_size=18,
),
on_click=lambda x: player.play_pause(),
visible=player.bind("can_play"),
css_classes=player.bind(
"playback_status",
lambda value: [
self.get_css("media-playback-button"),
"playing",
]
if value == "Playing"
else [
self.get_css("media-playback-button"),
"paused",
],
),
),
],
),
],
),
Widget.Box(
vexpand=True,
valign="end",
style="padding: 1rem;",
child=[
Widget.Scale(
value=player.bind("position"),
max=player.bind("length"),
hexpand=True,
css_classes=[self.get_css("media-scale")],
on_change=lambda x: player.set_position(x.value),
visible=player.bind(
"position", lambda value: value != -1
),
),
Widget.Button(
child=Widget.Icon(
image="media-skip-backward-symbolic",
pixel_size=20,
),
css_classes=[self.get_css("media-skip-button")],
on_click=lambda x: player.previous(),
visible=player.bind("can_go_previous"),
style="margin-left: 1rem;",
),
Widget.Button(
child=Widget.Icon(
image="media-skip-forward-symbolic",
pixel_size=20,
),
css_classes=[self.get_css("media-skip-button")],
on_click=lambda x: player.next(),
visible=player.bind("can_go_next"),
style="margin-left: 1rem;",
),
],
),
],
),
)
def get_player_icon(self) -> str:
if self._player.desktop_entry == "firefox":
return PLAYER_ICONS["firefox"]
elif self._player.desktop_entry == "spotify":
return PLAYER_ICONS["spotify"]
elif "chromium" in self._player.track_id or "chrome" in self._player.track_id:
return PLAYER_ICONS["chrome"]
else:
return PLAYER_ICONS[None]
def destroy(self) -> None:
self.set_reveal_child(False)
Utils.Timeout(self.transition_duration, super().unparent)
def get_css(self, class_name: str) -> str:
return f"{class_name}-{self._player.desktop_entry}"
def load_colors(self) -> None:
if not self._player.art_url:
art_url = MEDIA_ART_FALLBACK
else:
art_url = self._player.art_url
try:
app.remove_css(self._colors_path)
except StylePathNotFoundError:
pass
colors = material.get_colors_from_img(art_url, True)
colors["art_url"] = art_url
colors["desktop_entry"] = self._player.desktop_entry
material.render_template(
colors, input_file=MEDIA_TEMPLATE, output_file=self._colors_path
)
app.apply_css(self._colors_path)
def media() -> Widget.Box:
def add_player(box: Widget.Box, obj: MprisPlayer) -> None:
player = Player(obj)
box.append(player)
player.set_reveal_child(True)
return Widget.Box(
vertical=True,
setup=lambda self: mpris.connect(
"player_added", lambda x, player: add_player(self, player)
),
css_classes=["rec-unset"],
)
@@ -0,0 +1,242 @@
from ignis.widgets import Widget
from ignis.services.notifications import Notification, NotificationService
from ignis.utils import Utils
from gi.repository import GLib # type: ignore
notifications = NotificationService.get_default()
class ScreenshotLayout(Widget.Box):
def __init__(self, notification: Notification) -> None:
super().__init__(
vertical=True,
hexpand=True,
child=[
Widget.Box(
child=[
Widget.Picture(
image=notification.icon,
content_fit="cover",
width=1920 // 7,
height=1080 // 7,
style="border-radius: 1rem; background-color: black;",
),
Widget.Button(
child=Widget.Icon(
image="window-close-symbolic", pixel_size=20
),
halign="end",
valign="start",
hexpand=True,
css_classes=["notification-close"],
on_click=lambda x: notification.close(),
),
],
),
Widget.Label(
label="Screenshot saved",
css_classes=["notification-screenshot-label"],
),
Widget.Box(
homogeneous=True,
style="margin-top: 0.75rem;",
spacing=10,
child=[
Widget.Button(
child=Widget.Label(label="Open"),
css_classes=["notification-action"],
on_click=lambda x: Utils.exec_sh_async(
f"xdg-open {notification.icon}"
),
),
Widget.Button(
child=Widget.Label(label="Close"),
css_classes=["notification-action"],
on_click=lambda x: notification.close(),
),
],
),
],
)
class NormalLayout(Widget.Box):
def __init__(self, notification: Notification) -> None:
super().__init__(
vertical=True,
hexpand=True,
child=[
Widget.Box(
child=[
Widget.Icon(
image=notification.icon
if notification.icon
else "dialog-information-symbolic",
pixel_size=48,
halign="start",
valign="start",
),
Widget.Box(
vertical=True,
style="margin-left: 0.75rem;",
child=[
Widget.Label(
ellipsize="end",
label=notification.summary,
halign="start",
visible=notification.summary != "",
css_classes=["notification-summary"],
),
Widget.Label(
label=notification.body,
ellipsize="end",
halign="start",
css_classes=["notification-body"],
visible=notification.body != "",
),
],
),
Widget.Button(
child=Widget.Icon(
image="window-close-symbolic", pixel_size=20
),
halign="end",
valign="start",
hexpand=True,
css_classes=["notification-close"],
on_click=lambda x: notification.close(),
),
],
),
Widget.Box(
child=[
Widget.Button(
child=Widget.Label(label=action.label),
on_click=lambda x, action=action: action.invoke(),
css_classes=["notification-action"],
)
for action in notification.actions
],
homogeneous=True,
style="margin-top: 0.75rem;" if notification.actions else "",
spacing=10,
),
],
)
class NotificationWidget(Widget.Box):
def __init__(self, notification: Notification) -> None:
layout: NormalLayout | ScreenshotLayout
if notification.app_name == "grimblast":
layout = ScreenshotLayout(notification)
else:
layout = NormalLayout(notification)
super().__init__(
css_classes=["notification"],
child=[layout],
)
class Popup(Widget.Revealer):
def __init__(self, notification: Notification, **kwargs):
widget = NotificationWidget(notification)
super().__init__(child=widget, transition_type="slide_down", **kwargs)
notification.connect("closed", lambda x: self.destroy())
def destroy(self):
self.reveal_child = False
Utils.Timeout(self.transition_duration, self.unparent)
def loading_notif_label() -> Widget.Label:
return Widget.Label(
label="Loading notifications...",
valign="center",
vexpand=True,
css_classes=["notification-center-info-label"],
)
def no_notifications_label() -> Widget.Label:
return Widget.Label(
label="No notifications",
valign="center",
vexpand=True,
visible=notifications.bind("notifications", lambda value: len(value) == 0),
css_classes=["notification-center-info-label"],
)
def on_notified(box: Widget.Box, notification: Notification) -> None:
notify = Popup(notification)
box.prepend(notify)
notify.reveal_child = True
def load_notifications() -> list[Widget.Label | Popup]:
widgets = []
for i in notifications.notifications:
GLib.idle_add(lambda i=i: widgets.append(Popup(i, reveal_child=True)))
return widgets
def notification_list() -> Widget.Box:
box = Widget.Box(
vertical=True,
child=[loading_notif_label()],
vexpand=True,
css_classes=["rec-unset"],
setup=lambda self: notifications.connect(
"notified",
lambda x, notification: on_notified(self, notification),
),
)
Utils.ThreadTask(
load_notifications,
lambda result: box.set_child(result + [no_notifications_label()]),
).run()
return box
def notification_center() -> Widget.Box:
main_box = Widget.Box(
vertical=True,
vexpand=True,
css_classes=["notification-center"],
child=[
Widget.Box(
css_classes=["notification-center-header", "rec-unset"],
child=[
Widget.Label(
label=notifications.bind(
"notifications", lambda value: str(len(value))
),
css_classes=["notification-count"],
),
Widget.Label(
label="notifications",
css_classes=["notification-header-label"],
),
Widget.Button(
child=Widget.Label(label="Clear all"),
halign="end",
hexpand=True,
on_click=lambda x: notifications.clear_all(),
css_classes=["notification-clear-all"],
),
],
),
Widget.Scroll(
child=notification_list(),
vexpand=True,
),
],
)
return main_box
@@ -0,0 +1,64 @@
from ignis.widgets import Widget
from gi.repository import GObject # type: ignore
from typing import Callable
from ignis.gobject import Binding
class QSButton(Widget.Button):
def __init__(
self,
label: str | Binding,
icon_name: str | Binding,
on_activate: Callable | None = None,
on_deactivate: Callable | None = None,
content: Widget.Revealer | None = None,
**kwargs,
):
self.on_activate = on_activate
self.on_deactivate = on_deactivate
self._active = False
self._content = content
super().__init__(
child=Widget.Box(
child=[
Widget.Icon(image=icon_name),
Widget.Label(label=label, css_classes=["qs-button-label"]),
Widget.Arrow(
halign="end",
hexpand=True,
pixel_size=20,
rotated=content.bind("reveal_child"),
)
if content
else None,
]
),
on_click=self.__callback,
css_classes=["qs-button", "unset"],
hexpand=True,
**kwargs,
)
def __callback(self, *args) -> None:
if self.active:
if self.on_deactivate:
self.on_deactivate(self)
else:
if self.on_activate:
self.on_activate(self)
@GObject.Property
def active(self) -> bool:
return self._active
@active.setter
def active(self, value: bool) -> None:
self._active = value
if value:
self.add_css_class("active")
else:
self.remove_css_class("active")
@GObject.Property
def content(self) -> Widget.Revealer | None:
return self._content
@@ -0,0 +1,64 @@
from ignis.widgets import Widget
from .wifi import wifi_control
from .record import record_control
from .dnd import dnd_button
from .dark_mode import dark_mode_button
from .ethernet import ethernet_control
from .vpn import vpn_control
from .qs_button import QSButton
from ignis.services.network import NetworkService
network = NetworkService.get_default()
def add_button(main_box: Widget.Box, buttons: tuple[QSButton, ...], i: int) -> None:
row = Widget.Box(homogeneous=True)
if len(main_box.child) > 0:
row.style = "margin-top: 0.5rem;"
main_box.append(row)
button1 = buttons[i]
row.append(button1)
if button1.content:
main_box.append(button1.content)
if i + 1 < len(buttons):
button2 = buttons[i + 1]
button2.style = "margin-left: 0.5rem;"
row.append(button2)
if button2.content:
main_box.append(button2.content)
def qs_fabric(main_box: Widget.Box, *buttons: QSButton) -> None:
for i in range(0, len(buttons), 2):
add_button(main_box, buttons, i)
def qs_config(main_box: Widget.Box) -> None:
qs_fabric(
main_box,
*wifi_control(),
*ethernet_control(),
*vpn_control(),
dnd_button(),
dark_mode_button(),
record_control(),
)
def update_box(main_box: Widget.Box):
main_box.child = []
qs_config(main_box)
def quick_settings() -> Widget.Box:
main_box = Widget.Box(vertical=True, css_classes=["qs-main-box"])
network.wifi.connect("notify::devices", lambda x, y: update_box(main_box))
network.ethernet.connect("notify::devices", lambda x, y: update_box(main_box))
network.vpn.connect("notify::connections", lambda x, y: update_box(main_box))
return main_box
@@ -0,0 +1,100 @@
from ignis.widgets import Widget
from .qs_button import QSButton
from ignis.services.recorder import RecorderService
recorder = RecorderService.get_default()
def record_control() -> QSButton:
record_audio_switch = Widget.Switch(halign="end", hexpand=True, valign="center")
dropdown = Widget.DropDown(
items=["Internal audio", "Microphone", "Both sources"],
css_classes=["record-dropdown"],
)
def start_recording(record_menu: Widget.Revealer) -> None:
record_menu.set_reveal_child(False)
microphone = False
internal = False
if record_audio_switch.active:
if dropdown.selected == "Internal audio":
internal = True
elif dropdown.selected == "Microphone":
microphone = True
else:
internal = True
microphone = True
recorder.start_recording(
record_microphone=microphone, record_internal_audio=internal
)
record_menu = Widget.Revealer(
transition_duration=300,
transition_type="slide_down",
child=Widget.Box(
css_classes=["record-menu"],
vertical=True,
child=[
Widget.Icon(
image="media-record-symbolic",
pixel_size=36,
halign="center",
css_classes=["record-icon"],
),
Widget.Label(
label="Start recording?",
halign="center",
style="font-size: 1.2rem;",
),
Widget.Box(
style="margin-top: 0.5rem;",
child=[
Widget.Icon(
image="microphone-sensitivity-medium-symbolic",
pixel_size=20,
style="margin-right: 0.5rem;",
),
Widget.Box(
vertical=True,
child=[
Widget.Label(
label="Record audio",
style="font-size: 1.1rem;",
halign="start",
),
dropdown,
],
),
record_audio_switch,
],
),
Widget.Box(
style="margin-top: 1rem;",
child=[
Widget.Button(
child=Widget.Label(label="Cancel"),
css_classes=["record-cancel-button", "unset"],
on_click=lambda x: record_menu.set_reveal_child(False), # type: ignore
),
Widget.Button(
child=Widget.Label(label="Start recording"),
halign="end",
hexpand=True,
css_classes=["record-start-button", "unset"],
on_click=lambda x: start_recording(record_menu), # type: ignore
),
],
),
],
),
)
return QSButton(
label="Recording",
icon_name="media-record-symbolic",
on_activate=lambda x: record_menu.toggle(),
on_deactivate=lambda x: recorder.stop_recording(),
active=recorder.bind("active"),
content=record_menu,
)
@@ -0,0 +1,76 @@
import os
from ignis.widgets import Widget
from ignis.utils import Utils
from ignis.app import IgnisApp
from ignis.services.fetch import FetchService
from ..settings import settings_window
from options import avatar_opt
fetch = FetchService.get_default()
app = IgnisApp.get_default()
def format_uptime(value):
days, hours, minutes, seconds = value
if days:
return f"up {days:02}:{hours:02}:{minutes:02}"
else:
return f"up {hours:02}:{minutes:02}"
def user_image() -> Widget.Picture:
return Widget.Picture(
image=avatar_opt.bind(
"value",
lambda value: "user-info" if not os.path.exists(value) else value,
),
width=44,
height=44,
content_fit="cover",
style="border-radius: 10rem;",
)
def username() -> Widget.Box:
return Widget.Box(
child=[
Widget.Label(
label=os.getenv("USER"), css_classes=["user-name"], halign="start"
),
Widget.Label(
label=Utils.Poll(
timeout=60 * 1000, callback=lambda x: fetch.uptime
).bind("output", lambda value: format_uptime(value)),
halign="start",
css_classes=["user-name-secondary"],
),
],
vertical=True,
css_classes=["user-name-box"],
)
def settings_button() -> Widget.Button:
return Widget.Button(
child=Widget.Icon(image="emblem-system-symbolic", pixel_size=20),
halign="end",
hexpand=True,
css_classes=["user-settings", "unset"],
on_click=lambda x: settings_window(),
)
def power_button() -> Widget.Button:
return Widget.Button(
child=Widget.Icon(image="system-shutdown-symbolic", pixel_size=20),
halign="end",
css_classes=["user-power", "unset"],
on_click=lambda x: app.toggle_window("ignis_POWERMENU"),
)
def user() -> Widget.Box:
return Widget.Box(
child=[user_image(), username(), settings_button(), power_button()],
css_classes=["user"],
)
@@ -0,0 +1,161 @@
from ignis.widgets import Widget
from ignis.utils import Utils
from ignis.services.audio import AudioService, Stream
audio = AudioService.get_default()
def volume_scale(stream: Stream) -> Widget.Scale:
return Widget.Scale(
css_classes=["material-slider"],
value=stream.bind("volume"),
step=5,
hexpand=True,
on_change=lambda x: stream.set_volume(x.value),
sensitive=stream.bind("is_muted", lambda value: not value),
)
def device_entry(stream: Stream, _type: str) -> Widget.Button:
widget = Widget.Button(
child=Widget.Box(
child=[
Widget.Icon(image="audio-card-symbolic"),
Widget.Label(
label=stream.description,
ellipsize="end",
max_width_chars=30,
halign="start",
css_classes=["volume-entry-label"],
),
Widget.Icon(
image="object-select-symbolic",
halign="end",
hexpand=True,
visible=stream.bind("is_default"),
),
]
),
css_classes=["volume-entry", "unset"],
hexpand=True,
on_click=lambda x: setattr(audio, _type, stream),
)
stream.connect("removed", lambda x: widget.unparent())
return widget
def device_list(
header_label: str, header_icon: str, _type: str, **kwargs
) -> Widget.Revealer:
box = Widget.Box(
css_classes=["control-center-menu"],
vertical=True,
child=[
Widget.Box(
child=[
Widget.Icon(image=header_icon, pixel_size=24),
Widget.Label(
label=header_label,
halign="start",
css_classes=["volume-entry-list-header-label"],
),
],
css_classes=["volume-entry-list-header-box"],
),
Widget.Box(
vertical=True,
setup=lambda self: audio.connect(
f"{_type}-added",
lambda x, stream: self.append(device_entry(stream, _type)),
),
),
Widget.Separator(css_classes=["volume-entry-list-separator"]),
Widget.Button(
child=Widget.Box(
child=[
Widget.Icon(image="preferences-system-symbolic"),
Widget.Label(
label="Sound Settings",
halign="start",
css_classes=["volume-entry-label"],
),
]
),
css_classes=["volume-entry", "unset"],
style="margin-bottom: 0;",
on_click=lambda x: Utils.exec_sh_async("pavucontrol"),
),
],
**kwargs,
)
return Widget.Revealer(
child=box, transition_type="slide_down", transition_duration=300
)
def volume_icon(stream: Stream) -> Widget.Button:
return Widget.Button(
child=Widget.Icon(
image=stream.bind("icon_name"),
pixel_size=18,
),
css_classes=["material-slider-icon", "unset", "hover-surface"],
on_click=lambda x: stream.set_is_muted(not stream.is_muted),
)
def device_list_arrow(device_list: Widget.Revealer) -> Widget.Button:
return Widget.ArrowButton(
arrow=Widget.Arrow(pixel_size=20),
css_classes=["material-slider-arrow", "hover-surface"],
on_click=lambda x: device_list.toggle(),
)
def volume_control():
speakers_list = device_list(
header_label="Sound Output",
header_icon="audio-headphones-symbolic",
_type="speaker",
style="margin-bottom: 1rem;",
)
microphones_list = device_list(
header_label="Sound Input",
header_icon="microphone-sensitivity-high-symbolic",
_type="microphone",
)
speaker_icon = volume_icon(audio.speaker)
microphone_icon = volume_icon(audio.microphone)
speaker_scale = volume_scale(audio.speaker)
microphone_scale = volume_scale(audio.microphone)
speaker_arrow = device_list_arrow(speakers_list)
microphone_arrow = device_list_arrow(microphones_list)
speaker_control = Widget.Box(
vertical=True,
child=[
Widget.Box(child=[speaker_icon, speaker_scale, speaker_arrow]),
speakers_list,
],
style="margin-top: 1rem;",
)
microphone_control = Widget.Box(
vertical=True,
child=[
Widget.Box(child=[microphone_icon, microphone_scale, microphone_arrow]),
microphones_list,
],
style="margin-top: 0.25rem;",
)
return Widget.Box(
vertical=True,
child=[speaker_control, microphone_control],
)
@@ -0,0 +1,120 @@
from ignis.widgets import Widget
from ignis.utils import Utils
from .qs_button import QSButton
from typing import List
from ignis.services.network import NetworkService, VpnConnection
network = NetworkService.get_default()
class VpnNetworkItem(Widget.Button):
def __init__(self, conn: VpnConnection):
super().__init__(
css_classes=["vpn-connection-item", "unset"],
on_click=lambda x: conn.toggle_connection(),
child=Widget.Box(
child=[
Widget.Label(
label=conn.name,
ellipsize="end",
max_width_chars=20,
halign="start",
css_classes=["vpn-connection-label"],
),
Widget.Button(
child=Widget.Label(
label=conn.bind(
"is_connected",
lambda value: "Disconnect" if value else "Connect",
)
),
css_classes=["vpn-connection-item-connect-label", "unset"],
halign="end",
hexpand=True,
),
]
),
)
def vpn_qsbutton() -> QSButton:
networks_list = Widget.Revealer(
transition_duration=300,
transition_type="slide_down",
child=Widget.Box(
vertical=True,
css_classes=["control-center-menu"],
child=[
Widget.Box(
css_classes=["vpn-header-box"],
child=[
Widget.Icon(
icon_name="network-vpn-symbolic", pixel_size=28),
Widget.Label(
label="VPN connections",
css_classes=["vpn-header-label"],
),
],
),
Widget.Box(
vertical=True,
child=network.vpn.bind(
"connections",
transform=lambda value: [
VpnNetworkItem(i) for i in value],
),
),
Widget.Separator(
css_classes=["vpn-connection-list-separator"]),
Widget.Button(
css_classes=["vpn-connection-item", "unset"],
on_click=lambda x: Utils.exec_sh_async(
"nm-connection-editor"),
style="margin-bottom: 0;",
child=Widget.Box(
child=[
Widget.Icon(image="preferences-system-symbolic"),
Widget.Label(
label="Network Manager",
halign="start",
css_classes=["vpn-connection-label"],
),
]
),
),
],
),
)
def get_label(id: str) -> str:
if id:
return id
else:
return "VPN"
def get_icon(icon_name: str) -> str:
if network.vpn.is_connected:
return icon_name
else:
return "network-vpn-symbolic"
def toggle_list(x) -> None:
networks_list.toggle()
return QSButton(
label=network.vpn.bind("active_vpn_id", get_label),
icon_name=network.vpn.bind("icon-name", get_icon),
on_activate=toggle_list,
on_deactivate=toggle_list,
active=network.vpn.bind("is-connected"),
content=networks_list,
)
def vpn_control() -> List[QSButton]:
if len(network.vpn.connections) > 0:
return [vpn_qsbutton()]
else:
return []
@@ -0,0 +1,112 @@
from ignis.widgets import Widget
from ignis.utils import Utils
from .qs_button import QSButton
from ignis.services.network import NetworkService, WifiAccessPoint, WifiDevice
from typing import List
network = NetworkService.get_default()
class WifiNetworkItem(Widget.Button):
def __init__(self, access_point: WifiAccessPoint):
super().__init__(
css_classes=["wifi-network-item", "unset"],
on_click=lambda x: access_point.connect_to_graphical(),
child=Widget.Box(
child=[
Widget.Icon(
image=access_point.bind(
"strength", transform=lambda value: access_point.icon_name
),
),
Widget.Label(
label=access_point.ssid,
halign="start",
css_classes=["wifi-network-label"],
),
Widget.Icon(
image="object-select-symbolic",
halign="end",
hexpand=True,
visible=access_point.bind("is_connected"),
),
]
),
)
def wifi_qsbutton(device: WifiDevice) -> QSButton:
networks_list = Widget.Revealer(
transition_duration=300,
transition_type="slide_down",
child=Widget.Box(
vertical=True,
css_classes=["control-center-menu"],
child=[
Widget.Box(
child=[
Widget.Label(label="Wi-Fi", css_classes=["wifi-header-label"]),
Widget.Switch(
halign="end",
hexpand=True,
active=network.wifi.enabled,
on_change=lambda x, state: network.wifi.set_enabled(state),
),
],
css_classes=["toggle-box", "wifi-header-box"],
),
Widget.Box(
vertical=True,
child=device.bind(
"access_points",
transform=lambda value: [WifiNetworkItem(i) for i in value],
),
),
Widget.Separator(css_classes=["wifi-network-list-separator"]),
Widget.Button(
css_classes=["wifi-network-item", "unset"],
on_click=lambda x: Utils.exec_sh_async("nm-connection-editor"),
style="margin-bottom: 0;",
child=Widget.Box(
child=[
Widget.Icon(image="preferences-system-symbolic"),
Widget.Label(
label="Network Settings",
halign="start",
css_classes=["wifi-network-label"],
),
]
),
),
],
),
)
def get_label(ssid: str) -> str:
if ssid:
return ssid
else:
return "Wi-Fi"
def get_icon(icon_name: str) -> str:
if device.ap.is_connected:
return icon_name
else:
return "network-wireless-symbolic"
def toggle_list(x) -> None:
device.scan()
networks_list.toggle()
return QSButton(
label=device.ap.bind("ssid", get_label),
icon_name=device.ap.bind("icon-name", get_icon),
on_activate=toggle_list,
on_deactivate=toggle_list,
active=network.wifi.bind("enabled"),
content=networks_list,
)
def wifi_control() -> List[QSButton]:
return [wifi_qsbutton(dev) for dev in network.wifi.devices]
@@ -0,0 +1,212 @@
import re
from ignis.widgets import Widget
from ignis.app import IgnisApp
from ignis.services.applications import (
ApplicationsService,
Application,
ApplicationAction,
)
from ignis.utils import Utils
from gi.repository import Gio # type: ignore
app = IgnisApp.get_default()
applications = ApplicationsService.get_default()
def is_url(url: str) -> bool:
regex = re.compile(
r"^(?:http|ftp)s?://" # http:// or https://
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain
r"localhost|" # localhost
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|" # or ipv4
r"\[?[A-F0-9]*:[A-F0-9:]+\]?)" # or ipv6
r"(?::\d+)?" # optional port
r"(?:/?|[/?]\S+)$",
re.IGNORECASE,
)
return re.match(regex, url) is not None
class LauncherAppItem(Widget.Button):
def __init__(self, application: Application) -> None:
self._application = application
super().__init__(
on_click=lambda x: self.launch(),
on_right_click=lambda x: self._menu.popup(),
css_classes=["launcher-app"],
child=Widget.Box(
child=[
Widget.Icon(image=application.icon, pixel_size=48),
Widget.Label(
label=application.name,
ellipsize="end",
max_width_chars=30,
css_classes=["launcher-app-label"],
),
]
),
)
self.__sync_menu()
application.connect("notify::is-pinned", lambda x, y: self.__sync_menu())
def launch(self) -> None:
self._application.launch()
app.close_window("ignis_LAUNCHER")
def launch_action(self, action: ApplicationAction) -> None:
action.launch()
app.close_window("ignis_LAUNCHER")
def __sync_menu(self) -> None:
self._menu = Widget.PopoverMenu(
items=[
Widget.MenuItem(label="Launch", on_activate=lambda x: self.launch()),
Widget.Separator(),
]
+ [
Widget.MenuItem(
label=i.name,
on_activate=lambda x, action=i: self.launch_action(action),
)
for i in self._application.actions
]
+ [
Widget.Separator(),
Widget.MenuItem(
label="Pin", on_activate=lambda x: self._application.pin()
)
if not self._application.is_pinned
else Widget.MenuItem(
label="Unpin", on_activate=lambda x: self._application.unpin()
),
]
)
self.child.append(self._menu)
class SearchWebButton(Widget.Button):
def __init__(self, query: str):
self._query = query
self._url = ""
browser_desktop_file = Utils.exec_sh(
"xdg-settings get default-web-browser"
).stdout.replace("\n", "")
app_info = Gio.DesktopAppInfo.new(desktop_id=browser_desktop_file)
icon_name = "applications-internet-symbolic"
if app_info:
icon_string = app_info.get_string("Icon")
if icon_string:
icon_name = icon_string
if not query.startswith(("http://", "https://")) and "." in query:
query = "https://" + query
if is_url(query):
label = f"Visit {query}"
self._url = query
else:
label = "Search in Google"
self._url = f"https://www.google.com/search?q={query.replace(' ', '+')}"
super().__init__(
on_click=lambda x: self.launch(),
css_classes=["launcher-app"],
child=Widget.Box(
child=[
Widget.Icon(image=icon_name, pixel_size=48),
Widget.Label(
label=label,
css_classes=["launcher-app-label"],
),
]
),
)
def launch(self) -> None:
Utils.exec_sh_async(f"xdg-open {self._url}")
app.close_window("ignis_LAUNCHER")
def launcher() -> Widget.Window:
def search(entry: Widget.Entry, app_list: Widget.Box) -> None:
query = entry.text
if query == "":
entry.grab_focus()
app_list.visible = False
return
apps = applications.search(applications.apps, query)
if apps == []:
app_list.child = [SearchWebButton(query)]
else:
app_list.visible = True
app_list.child = [LauncherAppItem(i) for i in apps[:5]]
def on_open(window: Widget.Window, entry: Widget.Entry) -> None:
if not window.visible:
return
entry.text = ""
entry.grab_focus()
app_list = Widget.Box(vertical=True, visible=False, style="margin-top: 1rem;")
entry = Widget.Entry(
hexpand=True,
placeholder_text="Search",
css_classes=["launcher-search"],
on_change=lambda x: search(x, app_list),
on_accept=lambda x: app_list.child[0].launch()
if len(app_list.child) > 0
else None,
)
main_box = Widget.Box(
vertical=True,
valign="start",
halign="center",
css_classes=["launcher"],
child=[
Widget.Box(
css_classes=["launcher-search-box"],
child=[
Widget.Icon(
icon_name="system-search-symbolic",
pixel_size=24,
style="margin-right: 0.5rem;",
),
entry,
],
),
app_list,
],
)
return Widget.Window(
namespace="ignis_LAUNCHER",
visible=False,
popup=True,
kb_mode="on_demand",
# exclusivity="ignore",
css_classes=["unset"],
setup=lambda self: self.connect(
"notify::visible", lambda x, y: on_open(self, entry)
),
anchor=["top", "right", "bottom", "left"],
child=Widget.Overlay(
child=Widget.Button(
vexpand=True,
hexpand=True,
can_focus=False,
css_classes=["unset"],
on_click=lambda x: app.close_window("ignis_LAUNCHER"),
style="background-color: rgba(0, 0, 0, 0.3);",
),
overlays=[main_box],
),
)
@@ -0,0 +1,95 @@
from ignis.widgets import Widget
from ignis.app import IgnisApp
from ignis.utils import Utils
from ignis.services.notifications import Notification, NotificationService
from ..control_center.notification_center import NotificationWidget
app = IgnisApp.get_default()
notifications = NotificationService.get_default()
class Popup(Widget.Box):
def __init__(self, notification: Notification):
widget = NotificationWidget(notification)
widget.css_classes = ["notification-popup"]
self._inner = Widget.Revealer(transition_type="slide_left", child=widget)
self._outer = Widget.Revealer(transition_type="slide_down", child=self._inner)
super().__init__(child=[self._outer], halign="end")
notification.connect("dismissed", lambda x: self.destroy())
def destroy(self):
def box_destroy():
box: Widget.Box = self.get_parent() # type: ignore
if not box:
return
self.unparent()
if len(notifications.popups) == 0:
window: Widget.Window = box.get_parent() # type: ignore
if not window:
return
window.visible = False
else:
change_window_input_region(box)
def outer_close():
self._outer.reveal_child = False
Utils.Timeout(self._outer.transition_duration, box_destroy)
self._inner.transition_type = "crossfade"
self._inner.reveal_child = False
Utils.Timeout(self._outer.transition_duration, outer_close)
def on_notified(box: Widget.Box, notification: Notification, monitor: int) -> None:
window: Widget.Window = app.get_window("ignis_CONTROL_CENTER") # type: ignore
if window.visible and window.monitor == monitor:
return
app.open_window(f"ignis_NOTIFICATION_POPUP_{monitor}")
popup = Popup(notification)
box.prepend(popup)
popup._outer.reveal_child = True
Utils.Timeout(popup._outer.transition_duration, reveal_popup, box, popup)
def reveal_popup(box: Widget.Box, popup: Popup) -> None:
popup._inner.set_reveal_child(True)
change_window_input_region(box)
def change_window_input_region(box: Widget.Box) -> None:
def callback() -> None:
width = box.get_width()
height = box.get_height()
window: Widget.Window = box.get_parent() # type: ignore
window.input_width = width
window.input_height = height
Utils.Timeout(ms=50, target=callback)
def notification_popup(monitor: int) -> Widget.Window:
notifications_box = Widget.Box(
vertical=True,
valign="start",
setup=lambda self: notifications.connect(
"new_popup",
lambda x, notification: on_notified(self, notification, monitor),
),
)
return Widget.Window(
anchor=["right", "top", "bottom"],
monitor=monitor,
namespace=f"ignis_NOTIFICATION_POPUP_{monitor}",
layer="top",
child=notifications_box,
visible=False,
css_classes=["rec-unset"],
style="min-width: 29rem;",
)
@@ -0,0 +1,43 @@
from ignis.widgets import Widget
from ignis.utils import Utils
from ignis.services.audio import AudioService
audio = AudioService.get_default()
class OSD(Widget.Window):
def __init__(self):
self._timer = None
super().__init__(
layer="overlay",
anchor=["bottom"],
namespace="ignis_OSD",
visible=False,
css_classes=["rec-unset"],
child=Widget.Box(
css_classes=["osd"],
child=[
Widget.Icon(
pixel_size=26,
style="margin-right: 0.5rem;",
image=audio.speaker.bind("icon_name"),
),
Widget.Scale(
value=audio.speaker.bind("volume"),
css_classes=["material-slider"],
sensitive=False,
hexpand=True,
),
],
),
)
def set_property(self, property_name, value):
if property_name == "visible":
if self._timer:
self._timer.cancel()
self._timer = None
self._timer = Utils.Timeout(3000, self.set_visible, False)
super().set_property(property_name, value)
@@ -0,0 +1,98 @@
from ignis.widgets import Widget
from ignis.utils import Utils
from ignis.app import IgnisApp
from typing import Callable
app = IgnisApp.get_default()
class PowermenuButton(Widget.Box):
def __init__(self, label: str, icon_name: str, on_click: Callable) -> None:
super().__init__(
child=[
Widget.Button(
child=Widget.Icon(image=icon_name, pixel_size=36),
on_click=on_click,
css_classes=["powermenu-button", "unset"],
),
Widget.Label(label=label, css_classes=["powermenu-button-label"]),
],
vertical=True,
css_classes=["powermenu-button-box"],
)
def poweroff(*args) -> None:
Utils.exec_sh_async("poweroff")
def reboot(*args) -> None:
Utils.exec_sh_async("reboot")
def suspend(*args) -> None:
app.close_window("ignis_POWERMENU")
Utils.exec_sh_async("systemctl suspend && hyprlock")
def hypr_exit(*args) -> None:
Utils.exec_sh_async("hyprctl dispatch exit 0")
def powermenu():
return Widget.Window(
popup=True,
kb_mode="on_demand",
namespace="ignis_POWERMENU",
exclusivity="ignore",
anchor=["left", "right", "top", "bottom"],
visible=False,
child=Widget.Overlay(
child=Widget.Button(
vexpand=True,
hexpand=True,
can_focus=False,
css_classes=["unset", "powermenu-overlay"],
on_click=lambda x: app.close_window("ignis_POWERMENU"),
),
overlays=[
Widget.Box(
vertical=True,
valign="center",
halign="center",
css_classes=["powermenu"],
child=[
Widget.Box(
child=[
PowermenuButton(
label="Power off",
icon_name="system-shutdown-symbolic",
on_click=poweroff,
),
PowermenuButton(
label="Reboot",
icon_name="system-reboot-symbolic",
on_click=reboot,
),
]
),
Widget.Box(
child=[
PowermenuButton(
label="Suspend",
icon_name="night-light-symbolic",
on_click=suspend,
),
PowermenuButton(
label="Sign out",
icon_name="system-log-out-symbolic",
on_click=hypr_exit,
),
]
),
],
)
],
),
css_classes=["unset"],
)
@@ -0,0 +1,83 @@
from ignis.widgets import Widget
from ignis.gobject import IgnisGObject
from gi.repository import GObject # type: ignore
from .notifications import notifications_entry
from .about import about_entry
from .appearance import appearance_entry
from .recorder import recorder_entry
from .user import user_entry
from .elements import SettingsPage
from ignis.app import IgnisApp
from ignis.exceptions import WindowNotFoundError
from options import settings_last_page
app = IgnisApp.get_default()
class ActivePage(IgnisGObject):
def __init__(self, name: str | None, page: SettingsPage | None):
super().__init__()
self._name = name
self._page = page
@GObject.Property
def name(self) -> str | None:
return self._name
@name.setter
def name(self, value: str | None) -> None:
self._name = value
@GObject.Property
def page(self) -> SettingsPage | None:
return self._page
@page.setter
def page(self, value: SettingsPage | None) -> None:
self._page = value
def settings_widget():
active_page = ActivePage(name="Settings", page=None)
content = Widget.Box(
hexpand=True,
vexpand=True,
child=active_page.bind("page", transform=lambda value: [value]),
)
listbox = Widget.ListBox(
rows=[
notifications_entry(active_page),
recorder_entry(active_page),
appearance_entry(active_page),
user_entry(active_page),
about_entry(active_page),
],
)
listbox.select_row(listbox.rows[settings_last_page.value])
navigation_sidebar = Widget.Box(
vertical=True,
css_classes=["settings-sidebar"],
child=[
Widget.Label(
label="Settings", halign="start", css_classes=["settings-sidebar-label"]
),
listbox,
],
)
return Widget.Box(child=[navigation_sidebar, content])
def settings_window():
try:
app.get_window("ignis_SETTINGS")
except WindowNotFoundError:
return Widget.RegularWindow(
default_width=900,
default_height=600,
resizable=False,
child=settings_widget(),
namespace="ignis_SETTINGS",
)

Some files were not shown because too many files have changed in this diff Show More