commit cbe4355c3d89fcd2cd5eb8bfdff353928cf57486 Author: Ano-sys Date: Wed Jan 15 01:39:27 2025 +0100 Backup dotfiles diff --git a/hypr/hyprland.conf b/hypr/hyprland.conf new file mode 100644 index 0000000..fff9c5a --- /dev/null +++ b/hypr/hyprland.conf @@ -0,0 +1,311 @@ +#----------------------------------------------------------------------------------------------------------- +# 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 = 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 + } + + 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 + passes = 4 + new_optimizations = true + } + + shadow { + enabled = true + range = 30 + render_power = 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 + + 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 +} + +#----------------------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)$ + +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 + diff --git a/hypr/hyprlock-time.sh b/hypr/hyprlock-time.sh new file mode 100644 index 0000000..97ddd45 --- /dev/null +++ b/hypr/hyprlock-time.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +current_hour=$(date +"%H") +user_string="$USER" + +if [ "$current_hour" -ge 5 ] && [ "$current_hour" -lt 12 ]; then + echo "Good morning, $user_string" +elif [ "$current_hour" -ge 12 ] && [ "$current_hour" -lt 18 ]; then + echo "Good day, $user_string" +elif [ "$current_hour" -ge 18 ] && [ "$current_hour" -lt 22 ]; then + echo "Good evening, $user_string" +else + echo "Good night, $user_string" +fi diff --git a/hypr/hyprlock.conf b/hypr/hyprlock.conf new file mode 100644 index 0000000..df35bf3 --- /dev/null +++ b/hypr/hyprlock.conf @@ -0,0 +1,82 @@ +source=~/.cache/ignis/material/dark_colors-hyprland.conf + +# BACKGROUND +background { + monitor = + path = ~/.cache/ignis/wallpaper + blur_passes = 2 + contrast = 0.9 + brightness = 0.5 + vibrancy = 0.17 + vibrancy_darkness = 0 +} + +# GENERAL +general { + disable_loading_bar = true +} + +# INPUT FIELD +input-field { + monitor = + size = 300, 40 + outline_thickness = 2 + dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 + dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 + dots_center = true + outer_color = $surface + inner_color = $surface + font_color = $onSurface + fade_on_empty = false + placeholder_text = + hide_input = false + position = 0, 150 + halign = center + valign = bottom +} + +# Hour-Time +label { + monitor = + text = cmd[update:1000] echo -e "$(date +"%H")" + color = $primary + font_family = JetBrainsMono Bold + font_size = 180 + position = 0, 150 + halign = center + valign = center +} + +# Minute-Time +label { + monitor = + text = cmd[update:1000] echo -e "$(date +"%M")" + color = $onSurface + font_family = JetBrainsMono Bold + font_size = 180 + position = 0, -75 + halign = center + valign = center +} + +# Date +label { + monitor = + text = cmd[update:1000] echo -e "$(date +"%a, %b %d")" + color = $onSurface + font_family = JetBrainsMono Bold + position = 100, -100 + halign = left + valign = top +} + +# Date +label { + monitor = + text = cmd[update:1000] primaryHex=$primaryHex bash ~/.config/hypr/scripts/hyprlock-time.sh + color = $onSurface + font_family = JetBrainsMono Bold + position = 100, -130 + halign = left + valign = top +} \ No newline at end of file diff --git a/hypr/hyprlock.conf.bak b/hypr/hyprlock.conf.bak new file mode 100644 index 0000000..5f02c68 --- /dev/null +++ b/hypr/hyprlock.conf.bak @@ -0,0 +1,89 @@ +# BACKGROUND +background { + monitor = + path = screenshot + blur_passes = 2 + contrast = 0.8916 + brightness = 0.8172 + vibrancy = 0.1696 + vibrancy_darkness = 0.0 +} + +# GENERAL +general { + no_fade_in = false + grace = 0 + disable_loading_bar = true +} + +# INPUT FIELD +input-field { + monitor = + size = 250, 60 + outline_thickness = 2 + dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 + dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 + dots_center = true +# outer_color = rgba(0, 0, 0, 0) + inner_color = rgba(0, 0, 0, 0.5) + font_color = rgb(200, 200, 200) + fade_on_empty = true + font_family = JetBrains Mono Nerd Font Mono +# placeholder_text = Password + hide_input = false + position = 0, -140 + halign = center + valign = center +} + +# TIME +label { + monitor = +# text = cmd[update:1000] echo "$(date +"%-I:%M%p")" + text = cmd[update:1000] echo " $(date +"%I:%M:%S %p") " + color = $foreground + #color = rgba(255, 255, 255, 0.6) + font_size = 120 + font_family = JetBrains Mono Nerd Font Mono ExtraBold + position = 0, -300 + halign = center + valign = top +} + +# DATE +label { + monitor = + text = cmd[update:18000000] echo " "$(date +'%A, %-d %B %Y')" " + color = $color12 + font_size = 34 + font_family = JetBrains Mono Nerd Font 10 + + position = 0, -150 + halign = center + valign = top +} + +# WEEK +label { + monitor = + text = cmd[update:18000000] echo " "$(date +'Week %U')" " + color = $color5 + font_size = 24 + font_family = JetBrains Mono Nerd Font 10 + position = 0, -250 + halign = center + valign = top +} + +# USER +label { + monitor = + text = Hi there, $USER + color = $foreground + #color = rgba(255, 255, 255, 0.6) + font_size = 30 + font_family = JetBrains Mono Nerd Font Mono + position = 0, -400 + halign = center + valign = center +} diff --git a/hypr/linux-wallpaperengine/chrome-sandbox b/hypr/linux-wallpaperengine/chrome-sandbox new file mode 100644 index 0000000..dd6bea4 Binary files /dev/null and b/hypr/linux-wallpaperengine/chrome-sandbox differ diff --git a/hypr/linux-wallpaperengine/chrome_100_percent.pak b/hypr/linux-wallpaperengine/chrome_100_percent.pak new file mode 100644 index 0000000..8f930a5 Binary files /dev/null and b/hypr/linux-wallpaperengine/chrome_100_percent.pak differ diff --git a/hypr/linux-wallpaperengine/chrome_200_percent.pak b/hypr/linux-wallpaperengine/chrome_200_percent.pak new file mode 100644 index 0000000..22cbef4 Binary files /dev/null and b/hypr/linux-wallpaperengine/chrome_200_percent.pak differ diff --git a/hypr/linux-wallpaperengine/debug.log b/hypr/linux-wallpaperengine/debug.log new file mode 100644 index 0000000..d7a0ad4 --- /dev/null +++ b/hypr/linux-wallpaperengine/debug.log @@ -0,0 +1,79 @@ +[0112/021300.720084:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021300.870860:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021300.870888:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021300.870896:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/021301.355147:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021343.382965:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021343.525628:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021343.525659:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021343.525668:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0100/000000.039064:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021355.430770:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021355.560402:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021355.560426:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021355.560432:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0100/000000.049808:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021412.724550:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021412.870056:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021412.870093:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021412.870102:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/021413.450074:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021524.544984:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021524.688350:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021524.688376:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021524.688382:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/021525.254049:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021628.633179:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021628.774640:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021628.774668:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021628.774674:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0100/000000.276965:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021635.903220:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021636.049056:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021636.049086:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021636.049095:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0100/000000.530886:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/021956.965069:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/021957.097094:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/021957.097121:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/021957.097129:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/021957.624946:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/022033.369689:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/022033.499608:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/022033.499633:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/022033.499639:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/022034.083089:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/022214.694138:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/022214.834446:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/022214.834472:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/022214.834480:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/022215.403212:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/022606.435001:ERROR:icu_util.cc(240)] Invalid file descriptor to ICU data received. +[0112/022642.499976:ERROR:icu_util.cc(240)] Invalid file descriptor to ICU data received. +[0112/022735.719527:ERROR:icu_util.cc(240)] Invalid file descriptor to ICU data received. +[0112/022742.392757:ERROR:icu_util.cc(240)] Invalid file descriptor to ICU data received. +[0112/022755.970339:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/022756.118652:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/022756.118686:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/022756.118696:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/022756.705851:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/022825.500775:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/022825.623444:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/022825.623465:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/022825.623470:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/022826.159575:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/023053.534711:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/023053.656423:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/023053.656448:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/023053.656453:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/023054.158978:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/025924.491936:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/025924.643309:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/025924.643333:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/025924.643440:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/025925.131623:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. +[0112/030231.707148:WARNING:resource_util.cc(94)] Please customize CefSettings.root_cache_path for your application. Use of the default value may lead to unintended process singleton behavior. +[0112/030231.835773:ERROR:object_proxy.cc(577)] Failed to call method: org.freedesktop.DBus.Properties.Get: object_path= /org/freedesktop/portal/desktop: org.freedesktop.DBus.Error.InvalidArgs: Keine derartige Schnittstelle »org.freedesktop.portal.FileChooser« +[0112/030231.835797:WARNING:property.cc(144)] version: GetAndBlock: failed. +[0112/030231.835802:ERROR:select_file_dialog_linux_portal.cc(285)] Failed to read portal version property +[0112/030232.324862:WARNING:sandbox_linux.cc(400)] InitializeSandbox() called with multiple threads in process gpu-process. diff --git a/hypr/linux-wallpaperengine/icudtl.dat b/hypr/linux-wallpaperengine/icudtl.dat new file mode 100644 index 0000000..4d7e9ac Binary files /dev/null and b/hypr/linux-wallpaperengine/icudtl.dat differ diff --git a/hypr/linux-wallpaperengine/libEGL.so b/hypr/linux-wallpaperengine/libEGL.so new file mode 100644 index 0000000..98baa8a Binary files /dev/null and b/hypr/linux-wallpaperengine/libEGL.so differ diff --git a/hypr/linux-wallpaperengine/libGLESv2.so b/hypr/linux-wallpaperengine/libGLESv2.so new file mode 100644 index 0000000..4dc64e0 Binary files /dev/null and b/hypr/linux-wallpaperengine/libGLESv2.so differ diff --git a/hypr/linux-wallpaperengine/libcef.so b/hypr/linux-wallpaperengine/libcef.so new file mode 100644 index 0000000..218c7a7 Binary files /dev/null and b/hypr/linux-wallpaperengine/libcef.so differ diff --git a/hypr/linux-wallpaperengine/libvk_swiftshader.so b/hypr/linux-wallpaperengine/libvk_swiftshader.so new file mode 100644 index 0000000..eb401a2 Binary files /dev/null and b/hypr/linux-wallpaperengine/libvk_swiftshader.so differ diff --git a/hypr/linux-wallpaperengine/linux-wallpaperengine b/hypr/linux-wallpaperengine/linux-wallpaperengine new file mode 100644 index 0000000..961f563 Binary files /dev/null and b/hypr/linux-wallpaperengine/linux-wallpaperengine differ diff --git a/hypr/linux-wallpaperengine/locales/af.pak b/hypr/linux-wallpaperengine/locales/af.pak new file mode 100644 index 0000000..185d5ae Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/af.pak differ diff --git a/hypr/linux-wallpaperengine/locales/am.pak b/hypr/linux-wallpaperengine/locales/am.pak new file mode 100644 index 0000000..dbe0923 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/am.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ar.pak b/hypr/linux-wallpaperengine/locales/ar.pak new file mode 100644 index 0000000..d74bd8f Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ar.pak differ diff --git a/hypr/linux-wallpaperengine/locales/bg.pak b/hypr/linux-wallpaperengine/locales/bg.pak new file mode 100644 index 0000000..09c9160 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/bg.pak differ diff --git a/hypr/linux-wallpaperengine/locales/bn.pak b/hypr/linux-wallpaperengine/locales/bn.pak new file mode 100644 index 0000000..69e4c71 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/bn.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ca.pak b/hypr/linux-wallpaperengine/locales/ca.pak new file mode 100644 index 0000000..06bf957 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ca.pak differ diff --git a/hypr/linux-wallpaperengine/locales/cs.pak b/hypr/linux-wallpaperengine/locales/cs.pak new file mode 100644 index 0000000..4ca9426 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/cs.pak differ diff --git a/hypr/linux-wallpaperengine/locales/da.pak b/hypr/linux-wallpaperengine/locales/da.pak new file mode 100644 index 0000000..55a36bb Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/da.pak differ diff --git a/hypr/linux-wallpaperengine/locales/de.pak b/hypr/linux-wallpaperengine/locales/de.pak new file mode 100644 index 0000000..2b1812e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/de.pak differ diff --git a/hypr/linux-wallpaperengine/locales/el.pak b/hypr/linux-wallpaperengine/locales/el.pak new file mode 100644 index 0000000..fa6802e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/el.pak differ diff --git a/hypr/linux-wallpaperengine/locales/en-GB.pak b/hypr/linux-wallpaperengine/locales/en-GB.pak new file mode 100644 index 0000000..30fed16 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/en-GB.pak differ diff --git a/hypr/linux-wallpaperengine/locales/en-US.pak b/hypr/linux-wallpaperengine/locales/en-US.pak new file mode 100644 index 0000000..917f5d8 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/en-US.pak differ diff --git a/hypr/linux-wallpaperengine/locales/es-419.pak b/hypr/linux-wallpaperengine/locales/es-419.pak new file mode 100644 index 0000000..edf3f13 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/es-419.pak differ diff --git a/hypr/linux-wallpaperengine/locales/es.pak b/hypr/linux-wallpaperengine/locales/es.pak new file mode 100644 index 0000000..06ba3f9 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/es.pak differ diff --git a/hypr/linux-wallpaperengine/locales/et.pak b/hypr/linux-wallpaperengine/locales/et.pak new file mode 100644 index 0000000..7518782 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/et.pak differ diff --git a/hypr/linux-wallpaperengine/locales/fa.pak b/hypr/linux-wallpaperengine/locales/fa.pak new file mode 100644 index 0000000..bca1f46 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/fa.pak differ diff --git a/hypr/linux-wallpaperengine/locales/fi.pak b/hypr/linux-wallpaperengine/locales/fi.pak new file mode 100644 index 0000000..3e0576e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/fi.pak differ diff --git a/hypr/linux-wallpaperengine/locales/fil.pak b/hypr/linux-wallpaperengine/locales/fil.pak new file mode 100644 index 0000000..688d93e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/fil.pak differ diff --git a/hypr/linux-wallpaperengine/locales/fr.pak b/hypr/linux-wallpaperengine/locales/fr.pak new file mode 100644 index 0000000..a5e8df3 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/fr.pak differ diff --git a/hypr/linux-wallpaperengine/locales/gu.pak b/hypr/linux-wallpaperengine/locales/gu.pak new file mode 100644 index 0000000..661ba02 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/gu.pak differ diff --git a/hypr/linux-wallpaperengine/locales/he.pak b/hypr/linux-wallpaperengine/locales/he.pak new file mode 100644 index 0000000..1513534 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/he.pak differ diff --git a/hypr/linux-wallpaperengine/locales/hi.pak b/hypr/linux-wallpaperengine/locales/hi.pak new file mode 100644 index 0000000..edc3cab Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/hi.pak differ diff --git a/hypr/linux-wallpaperengine/locales/hr.pak b/hypr/linux-wallpaperengine/locales/hr.pak new file mode 100644 index 0000000..c3d9ee7 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/hr.pak differ diff --git a/hypr/linux-wallpaperengine/locales/hu.pak b/hypr/linux-wallpaperengine/locales/hu.pak new file mode 100644 index 0000000..77ae205 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/hu.pak differ diff --git a/hypr/linux-wallpaperengine/locales/id.pak b/hypr/linux-wallpaperengine/locales/id.pak new file mode 100644 index 0000000..a787121 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/id.pak differ diff --git a/hypr/linux-wallpaperengine/locales/it.pak b/hypr/linux-wallpaperengine/locales/it.pak new file mode 100644 index 0000000..f4f0fd9 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/it.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ja.pak b/hypr/linux-wallpaperengine/locales/ja.pak new file mode 100644 index 0000000..0476896 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ja.pak differ diff --git a/hypr/linux-wallpaperengine/locales/kn.pak b/hypr/linux-wallpaperengine/locales/kn.pak new file mode 100644 index 0000000..1e8dca4 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/kn.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ko.pak b/hypr/linux-wallpaperengine/locales/ko.pak new file mode 100644 index 0000000..d2ccff6 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ko.pak differ diff --git a/hypr/linux-wallpaperengine/locales/lt.pak b/hypr/linux-wallpaperengine/locales/lt.pak new file mode 100644 index 0000000..16ec747 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/lt.pak differ diff --git a/hypr/linux-wallpaperengine/locales/lv.pak b/hypr/linux-wallpaperengine/locales/lv.pak new file mode 100644 index 0000000..09a5a3e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/lv.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ml.pak b/hypr/linux-wallpaperengine/locales/ml.pak new file mode 100644 index 0000000..a8e3354 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ml.pak differ diff --git a/hypr/linux-wallpaperengine/locales/mr.pak b/hypr/linux-wallpaperengine/locales/mr.pak new file mode 100644 index 0000000..96eb439 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/mr.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ms.pak b/hypr/linux-wallpaperengine/locales/ms.pak new file mode 100644 index 0000000..6a44ce6 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ms.pak differ diff --git a/hypr/linux-wallpaperengine/locales/nb.pak b/hypr/linux-wallpaperengine/locales/nb.pak new file mode 100644 index 0000000..584da4e Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/nb.pak differ diff --git a/hypr/linux-wallpaperengine/locales/nl.pak b/hypr/linux-wallpaperengine/locales/nl.pak new file mode 100644 index 0000000..acb4388 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/nl.pak differ diff --git a/hypr/linux-wallpaperengine/locales/pl.pak b/hypr/linux-wallpaperengine/locales/pl.pak new file mode 100644 index 0000000..cb83f39 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/pl.pak differ diff --git a/hypr/linux-wallpaperengine/locales/pt-BR.pak b/hypr/linux-wallpaperengine/locales/pt-BR.pak new file mode 100644 index 0000000..02dd1f4 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/pt-BR.pak differ diff --git a/hypr/linux-wallpaperengine/locales/pt-PT.pak b/hypr/linux-wallpaperengine/locales/pt-PT.pak new file mode 100644 index 0000000..d55f2db Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/pt-PT.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ro.pak b/hypr/linux-wallpaperengine/locales/ro.pak new file mode 100644 index 0000000..cde47af Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ro.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ru.pak b/hypr/linux-wallpaperengine/locales/ru.pak new file mode 100644 index 0000000..0fec087 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ru.pak differ diff --git a/hypr/linux-wallpaperengine/locales/sk.pak b/hypr/linux-wallpaperengine/locales/sk.pak new file mode 100644 index 0000000..23f9890 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/sk.pak differ diff --git a/hypr/linux-wallpaperengine/locales/sl.pak b/hypr/linux-wallpaperengine/locales/sl.pak new file mode 100644 index 0000000..d6024da Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/sl.pak differ diff --git a/hypr/linux-wallpaperengine/locales/sr.pak b/hypr/linux-wallpaperengine/locales/sr.pak new file mode 100644 index 0000000..1ebbd50 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/sr.pak differ diff --git a/hypr/linux-wallpaperengine/locales/sv.pak b/hypr/linux-wallpaperengine/locales/sv.pak new file mode 100644 index 0000000..16ce26d Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/sv.pak differ diff --git a/hypr/linux-wallpaperengine/locales/sw.pak b/hypr/linux-wallpaperengine/locales/sw.pak new file mode 100644 index 0000000..435602d Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/sw.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ta.pak b/hypr/linux-wallpaperengine/locales/ta.pak new file mode 100644 index 0000000..762dc33 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ta.pak differ diff --git a/hypr/linux-wallpaperengine/locales/te.pak b/hypr/linux-wallpaperengine/locales/te.pak new file mode 100644 index 0000000..6192c5c Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/te.pak differ diff --git a/hypr/linux-wallpaperengine/locales/th.pak b/hypr/linux-wallpaperengine/locales/th.pak new file mode 100644 index 0000000..0d4d792 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/th.pak differ diff --git a/hypr/linux-wallpaperengine/locales/tr.pak b/hypr/linux-wallpaperengine/locales/tr.pak new file mode 100644 index 0000000..4b77eac Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/tr.pak differ diff --git a/hypr/linux-wallpaperengine/locales/uk.pak b/hypr/linux-wallpaperengine/locales/uk.pak new file mode 100644 index 0000000..cf5f94c Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/uk.pak differ diff --git a/hypr/linux-wallpaperengine/locales/ur.pak b/hypr/linux-wallpaperengine/locales/ur.pak new file mode 100644 index 0000000..cb406f6 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/ur.pak differ diff --git a/hypr/linux-wallpaperengine/locales/vi.pak b/hypr/linux-wallpaperengine/locales/vi.pak new file mode 100644 index 0000000..d15f8ee Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/vi.pak differ diff --git a/hypr/linux-wallpaperengine/locales/zh-CN.pak b/hypr/linux-wallpaperengine/locales/zh-CN.pak new file mode 100644 index 0000000..1fd25e0 Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/zh-CN.pak differ diff --git a/hypr/linux-wallpaperengine/locales/zh-TW.pak b/hypr/linux-wallpaperengine/locales/zh-TW.pak new file mode 100644 index 0000000..fdcf53b Binary files /dev/null and b/hypr/linux-wallpaperengine/locales/zh-TW.pak differ diff --git a/hypr/linux-wallpaperengine/resources.pak b/hypr/linux-wallpaperengine/resources.pak new file mode 100644 index 0000000..10462b6 Binary files /dev/null and b/hypr/linux-wallpaperengine/resources.pak differ diff --git a/hypr/linux-wallpaperengine/snapshot_blob.bin b/hypr/linux-wallpaperengine/snapshot_blob.bin new file mode 100644 index 0000000..f934714 Binary files /dev/null and b/hypr/linux-wallpaperengine/snapshot_blob.bin differ diff --git a/hypr/linux-wallpaperengine/v8_context_snapshot.bin b/hypr/linux-wallpaperengine/v8_context_snapshot.bin new file mode 100644 index 0000000..e96eccc Binary files /dev/null and b/hypr/linux-wallpaperengine/v8_context_snapshot.bin differ diff --git a/hypr/linux-wallpaperengine/vk_swiftshader_icd.json b/hypr/linux-wallpaperengine/vk_swiftshader_icd.json new file mode 100644 index 0000000..28be1f3 --- /dev/null +++ b/hypr/linux-wallpaperengine/vk_swiftshader_icd.json @@ -0,0 +1 @@ +{"file_format_version": "1.0.0", "ICD": {"library_path": "./libvk_swiftshader.so", "api_version": "1.0.5"}} \ No newline at end of file diff --git a/hypr/mocha.conf b/hypr/mocha.conf new file mode 100644 index 0000000..8ccb56a --- /dev/null +++ b/hypr/mocha.conf @@ -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 diff --git a/hypr/monitors.conf b/hypr/monitors.conf new file mode 100644 index 0000000..4aa44f7 --- /dev/null +++ b/hypr/monitors.conf @@ -0,0 +1,5 @@ +# Generated by nwg-displays on 2024-10-22 at 21:06:35. Do not edit manually. + +monitor=DP-2,2560x1080@60.0,4484x0,1.0 +monitor=DP-3,3840x2160@60.0,3840x1080,1.0,bitdepth,10 +monitor=HDMI-A-1,3840x2160@60.0,0x1080,1.0,bitdepth,10 diff --git a/hypr/scripts/hyprlock-time.sh b/hypr/scripts/hyprlock-time.sh new file mode 100644 index 0000000..97ddd45 --- /dev/null +++ b/hypr/scripts/hyprlock-time.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +current_hour=$(date +"%H") +user_string="$USER" + +if [ "$current_hour" -ge 5 ] && [ "$current_hour" -lt 12 ]; then + echo "Good morning, $user_string" +elif [ "$current_hour" -ge 12 ] && [ "$current_hour" -lt 18 ]; then + echo "Good day, $user_string" +elif [ "$current_hour" -ge 18 ] && [ "$current_hour" -lt 22 ]; then + echo "Good evening, $user_string" +else + echo "Good night, $user_string" +fi diff --git a/hypr/scripts/volume_down.sh b/hypr/scripts/volume_down.sh new file mode 100644 index 0000000..82bcd4a --- /dev/null +++ b/hypr/scripts/volume_down.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pactl set-sink-volume @DEFAULT_SINK -5% diff --git a/hypr/scripts/volume_up.sh b/hypr/scripts/volume_up.sh new file mode 100644 index 0000000..d213fae --- /dev/null +++ b/hypr/scripts/volume_up.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pactl set-sink-volume @DEFAULT_SINK +5% diff --git a/hypr/wallpaper/wallpaper.jpg b/hypr/wallpaper/wallpaper.jpg new file mode 100644 index 0000000..19626f8 Binary files /dev/null and b/hypr/wallpaper/wallpaper.jpg differ diff --git a/hypr/wallpaper/wallpaper2.jpg b/hypr/wallpaper/wallpaper2.jpg new file mode 100644 index 0000000..d522736 Binary files /dev/null and b/hypr/wallpaper/wallpaper2.jpg differ diff --git a/hypr/workspaces.conf b/hypr/workspaces.conf new file mode 100644 index 0000000..e4504bc --- /dev/null +++ b/hypr/workspaces.conf @@ -0,0 +1,4 @@ +# Generated by nwg-displays on 2024-07-19 at 14:06:57. Do not edit manually. + +workspace=1,monitor:DP-3,default:true +workspace=2,monitor:DP-2,default:true diff --git a/ignis/__init__.py b/ignis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ignis/__pycache__/check_version.cpython-312.pyc b/ignis/__pycache__/check_version.cpython-312.pyc new file mode 100644 index 0000000..a7bbbcd Binary files /dev/null and b/ignis/__pycache__/check_version.cpython-312.pyc differ diff --git a/ignis/__pycache__/check_version.cpython-313.pyc b/ignis/__pycache__/check_version.cpython-313.pyc new file mode 100644 index 0000000..2cd7164 Binary files /dev/null and b/ignis/__pycache__/check_version.cpython-313.pyc differ diff --git a/ignis/__pycache__/config.cpython-312.pyc b/ignis/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..b91d3c3 Binary files /dev/null and b/ignis/__pycache__/config.cpython-312.pyc differ diff --git a/ignis/__pycache__/config.cpython-313.pyc b/ignis/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000..8bc7e8e Binary files /dev/null and b/ignis/__pycache__/config.cpython-313.pyc differ diff --git a/ignis/__pycache__/options.cpython-312.pyc b/ignis/__pycache__/options.cpython-312.pyc new file mode 100644 index 0000000..d6bfdcd Binary files /dev/null and b/ignis/__pycache__/options.cpython-312.pyc differ diff --git a/ignis/__pycache__/options.cpython-313.pyc b/ignis/__pycache__/options.cpython-313.pyc new file mode 100644 index 0000000..c6fc568 Binary files /dev/null and b/ignis/__pycache__/options.cpython-313.pyc differ diff --git a/ignis/check_version.py b/ignis/check_version.py new file mode 100644 index 0000000..e68e18b --- /dev/null +++ b/ignis/check_version.py @@ -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) diff --git a/ignis/config.py b/ignis/config.py new file mode 100644 index 0000000..af1361e --- /dev/null +++ b/ignis/config.py @@ -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() diff --git a/ignis/misc/media-art-fallback.png b/ignis/misc/media-art-fallback.png new file mode 100644 index 0000000..f76719b Binary files /dev/null and b/ignis/misc/media-art-fallback.png differ diff --git a/ignis/modules/__init__.py b/ignis/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ignis/modules/__pycache__/__init__.cpython-312.pyc b/ignis/modules/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2a9d474 Binary files /dev/null and b/ignis/modules/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/__pycache__/__init__.cpython-313.pyc b/ignis/modules/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..6b67587 Binary files /dev/null and b/ignis/modules/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/bar/__init__.py b/ignis/modules/bar/__init__.py new file mode 100644 index 0000000..a167d8f --- /dev/null +++ b/ignis/modules/bar/__init__.py @@ -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"], + ) diff --git a/ignis/modules/bar/__pycache__/__init__.cpython-312.pyc b/ignis/modules/bar/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d750549 Binary files /dev/null and b/ignis/modules/bar/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/__init__.cpython-313.pyc b/ignis/modules/bar/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..a18b492 Binary files /dev/null and b/ignis/modules/bar/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/battery.cpython-312.pyc b/ignis/modules/bar/__pycache__/battery.cpython-312.pyc new file mode 100644 index 0000000..0160378 Binary files /dev/null and b/ignis/modules/bar/__pycache__/battery.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/battery.cpython-313.pyc b/ignis/modules/bar/__pycache__/battery.cpython-313.pyc new file mode 100644 index 0000000..2d45190 Binary files /dev/null and b/ignis/modules/bar/__pycache__/battery.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/clock.cpython-312.pyc b/ignis/modules/bar/__pycache__/clock.cpython-312.pyc new file mode 100644 index 0000000..178a246 Binary files /dev/null and b/ignis/modules/bar/__pycache__/clock.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/clock.cpython-313.pyc b/ignis/modules/bar/__pycache__/clock.cpython-313.pyc new file mode 100644 index 0000000..84ebb0a Binary files /dev/null and b/ignis/modules/bar/__pycache__/clock.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/indicator.cpython-312.pyc b/ignis/modules/bar/__pycache__/indicator.cpython-312.pyc new file mode 100644 index 0000000..59fd0dc Binary files /dev/null and b/ignis/modules/bar/__pycache__/indicator.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/indicator.cpython-313.pyc b/ignis/modules/bar/__pycache__/indicator.cpython-313.pyc new file mode 100644 index 0000000..c2078cc Binary files /dev/null and b/ignis/modules/bar/__pycache__/indicator.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/kb_layout.cpython-312.pyc b/ignis/modules/bar/__pycache__/kb_layout.cpython-312.pyc new file mode 100644 index 0000000..893e83d Binary files /dev/null and b/ignis/modules/bar/__pycache__/kb_layout.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/kb_layout.cpython-313.pyc b/ignis/modules/bar/__pycache__/kb_layout.cpython-313.pyc new file mode 100644 index 0000000..9ce3d39 Binary files /dev/null and b/ignis/modules/bar/__pycache__/kb_layout.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/pinned_apps.cpython-312.pyc b/ignis/modules/bar/__pycache__/pinned_apps.cpython-312.pyc new file mode 100644 index 0000000..642f8f7 Binary files /dev/null and b/ignis/modules/bar/__pycache__/pinned_apps.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/pinned_apps.cpython-313.pyc b/ignis/modules/bar/__pycache__/pinned_apps.cpython-313.pyc new file mode 100644 index 0000000..85a4de7 Binary files /dev/null and b/ignis/modules/bar/__pycache__/pinned_apps.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/tray.cpython-312.pyc b/ignis/modules/bar/__pycache__/tray.cpython-312.pyc new file mode 100644 index 0000000..74aea45 Binary files /dev/null and b/ignis/modules/bar/__pycache__/tray.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/tray.cpython-313.pyc b/ignis/modules/bar/__pycache__/tray.cpython-313.pyc new file mode 100644 index 0000000..304e04d Binary files /dev/null and b/ignis/modules/bar/__pycache__/tray.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/wallpaper_engine.cpython-313.pyc b/ignis/modules/bar/__pycache__/wallpaper_engine.cpython-313.pyc new file mode 100644 index 0000000..8692a99 Binary files /dev/null and b/ignis/modules/bar/__pycache__/wallpaper_engine.cpython-313.pyc differ diff --git a/ignis/modules/bar/__pycache__/workspaces.cpython-312.pyc b/ignis/modules/bar/__pycache__/workspaces.cpython-312.pyc new file mode 100644 index 0000000..a2c99fc Binary files /dev/null and b/ignis/modules/bar/__pycache__/workspaces.cpython-312.pyc differ diff --git a/ignis/modules/bar/__pycache__/workspaces.cpython-313.pyc b/ignis/modules/bar/__pycache__/workspaces.cpython-313.pyc new file mode 100644 index 0000000..648f107 Binary files /dev/null and b/ignis/modules/bar/__pycache__/workspaces.cpython-313.pyc differ diff --git a/ignis/modules/bar/battery.py b/ignis/modules/bar/battery.py new file mode 100644 index 0000000..e72d1bd --- /dev/null +++ b/ignis/modules/bar/battery.py @@ -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)) + ), + ) diff --git a/ignis/modules/bar/clock.py b/ignis/modules/bar/clock.py new file mode 100644 index 0000000..a176ba9 --- /dev/null +++ b/ignis/modules/bar/clock.py @@ -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, + ) diff --git a/ignis/modules/bar/indicator.py b/ignis/modules/bar/indicator.py new file mode 100644 index 0000000..b2d718d --- /dev/null +++ b/ignis/modules/bar/indicator.py @@ -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()] + ) diff --git a/ignis/modules/bar/kb_layout.py b/ignis/modules/bar/kb_layout.py new file mode 100644 index 0000000..b3a07a3 --- /dev/null +++ b/ignis/modules/bar/kb_layout.py @@ -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 diff --git a/ignis/modules/bar/pinned_apps.py b/ignis/modules/bar/pinned_apps.py new file mode 100644 index 0000000..bcd492c --- /dev/null +++ b/ignis/modules/bar/pinned_apps.py @@ -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()], + ) + ) diff --git a/ignis/modules/bar/tray.py b/ignis/modules/bar/tray.py new file mode 100644 index 0000000..fdbf77c --- /dev/null +++ b/ignis/modules/bar/tray.py @@ -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, + ) diff --git a/ignis/modules/bar/wallpaper_engine.py b/ignis/modules/bar/wallpaper_engine.py new file mode 100644 index 0000000..c545e47 --- /dev/null +++ b/ignis/modules/bar/wallpaper_engine.py @@ -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 diff --git a/ignis/modules/bar/workspaces.py b/ignis/modules/bar/workspaces.py new file mode 100644 index 0000000..0cf7298 --- /dev/null +++ b/ignis/modules/bar/workspaces.py @@ -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 diff --git a/ignis/modules/control_center/__init__.py b/ignis/modules/control_center/__init__.py new file mode 100644 index 0000000..285bb00 --- /dev/null +++ b/ignis/modules/control_center/__init__.py @@ -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, + ) diff --git a/ignis/modules/control_center/__pycache__/__init__.cpython-312.pyc b/ignis/modules/control_center/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..e21d8e4 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/__init__.cpython-313.pyc b/ignis/modules/control_center/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..a14a45b Binary files /dev/null and b/ignis/modules/control_center/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/brightness.cpython-312.pyc b/ignis/modules/control_center/__pycache__/brightness.cpython-312.pyc new file mode 100644 index 0000000..b92a376 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/brightness.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/brightness.cpython-313.pyc b/ignis/modules/control_center/__pycache__/brightness.cpython-313.pyc new file mode 100644 index 0000000..322b318 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/brightness.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/dark_mode.cpython-312.pyc b/ignis/modules/control_center/__pycache__/dark_mode.cpython-312.pyc new file mode 100644 index 0000000..a9a7548 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/dark_mode.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/dark_mode.cpython-313.pyc b/ignis/modules/control_center/__pycache__/dark_mode.cpython-313.pyc new file mode 100644 index 0000000..5acb0c4 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/dark_mode.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/dnd.cpython-312.pyc b/ignis/modules/control_center/__pycache__/dnd.cpython-312.pyc new file mode 100644 index 0000000..017eabe Binary files /dev/null and b/ignis/modules/control_center/__pycache__/dnd.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/dnd.cpython-313.pyc b/ignis/modules/control_center/__pycache__/dnd.cpython-313.pyc new file mode 100644 index 0000000..06e6554 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/dnd.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/ethernet.cpython-312.pyc b/ignis/modules/control_center/__pycache__/ethernet.cpython-312.pyc new file mode 100644 index 0000000..e329fe4 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/ethernet.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/ethernet.cpython-313.pyc b/ignis/modules/control_center/__pycache__/ethernet.cpython-313.pyc new file mode 100644 index 0000000..26829a4 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/ethernet.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/media.cpython-312.pyc b/ignis/modules/control_center/__pycache__/media.cpython-312.pyc new file mode 100644 index 0000000..6ec2385 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/media.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/media.cpython-313.pyc b/ignis/modules/control_center/__pycache__/media.cpython-313.pyc new file mode 100644 index 0000000..d829979 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/media.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/notification_center.cpython-312.pyc b/ignis/modules/control_center/__pycache__/notification_center.cpython-312.pyc new file mode 100644 index 0000000..8c61d5b Binary files /dev/null and b/ignis/modules/control_center/__pycache__/notification_center.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/notification_center.cpython-313.pyc b/ignis/modules/control_center/__pycache__/notification_center.cpython-313.pyc new file mode 100644 index 0000000..0e77675 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/notification_center.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/qs_button.cpython-312.pyc b/ignis/modules/control_center/__pycache__/qs_button.cpython-312.pyc new file mode 100644 index 0000000..a8f2ef6 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/qs_button.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/qs_button.cpython-313.pyc b/ignis/modules/control_center/__pycache__/qs_button.cpython-313.pyc new file mode 100644 index 0000000..5af9afd Binary files /dev/null and b/ignis/modules/control_center/__pycache__/qs_button.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/quick_settings.cpython-312.pyc b/ignis/modules/control_center/__pycache__/quick_settings.cpython-312.pyc new file mode 100644 index 0000000..04dc3be Binary files /dev/null and b/ignis/modules/control_center/__pycache__/quick_settings.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/quick_settings.cpython-313.pyc b/ignis/modules/control_center/__pycache__/quick_settings.cpython-313.pyc new file mode 100644 index 0000000..2419a45 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/quick_settings.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/record.cpython-312.pyc b/ignis/modules/control_center/__pycache__/record.cpython-312.pyc new file mode 100644 index 0000000..af3c069 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/record.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/record.cpython-313.pyc b/ignis/modules/control_center/__pycache__/record.cpython-313.pyc new file mode 100644 index 0000000..ce79176 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/record.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/user.cpython-312.pyc b/ignis/modules/control_center/__pycache__/user.cpython-312.pyc new file mode 100644 index 0000000..43da95d Binary files /dev/null and b/ignis/modules/control_center/__pycache__/user.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/user.cpython-313.pyc b/ignis/modules/control_center/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000..b076b1a Binary files /dev/null and b/ignis/modules/control_center/__pycache__/user.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/volume.cpython-312.pyc b/ignis/modules/control_center/__pycache__/volume.cpython-312.pyc new file mode 100644 index 0000000..5c73dd4 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/volume.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/volume.cpython-313.pyc b/ignis/modules/control_center/__pycache__/volume.cpython-313.pyc new file mode 100644 index 0000000..0956d7d Binary files /dev/null and b/ignis/modules/control_center/__pycache__/volume.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/vpn.cpython-312.pyc b/ignis/modules/control_center/__pycache__/vpn.cpython-312.pyc new file mode 100644 index 0000000..7701426 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/vpn.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/vpn.cpython-313.pyc b/ignis/modules/control_center/__pycache__/vpn.cpython-313.pyc new file mode 100644 index 0000000..66f09fc Binary files /dev/null and b/ignis/modules/control_center/__pycache__/vpn.cpython-313.pyc differ diff --git a/ignis/modules/control_center/__pycache__/wifi.cpython-312.pyc b/ignis/modules/control_center/__pycache__/wifi.cpython-312.pyc new file mode 100644 index 0000000..16cd6c3 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/wifi.cpython-312.pyc differ diff --git a/ignis/modules/control_center/__pycache__/wifi.cpython-313.pyc b/ignis/modules/control_center/__pycache__/wifi.cpython-313.pyc new file mode 100644 index 0000000..0ccf542 Binary files /dev/null and b/ignis/modules/control_center/__pycache__/wifi.cpython-313.pyc differ diff --git a/ignis/modules/control_center/brightness.py b/ignis/modules/control_center/brightness.py new file mode 100644 index 0000000..da513c9 --- /dev/null +++ b/ignis/modules/control_center/brightness.py @@ -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), + ), + ], + ) diff --git a/ignis/modules/control_center/dark_mode.py b/ignis/modules/control_center/dark_mode.py new file mode 100644 index 0000000..eeaeca3 --- /dev/null +++ b/ignis/modules/control_center/dark_mode.py @@ -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"), + ) diff --git a/ignis/modules/control_center/dnd.py b/ignis/modules/control_center/dnd.py new file mode 100644 index 0000000..2a01638 --- /dev/null +++ b/ignis/modules/control_center/dnd.py @@ -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"), + ) diff --git a/ignis/modules/control_center/ethernet.py b/ignis/modules/control_center/ethernet.py new file mode 100644 index 0000000..2a4e33f --- /dev/null +++ b/ignis/modules/control_center/ethernet.py @@ -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 [] diff --git a/ignis/modules/control_center/media.py b/ignis/modules/control_center/media.py new file mode 100644 index 0000000..4099ab5 --- /dev/null +++ b/ignis/modules/control_center/media.py @@ -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"], + ) diff --git a/ignis/modules/control_center/notification_center.py b/ignis/modules/control_center/notification_center.py new file mode 100644 index 0000000..2cbc6ac --- /dev/null +++ b/ignis/modules/control_center/notification_center.py @@ -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 diff --git a/ignis/modules/control_center/qs_button.py b/ignis/modules/control_center/qs_button.py new file mode 100644 index 0000000..381ddf5 --- /dev/null +++ b/ignis/modules/control_center/qs_button.py @@ -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 diff --git a/ignis/modules/control_center/quick_settings.py b/ignis/modules/control_center/quick_settings.py new file mode 100644 index 0000000..d2974dd --- /dev/null +++ b/ignis/modules/control_center/quick_settings.py @@ -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 diff --git a/ignis/modules/control_center/record.py b/ignis/modules/control_center/record.py new file mode 100644 index 0000000..b6226a4 --- /dev/null +++ b/ignis/modules/control_center/record.py @@ -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, + ) diff --git a/ignis/modules/control_center/user.py b/ignis/modules/control_center/user.py new file mode 100644 index 0000000..21ab734 --- /dev/null +++ b/ignis/modules/control_center/user.py @@ -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"], + ) diff --git a/ignis/modules/control_center/volume.py b/ignis/modules/control_center/volume.py new file mode 100644 index 0000000..bb771f5 --- /dev/null +++ b/ignis/modules/control_center/volume.py @@ -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], + ) diff --git a/ignis/modules/control_center/vpn.py b/ignis/modules/control_center/vpn.py new file mode 100644 index 0000000..d2167a4 --- /dev/null +++ b/ignis/modules/control_center/vpn.py @@ -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 [] diff --git a/ignis/modules/control_center/wifi.py b/ignis/modules/control_center/wifi.py new file mode 100644 index 0000000..70f4314 --- /dev/null +++ b/ignis/modules/control_center/wifi.py @@ -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] diff --git a/ignis/modules/launcher/__init__.py b/ignis/modules/launcher/__init__.py new file mode 100644 index 0000000..48480a1 --- /dev/null +++ b/ignis/modules/launcher/__init__.py @@ -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], + ), + ) diff --git a/ignis/modules/launcher/__pycache__/__init__.cpython-312.pyc b/ignis/modules/launcher/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..5e84f1d Binary files /dev/null and b/ignis/modules/launcher/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/launcher/__pycache__/__init__.cpython-313.pyc b/ignis/modules/launcher/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..224d862 Binary files /dev/null and b/ignis/modules/launcher/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/notification_popup/__init__.py b/ignis/modules/notification_popup/__init__.py new file mode 100644 index 0000000..8df12b3 --- /dev/null +++ b/ignis/modules/notification_popup/__init__.py @@ -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;", + ) diff --git a/ignis/modules/notification_popup/__pycache__/__init__.cpython-312.pyc b/ignis/modules/notification_popup/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..8645f05 Binary files /dev/null and b/ignis/modules/notification_popup/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/notification_popup/__pycache__/__init__.cpython-313.pyc b/ignis/modules/notification_popup/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..0ef8d4c Binary files /dev/null and b/ignis/modules/notification_popup/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/osd/__init__.py b/ignis/modules/osd/__init__.py new file mode 100644 index 0000000..a41e2d3 --- /dev/null +++ b/ignis/modules/osd/__init__.py @@ -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) diff --git a/ignis/modules/osd/__pycache__/__init__.cpython-312.pyc b/ignis/modules/osd/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..63d962d Binary files /dev/null and b/ignis/modules/osd/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/osd/__pycache__/__init__.cpython-313.pyc b/ignis/modules/osd/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..221b777 Binary files /dev/null and b/ignis/modules/osd/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/powermenu/__init__.py b/ignis/modules/powermenu/__init__.py new file mode 100644 index 0000000..cdc4825 --- /dev/null +++ b/ignis/modules/powermenu/__init__.py @@ -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"], + ) diff --git a/ignis/modules/powermenu/__pycache__/__init__.cpython-312.pyc b/ignis/modules/powermenu/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..45609b8 Binary files /dev/null and b/ignis/modules/powermenu/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/powermenu/__pycache__/__init__.cpython-313.pyc b/ignis/modules/powermenu/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..b35c7be Binary files /dev/null and b/ignis/modules/powermenu/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/settings/__init__.py b/ignis/modules/settings/__init__.py new file mode 100644 index 0000000..941ab0c --- /dev/null +++ b/ignis/modules/settings/__init__.py @@ -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", + ) diff --git a/ignis/modules/settings/__pycache__/__init__.cpython-312.pyc b/ignis/modules/settings/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..65d08f5 Binary files /dev/null and b/ignis/modules/settings/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/__init__.cpython-313.pyc b/ignis/modules/settings/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..e53b964 Binary files /dev/null and b/ignis/modules/settings/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/settings/__pycache__/about.cpython-312.pyc b/ignis/modules/settings/__pycache__/about.cpython-312.pyc new file mode 100644 index 0000000..5c60a1c Binary files /dev/null and b/ignis/modules/settings/__pycache__/about.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/about.cpython-313.pyc b/ignis/modules/settings/__pycache__/about.cpython-313.pyc new file mode 100644 index 0000000..8f41b1d Binary files /dev/null and b/ignis/modules/settings/__pycache__/about.cpython-313.pyc differ diff --git a/ignis/modules/settings/__pycache__/appearance.cpython-312.pyc b/ignis/modules/settings/__pycache__/appearance.cpython-312.pyc new file mode 100644 index 0000000..73628eb Binary files /dev/null and b/ignis/modules/settings/__pycache__/appearance.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/appearance.cpython-313.pyc b/ignis/modules/settings/__pycache__/appearance.cpython-313.pyc new file mode 100644 index 0000000..ee4eab4 Binary files /dev/null and b/ignis/modules/settings/__pycache__/appearance.cpython-313.pyc differ diff --git a/ignis/modules/settings/__pycache__/notifications.cpython-312.pyc b/ignis/modules/settings/__pycache__/notifications.cpython-312.pyc new file mode 100644 index 0000000..05de33c Binary files /dev/null and b/ignis/modules/settings/__pycache__/notifications.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/notifications.cpython-313.pyc b/ignis/modules/settings/__pycache__/notifications.cpython-313.pyc new file mode 100644 index 0000000..3395837 Binary files /dev/null and b/ignis/modules/settings/__pycache__/notifications.cpython-313.pyc differ diff --git a/ignis/modules/settings/__pycache__/recorder.cpython-312.pyc b/ignis/modules/settings/__pycache__/recorder.cpython-312.pyc new file mode 100644 index 0000000..339f312 Binary files /dev/null and b/ignis/modules/settings/__pycache__/recorder.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/recorder.cpython-313.pyc b/ignis/modules/settings/__pycache__/recorder.cpython-313.pyc new file mode 100644 index 0000000..108da2c Binary files /dev/null and b/ignis/modules/settings/__pycache__/recorder.cpython-313.pyc differ diff --git a/ignis/modules/settings/__pycache__/user.cpython-312.pyc b/ignis/modules/settings/__pycache__/user.cpython-312.pyc new file mode 100644 index 0000000..938eed2 Binary files /dev/null and b/ignis/modules/settings/__pycache__/user.cpython-312.pyc differ diff --git a/ignis/modules/settings/__pycache__/user.cpython-313.pyc b/ignis/modules/settings/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000..a0d562d Binary files /dev/null and b/ignis/modules/settings/__pycache__/user.cpython-313.pyc differ diff --git a/ignis/modules/settings/about.py b/ignis/modules/settings/about.py new file mode 100644 index 0000000..7fc529e --- /dev/null +++ b/ignis/modules/settings/about.py @@ -0,0 +1,44 @@ +from .elements import SettingsPage, SettingsRow, SettingsEntry +from ignis.utils import Utils +from ignis.widgets import Widget +from services.material import MaterialService +from ignis.services.fetch import FetchService + +fetch = FetchService.get_default() +material = MaterialService.get_default() + + +def about_entry(active_page): + about_page = SettingsPage( + name="About", + groups=[ + Widget.Box( + child=[ + Widget.Picture( + image=material.bind( + "dark_mode", + transform=lambda value: fetch.os_logo_text_dark + if value + else fetch.os_logo_text, + ), + width=300, + height=100, + ) + ], + halign="center", + width_request=300, + height_request=100, + ), + SettingsRow(label="OS", sublabel=fetch.os_name), + SettingsRow(label="Ignis version", sublabel=Utils.get_ignis_version()), + SettingsRow(label="Session type", sublabel=fetch.session_type), + SettingsRow(label="Wayland compositor", sublabel=fetch.current_desktop), + SettingsRow(label="Kernel", sublabel=fetch.kernel), + ], + ) + return SettingsEntry( + label="About", + icon="help-about-symbolic", + active_page=active_page, + page=about_page, + ) diff --git a/ignis/modules/settings/appearance.py b/ignis/modules/settings/appearance.py new file mode 100644 index 0000000..492eddd --- /dev/null +++ b/ignis/modules/settings/appearance.py @@ -0,0 +1,64 @@ +import os +from services.material import MaterialService +from .elements import SwitchRow, SettingsPage, SettingsGroup, FileRow, SettingsEntry +from ignis.widgets import Widget +from ignis.services.wallpaper import WallpaperService + +wallpaper = WallpaperService.get_default() +material = MaterialService.get_default() + + +def appearance_entry(active_page): + appearance_page = SettingsPage( + name="Appearance", + groups=[ + SettingsGroup( + name=None, + rows=[ + Widget.ListBoxRow( + child=Widget.Picture( + image=wallpaper.bind("wallpaper"), + width=1920 // 4, + height=1080 // 4, + halign="center", + style="border-radius: 1rem;", + content_fit="cover", + ), + selectable=False, + activatable=False, + ), + SwitchRow( + label="Dark mode", + active=material.bind("dark_mode"), + on_change=lambda x, state: material.set_dark_mode(state), + style="margin-top: 1rem;", + ), + FileRow( + label="Wallpaper path", + button_label=os.path.basename(wallpaper.wallpaper) + if wallpaper.wallpaper + else None, + dialog=Widget.FileDialog( + on_file_set=lambda x, file: material.generate_colors( + file.get_path() + ), + initial_path=wallpaper.bind("wallpaper"), + filters=[ + Widget.FileFilter( + mime_types=["image/jpeg", "image/png"], + default=True, + name="Images JPEG/PNG", + ) + ], + ), + ), + ], + ) + ], + ) + return SettingsEntry( + label="Appearance", + icon="preferences-desktop-wallpaper-symbolic", + active_page=active_page, + page=appearance_page, + ) diff --git a/ignis/modules/settings/elements/__init__.py b/ignis/modules/settings/elements/__init__.py new file mode 100644 index 0000000..d3795f0 --- /dev/null +++ b/ignis/modules/settings/elements/__init__.py @@ -0,0 +1,20 @@ +from .row import SettingsRow +from .page import SettingsPage +from .group import SettingsGroup +from .switchrow import SwitchRow +from .filerow import FileRow +from .spinrow import SpinRow +from .entryrow import EntryRow +from .entry import SettingsEntry + +__all__ = [ + "SettingsRow", + "SettingsPage", + "SettingsGroup", + "SettingsRow", + "SwitchRow", + "FileRow", + "SpinRow", + "EntryRow", + "SettingsEntry", +] diff --git a/ignis/modules/settings/elements/__pycache__/__init__.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..cbba8ba Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/__init__.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..6956187 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/entry.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/entry.cpython-312.pyc new file mode 100644 index 0000000..4554234 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/entry.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/entry.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/entry.cpython-313.pyc new file mode 100644 index 0000000..1b5760b Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/entry.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/entryrow.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/entryrow.cpython-312.pyc new file mode 100644 index 0000000..4de8cab Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/entryrow.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/entryrow.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/entryrow.cpython-313.pyc new file mode 100644 index 0000000..78890a4 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/entryrow.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/filerow.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/filerow.cpython-312.pyc new file mode 100644 index 0000000..b21a852 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/filerow.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/filerow.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/filerow.cpython-313.pyc new file mode 100644 index 0000000..4967147 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/filerow.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/group.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/group.cpython-312.pyc new file mode 100644 index 0000000..68e1735 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/group.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/group.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/group.cpython-313.pyc new file mode 100644 index 0000000..445e1b4 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/group.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/page.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/page.cpython-312.pyc new file mode 100644 index 0000000..88b81ec Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/page.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/page.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/page.cpython-313.pyc new file mode 100644 index 0000000..50a42a9 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/page.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/row.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/row.cpython-312.pyc new file mode 100644 index 0000000..26ec3dd Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/row.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/row.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/row.cpython-313.pyc new file mode 100644 index 0000000..88d372f Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/row.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/spinrow.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/spinrow.cpython-312.pyc new file mode 100644 index 0000000..db1d8df Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/spinrow.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/spinrow.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/spinrow.cpython-313.pyc new file mode 100644 index 0000000..50d357e Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/spinrow.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/switchrow.cpython-312.pyc b/ignis/modules/settings/elements/__pycache__/switchrow.cpython-312.pyc new file mode 100644 index 0000000..59c3f7b Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/switchrow.cpython-312.pyc differ diff --git a/ignis/modules/settings/elements/__pycache__/switchrow.cpython-313.pyc b/ignis/modules/settings/elements/__pycache__/switchrow.cpython-313.pyc new file mode 100644 index 0000000..33d79f8 Binary files /dev/null and b/ignis/modules/settings/elements/__pycache__/switchrow.cpython-313.pyc differ diff --git a/ignis/modules/settings/elements/entry.py b/ignis/modules/settings/elements/entry.py new file mode 100644 index 0000000..2df6941 --- /dev/null +++ b/ignis/modules/settings/elements/entry.py @@ -0,0 +1,31 @@ +from ignis.widgets import Widget +from .page import SettingsPage +from options import settings_last_page + + +class SettingsEntry(Widget.ListBoxRow): + def __init__( + self, + icon: str, + label: str, + active_page, + page: SettingsPage, + **kwargs, + ): + def callback(x): + active_page.page = page + active_page.name = label + if self in self.parent.rows: + settings_last_page.set_value(self.parent.rows.index(self)) + + super().__init__( + child=Widget.Box( + child=[ + Widget.Icon(image=icon, pixel_size=20), + Widget.Label(label=label, style="margin-left: 0.75rem;"), + ], + ), + css_classes=["settings-sidebar-entry"], + on_activate=callback, + **kwargs, + ) diff --git a/ignis/modules/settings/elements/entryrow.py b/ignis/modules/settings/elements/entryrow.py new file mode 100644 index 0000000..3349c21 --- /dev/null +++ b/ignis/modules/settings/elements/entryrow.py @@ -0,0 +1,24 @@ +from ignis.widgets import Widget +from .row import SettingsRow +from typing import Callable +from ignis.gobject import Binding + + +class EntryRow(SettingsRow): + def __init__( + self, + text: str | Binding | None = None, + on_change: Callable | None = None, + width: int | None = None, + **kwargs, + ): + super().__init__(**kwargs) + self._entry = Widget.Entry( + on_change=on_change, + text=text, + halign="end", + valign="center", + width_request=width, + hexpand=True, + ) + self.child.append(self._entry) diff --git a/ignis/modules/settings/elements/filerow.py b/ignis/modules/settings/elements/filerow.py new file mode 100644 index 0000000..e948f5b --- /dev/null +++ b/ignis/modules/settings/elements/filerow.py @@ -0,0 +1,23 @@ +from ignis.widgets import Widget +from .row import SettingsRow +from ignis.gobject import Binding + + +class FileRow(SettingsRow): + def __init__( + self, + dialog: Widget.FileDialog, + button_label: str | Binding | None = None, + **kwargs, + ): + super().__init__(**kwargs) + self._button = Widget.FileChooserButton( + dialog=dialog, + label=Widget.Label( + label=button_label, ellipsize="start", max_width_chars=20 + ), + hexpand=True, + halign="end", + ) + + self.child.append(self._button) diff --git a/ignis/modules/settings/elements/group.py b/ignis/modules/settings/elements/group.py new file mode 100644 index 0000000..d869761 --- /dev/null +++ b/ignis/modules/settings/elements/group.py @@ -0,0 +1,24 @@ +from typing import List +from ignis.widgets import Widget +from .row import SettingsRow +from ignis.base_widget import BaseWidget + + +class SettingsGroup(Widget.Box): + def __init__( + self, name: str | None, rows: List[SettingsRow | BaseWidget] = [], **kwargs + ): + super().__init__( + vertical=True, + css_classes=["settings-group"], + child=[ + Widget.Label( + label=name, + css_classes=["settings-group-name"], + halign="start", + visible=True if name else False, + ), + Widget.ListBox(rows=[*rows]), + ], + **kwargs, + ) diff --git a/ignis/modules/settings/elements/page.py b/ignis/modules/settings/elements/page.py new file mode 100644 index 0000000..64bd961 --- /dev/null +++ b/ignis/modules/settings/elements/page.py @@ -0,0 +1,24 @@ +from ignis.widgets import Widget +from typing import List +from .group import SettingsGroup +from ignis.base_widget import BaseWidget + + +class SettingsPage(Widget.Scroll): + def __init__(self, name: str, groups: List[SettingsGroup | BaseWidget] = []): + super().__init__( + hexpand=True, + vexpand=True, + child=Widget.Box( + vertical=True, + hexpand=True, + vexpand=True, + css_classes=["settings-page"], + child=[ + Widget.Label( + label=name, css_classes=["settings-page-name"], halign="start" + ), + *groups, + ], + ), + ) diff --git a/ignis/modules/settings/elements/row.py b/ignis/modules/settings/elements/row.py new file mode 100644 index 0000000..16f5950 --- /dev/null +++ b/ignis/modules/settings/elements/row.py @@ -0,0 +1,34 @@ +from ignis.widgets import Widget + + +class SettingsRow(Widget.ListBoxRow): + def __init__(self, label: str | None = None, sublabel: str | None = None, **kwargs): + super().__init__( + css_classes=["settings-row"], + child=Widget.Box( + child=[ + Widget.Box( + vertical=True, + child=[ + Widget.Label( + label=label, + css_classes=["settings-row-label"], + halign="start", + vexpand=True, + wrap=True, + visible=True if label else False, + ), + Widget.Label( + label=sublabel, + css_classes=["settings-row-sublabel"], + halign="start", + vexpand=True, + wrap=True, + visible=True if sublabel else False, + ), + ], + ) + ] + ), + **kwargs, + ) diff --git a/ignis/modules/settings/elements/spinrow.py b/ignis/modules/settings/elements/spinrow.py new file mode 100644 index 0000000..3d3f09e --- /dev/null +++ b/ignis/modules/settings/elements/spinrow.py @@ -0,0 +1,30 @@ +from ignis.widgets import Widget +from .row import SettingsRow +from typing import Callable +from ignis.gobject import Binding + + +class SpinRow(SettingsRow): + def __init__( + self, + value: int | Binding = 0, + on_change: Callable | None = None, + min: int = 0, + max: int = 100, + step: int = 1, + width: int = 0, + **kwargs, + ): + super().__init__(**kwargs) + self._spin_button = Widget.SpinButton( + value=value, + on_change=on_change, + min=min, + max=max, + halign="end", + valign="center", + width_request=width, + hexpand=True, + step=step, + ) + self.child.append(self._spin_button) diff --git a/ignis/modules/settings/elements/switchrow.py b/ignis/modules/settings/elements/switchrow.py new file mode 100644 index 0000000..b204e9c --- /dev/null +++ b/ignis/modules/settings/elements/switchrow.py @@ -0,0 +1,25 @@ +from ignis.widgets import Widget +from .row import SettingsRow +from typing import Callable +from ignis.gobject import Binding + + +class SwitchRow(SettingsRow): + def __init__( + self, + active: bool | Binding = False, + on_change: Callable | None = None, + **kwargs, + ): + super().__init__(**kwargs) + self._switch = Widget.Switch( + active=active, + on_change=on_change, + halign="end", + valign="center", + hexpand=True, + ) + self.on_activate = lambda x: self._switch.emit( + "activate" + ) # if set "active" property animation will not work + self.child.append(self._switch) diff --git a/ignis/modules/settings/notifications.py b/ignis/modules/settings/notifications.py new file mode 100644 index 0000000..df21602 --- /dev/null +++ b/ignis/modules/settings/notifications.py @@ -0,0 +1,48 @@ +from .elements import SwitchRow, SettingsPage, SettingsGroup, SpinRow, SettingsEntry +from ignis.services.notifications import NotificationService + +notifications = NotificationService.get_default() + + +def notifications_entry(active_page): + notifications_page = SettingsPage( + name="Notifications", + groups=[ + SettingsGroup( + name="General", + rows=[ + SwitchRow( + label="Do not disturb", + active=notifications.bind("dnd"), + on_change=lambda x, state: notifications.set_dnd(state), + ), + SpinRow( + label="Maximum popups count", + sublabel="The first popup will automatically dismiss", + value=notifications.bind("max_popups_count"), + min=1, + on_change=lambda x, value: notifications.set_max_popups_count( + value + ), + ), + SpinRow( + label="Popup timeout", + sublabel="Timeout before popup will be dismissed, in milliseconds.", + max=100000, + step=100, + value=notifications.bind("popup_timeout"), + on_change=lambda x, value: notifications.set_popup_timeout( + value + ), + ), + ], + ) + ], + ) + + return SettingsEntry( + label="Notifications", + icon="notification-symbolic", + active_page=active_page, + page=notifications_page, + ) diff --git a/ignis/modules/settings/recorder.py b/ignis/modules/settings/recorder.py new file mode 100644 index 0000000..f0b0fbd --- /dev/null +++ b/ignis/modules/settings/recorder.py @@ -0,0 +1,58 @@ +from ignis.services.recorder import RecorderService +from .elements import ( + SpinRow, + SettingsPage, + SettingsGroup, + EntryRow, + FileRow, + SettingsEntry, +) +from ignis.widgets import Widget + +recorder = RecorderService.get_default() + + +def recorder_entry(active_page): + recorder_page = SettingsPage( + name="Recorder", + groups=[ + SettingsGroup( + name="General", + rows=[ + SpinRow( + label="Recording bitrate", + sublabel="Affects the recording quality", + value=recorder.bind("bitrate"), + max=640000, + width=150, + on_change=lambda x, value: recorder.set_bitrate(int(value)), + step=1000, + ), + FileRow( + label="Recording path", + button_label=recorder.bind("default_file_location"), + dialog=Widget.FileDialog( + on_file_set=lambda x, + file: recorder.set_default_file_location(file.get_path()), + select_folder=True, + initial_path=recorder.default_file_location, + ), + ), + EntryRow( + label="Recording filename", + sublabel="Support time formatting", + text=recorder.bind("default_filename"), + on_change=lambda x: recorder.set_default_filename(x.text), + width=200, + ), + ], + ) + ], + ) + + return SettingsEntry( + label="Recorder", + icon="media-record-symbolic", + active_page=active_page, + page=recorder_page, + ) diff --git a/ignis/modules/settings/user.py b/ignis/modules/settings/user.py new file mode 100644 index 0000000..c62e3f0 --- /dev/null +++ b/ignis/modules/settings/user.py @@ -0,0 +1,56 @@ +import os +from .elements import SettingsGroup, SettingsPage, SettingsEntry, FileRow +from ignis.widgets import Widget +from options import avatar_opt + + +def user_entry(active_page): + page = SettingsPage( + name="User", + groups=[ + Widget.Box( + halign="start", + style="margin-left: 2rem;", + child=[ + Widget.Picture( + image=avatar_opt.bind( + "value", + lambda value: "user-info" + if not os.path.exists(value) + else value, + ), + width=96, + height=96, + style="border-radius: 10rem;", + ), + Widget.Label( + label=os.getenv("USER"), css_classes=["settings-user-name"] + ), + ], + ), + SettingsGroup( + style="margin-top: 2rem;", + valign="start", + name="General", + vexpand=True, + rows=[ + FileRow( + label="Avatar", + dialog=Widget.FileDialog( + initial_path=avatar_opt.bind("value"), + on_file_set=lambda x, gfile: avatar_opt.set_value( + gfile.get_path() + ), + ), + ) + ], + ), + ], + ) + + return SettingsEntry( + label="User", + icon="user-available-symbolic", + active_page=active_page, + page=page, + ) diff --git a/ignis/options.py b/ignis/options.py new file mode 100644 index 0000000..5bfa182 --- /dev/null +++ b/ignis/options.py @@ -0,0 +1,18 @@ +import os +from ignis.services.options import OptionsService + +options = OptionsService.get_default() + +user_opt_group = options.create_group("user", exists_ok=True) +avatar_opt = user_opt_group.create_option( + "avatar", + default=f"/var/lib/AccountsService/icons/{os.getenv('USER')}", + exists_ok=True, +) + +settings_opt_group = options.create_group("settings", exists_ok=True) +settings_last_page = settings_opt_group.create_option( + "last_page", + default=0, + exists_ok=True, +) diff --git a/ignis/scripts/recording.py b/ignis/scripts/recording.py new file mode 100644 index 0000000..7bf75c6 --- /dev/null +++ b/ignis/scripts/recording.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import sys +from ignis.client import IgnisClient + +CODE_TEMPLATE = """ +from ignis.services.recorder import RecorderService + +recorder = RecorderService.get_default() +recorder.{}_recording() +""" + + +def run_code(_type: str) -> None: + client = IgnisClient() + client.run_python(CODE_TEMPLATE.format(_type)) + + +if len(sys.argv) < 2: + sys.exit(1) + +elif sys.argv[1] == "start": + run_code("start") + +elif sys.argv[1] == "stop": + run_code("stop") + +elif sys.argv[1] == "pause": + run_code("pause") + +elif sys.argv[1] == "continue": + run_code("continue") diff --git a/ignis/scss/bar.scss b/ignis/scss/bar.scss new file mode 100644 index 0000000..9738518 --- /dev/null +++ b/ignis/scss/bar.scss @@ -0,0 +1,133 @@ +.bar-widget { + padding: 0.4rem 1rem; + margin: 0; + border-radius: 0; + transition: 0.3s; + background-color: rgba($surface, 0.3); +} + +.clock { + @include hover($surface); + color: $onSurface; + background-color: $surface; + font-size: 1.5rem; + border-radius: 1rem; + padding: 0 0.75rem; + margin: 0 0.5rem; + margin-right: 0; + transition: 0.3s; + + &.active { + background-color: $primary; + color: $surface; + + .record-indicator { + color: $surface; + } + + @include hover($primary) + } +} + +.kb-layout { + border-radius: 0.5rem; + padding: 0 0.25rem; + margin: 0 0.25rem; + transition: 0.3s; + font-size: 1.3rem; +} + +.kb-layout:hover { + background-color: rgba(white, 0.2); +} + +.pinned-app { + margin: 0 0.25rem; + transition: 0.3s; + padding: 0 0.1rem; + border-radius: 0.25rem; +} + +.pinned-app:hover { + background-color: rgba(white, 0.2); +} + + +.tray { + padding: 0 0.25rem; + margin: 0 0.25rem; +} + +.workspaces { + border-radius: 1rem; + padding: 0.5rem; + background-color: $surface; + margin: 0 0.5rem; + margin-left: 0; +} + +.workspace { + @include hover($primaryContainer); + border-radius: 1rem; + margin: 0 0.25rem; + min-width: 0.7rem; + min-height: 0.7rem; + transition: 0.3s; + + &.active { + @include hover($primary); + min-width: 2rem; + } +} + +@keyframes recorder-animation { + 0% { opacity: 0; } + 50% { opacity: 1; } + 90% { opacity: 1; } + 100% { opacity: 0; } +} + +.record-indicator { + color: $error; +} + +.record-indicator.active { + animation: recorder-animation 2s infinite; +} + +.battery-item { + margin: 0 0.25rem; +} + +.battery-scale { + min-width: 5rem; + all: unset; + + * { + all: unset; + } +} + +.battery-scale trough, +.battery-scale trough highlight { + background-color: $surfaceContainerHighest; + border-radius: 0.5rem; + min-height: 1.5rem; + min-width: 6rem; +} + + +.battery-scale trough highlight { + background-color: $primary; + min-width: 0rem; +} + +.battery-icon { + color: $onSurface; + margin-right: 0.3rem; +} + +.battery-percent { + color: $onSurface; + margin-right: 0.3rem; +} \ No newline at end of file diff --git a/ignis/scss/control_center.scss b/ignis/scss/control_center.scss new file mode 100644 index 0000000..c1c955f --- /dev/null +++ b/ignis/scss/control_center.scss @@ -0,0 +1,166 @@ +.control-center { + @include window; + min-width: 28rem; + padding: 0; +} + +.control-center-widget { + padding: 1rem; + margin-bottom: 1rem; +} + +.qs-main-box { + margin-top: 0.5rem; +} + +.user { + margin-top: 1rem; +} + +.qs-button { + @include hover($surfaceContainerHigh); + color: $onSurface; + min-height: 2.4rem; + border-radius: 1.2rem; + transition: 0.3s; + padding: 0.3rem 0.8rem; + + &.active { + @include hover($primary); + color: $surface; + } + +} + +.qs-button-label { + margin-left: 0.5rem; +} + +.record-menu { + background-color: $surfaceContainerHigh; + border-radius: 1rem; + padding: 1rem; + margin-top: 1rem; +} + +.record-icon { + color: $error; +} + +.record-dropdown { + button { + all: unset; + color: $onSurfaceVariant; + font-size: 0.9rem; + } +} + +.record-cancel-button, +.record-start-button { + color: $surface; + border-radius: 1rem; + padding: 0.25rem 0.75rem; + font-size: 0.95rem; + transition: 0.3s; +} + +.record-cancel-button { + @include hover($surfaceContainerHigh); + color: $onSurface; + border: 1px solid $primary; +} + +.record-start-button { + @include hover($primary); +} + +.volume-entry { + @include hover($surfaceContainerHigh); + transition: 0.3s; + border-radius: 1rem; + padding: 0.5rem 0.5rem; +} + +.volume-entry-list-header-label { + font-size: 1.35rem; + margin-left: 0.5rem; +} + +.volume-entry-list-header-box { + margin-bottom: 0.75rem; +} + +.user-name-box { + margin-left: 0.5rem; +} + +.user-name { + font-size: 1.2rem; +} + +.user-name-secondary { + color: $onSurfaceVariant; + font-size: 0.9rem; +} + +.user-settings { + @include hover($surfaceContainerHighest); + color: $onSurface; + border-radius: 2rem; + min-width: 2.35rem; + margin: 0.4rem 0rem; + margin-right: 0.5rem; + transition: 0.3s; +} + +.user-power { + @include hover($primary); + color: $surface; + border-radius: 1.5rem; + min-width: 2.35rem; + margin: 0.4rem 0rem; + transition: 0.3s; +} + +.ethernet-header-box, +.wifi-header-box, +.vpn-header-box { + margin-bottom: 0.5rem; +} + +.ethernet-header-label, +.vpn-header-label { + font-size: 1.35rem; + margin-left: 0.75rem; +} + +.wifi-header-label { + font-size: 1.35rem; +} + +.ethernet-connection-item, +.wifi-network-item, +.vpn-connection-item { + @include hover($surfaceContainerHigh); + transition: 0.3s; + border-radius: 1rem; + padding: 0.5rem 0.5rem; +} + + +.ethernet-connection-label, +.wifi-network-label, +.vpn-connection-label { + margin-left: 0.5rem; +} + +.ethernet-connection-item-connect-label, +.vpn-connection-item-connect-label { + color: $onSurfaceVariant; + font-size: 1rem; +} + +.volume-entry-label { + margin-left: 0.5rem; + margin-right: 1rem; +} diff --git a/ignis/scss/launcher.scss b/ignis/scss/launcher.scss new file mode 100644 index 0000000..5331b36 --- /dev/null +++ b/ignis/scss/launcher.scss @@ -0,0 +1,38 @@ +.launcher { + @include window; + margin-top: 20rem; +} + +.launcher-search { + all: unset; +} + +.launcher-search-box { + padding: 0.75rem 1rem; + background-color: $surfaceContainer; + border-radius: 1rem; + min-width: 30rem; +} + +.launcher-scroll { + min-height: 30rem; +} + +.launcher-app { + all: unset; + @include hover($surface); + border-radius: 1rem; + padding: 0.5rem 0.25rem; + border: 2px solid $surface; + margin-top: 0.25rem; + transition: 0.3s; + + &:focus { + border: 2px solid $primary; + } +} + +.launcher-app-label { + font-size: 1.1rem; + margin-left: 1rem; +} \ No newline at end of file diff --git a/ignis/scss/lib.scss b/ignis/scss/lib.scss new file mode 100644 index 0000000..0ce0072 --- /dev/null +++ b/ignis/scss/lib.scss @@ -0,0 +1,75 @@ +.rec-unset { + * { + all: unset; + } +} + +.unset { + all: unset; +} + +tooltip { + all: unset; + background-color: $surface; + padding: 0.5rem 1rem; + border-radius: 1rem; +} + +.toggle-box { + padding: 0.75rem 1rem; + border-radius: 1rem; + background-color: $primaryContainer; +} + +.material-slider { + all: unset; + + * { + all: unset; + } +} + +.material-slider trough, +.material-slider trough highlight { + background-color: $secondaryContainer; + border-radius: 1rem; + min-height: 1rem; +} + + +.material-slider trough highlight { + background-color: $primary; +} + +.material-slider-icon { + padding: 0.25rem; + min-width: 1.5rem; + min-height: 1.5rem; + border-radius: 1rem; + margin-right: 0.5rem; +} + +.material-slider-arrow { + all: unset; + padding: 0.25rem; + min-width: 1.5rem; + min-height: 1.5rem; + border-radius: 1rem; + margin-left: 0.5rem; +} + +.hover-surface { + @include hover($surface); + transition: 0.3s; +} + +.control-center-menu { + margin-top: 1rem; + background-color: $surfaceContainerHigh; + border-radius: 1rem; + padding: 1rem; + + separator { + margin: 0.5rem 0; + } +} diff --git a/ignis/scss/media.scss b/ignis/scss/media.scss new file mode 100644 index 0000000..22598b1 --- /dev/null +++ b/ignis/scss/media.scss @@ -0,0 +1,84 @@ +// WARNING: Do not import this file in style.scss + +.media-{{ desktop_entry }} { + margin-top: 1rem; +} + + +.media-image-{{ desktop_entry }} { + background-image: url('file:///{{ art_url }}'); + background-repeat: no-repeat; + background-size: cover; + background-position: center; + min-height: 11rem; + border-radius: 1.5rem; +} + +.media-image-gradient-{{ desktop_entry }} { + border-radius: 1.3rem; + background: linear-gradient(90deg, rgba({{ onPrimary }}, 0.9) 0%, rgba({{ onPrimary }}, 0.4) 50%, rgba({{ onPrimary }}, 0.9) 100%); +} + +.media-scale-{{ desktop_entry }} trough, +.media-scale-{{ desktop_entry }} trough highlight { + background-color: rgba({{ onSurface }}, 0.2); + border-radius: 1rem; + min-height: 0.2rem; +} + + +.media-scale-{{ desktop_entry }} trough highlight { + background-color: {{ onSurface }}; +} + +.media-scale-{{ desktop_entry }} slider { + background-color: {{ onSurface }}; + padding: 0.5rem 0.1rem; + margin: -0.5rem -0.1rem; +} + +.media-title-{{ desktop_entry }} { + color: {{ onSurface }}; +} + +.media-artist-{{ desktop_entry }} { + color: {{ onSurfaceVariant }}; + font-weight: normal; +} + + +.media-content-{{ desktop_entry }} { + padding: 1rem; +} + +.media-playback-button-{{ desktop_entry }} { + background-color: {{ primary }}; + color: {{ onPrimary }}; + border-radius: 4rem; + min-width: 2.7rem; + min-height: 2.7rem; + transition: 0.3s; + + &:hover { + background-color: lighten({{ primary }}, 5%); + } + + &.playing { + border-radius: 1rem; + } +} + +.media-player-icon-{{ desktop_entry }} { + color: {{ primary }}; + font-size: 1.6rem; + padding: 1rem; + font-family: "JetBrains Mono Nerd Font"; +} + +.media-skip-button-{{ desktop_entry }} { + transition: 0.3s; + color: {{ onSurface }}; + &:hover { + color: {{ primary }}; + } +} \ No newline at end of file diff --git a/ignis/scss/mixins/hover.scss b/ignis/scss/mixins/hover.scss new file mode 100644 index 0000000..7dca32c --- /dev/null +++ b/ignis/scss/mixins/hover.scss @@ -0,0 +1,12 @@ +@mixin hover($bg) { + background-color: $bg; + + &:hover { + @if $darkmode == true { + background-color: lighten($bg, 5%); + } + @else { + background-color: darken($bg, 5%); + } + } +} diff --git a/ignis/scss/mixins/window.scss b/ignis/scss/mixins/window.scss new file mode 100644 index 0000000..e73845a --- /dev/null +++ b/ignis/scss/mixins/window.scss @@ -0,0 +1,8 @@ +@mixin window { + background-color: $surface; + border-radius: 1rem; + padding: 1rem; + color: $onSurface; + box-shadow: 0 0 4px 2px $shadow; + margin: 1rem; +} \ No newline at end of file diff --git a/ignis/scss/notification_center.scss b/ignis/scss/notification_center.scss new file mode 100644 index 0000000..21d0854 --- /dev/null +++ b/ignis/scss/notification_center.scss @@ -0,0 +1,66 @@ +.notification-center { + background-color: $surfaceContainerLow; + border-radius: 1.5rem 1.5rem 1rem 1rem; + padding: 1rem; +} + +.notification-center-header { + margin-bottom: 1rem; +} + +.notification { + background-color: $surfaceContainerHigh; + border-radius: 1rem; + padding: 1rem; + margin-top: 1rem; +} + +.notification-count { + background-color: $tertiary; + color: $onTertiary; + min-width: 2rem; + font-size: 1.5rem; + border-radius: 2rem; +} + +.notification-header-label { + font-size: 1.1rem; + margin-left: 0.5rem; +} + +.notification-close { + @include hover($surfaceContainerHigh); + color: $onSurface; + border-radius: 1rem; + padding: 0.25rem; + margin-left: 0.5rem; + transition: 0.3s; +} + +.notification-clear-all { + @include hover($surfaceContainerHigh); + transition: 0.3s; + border-radius: 1rem; + padding: 0.25rem 1rem; +} + +.notification-action { + color: $primary; + @include hover($surfaceContainerHighest); + border-radius: 0.5rem; + padding: 0.25rem 0.25rem; + transition: 0.3s; +} + +.notification-screenshot-label { + margin-top: 0.5rem; + font-size: 1.1rem; +} + +.notification-center-info-label { + color: $onSurfaceVariant; +} + +.notification-body { + color: $onSurfaceVariant; +} \ No newline at end of file diff --git a/ignis/scss/notification_popup.scss b/ignis/scss/notification_popup.scss new file mode 100644 index 0000000..f4c9034 --- /dev/null +++ b/ignis/scss/notification_popup.scss @@ -0,0 +1,12 @@ +.notification-popup { + @include window; + min-width: 25rem; + + & .notification-close { + @include hover($surface); + } + + & .notification-action { + @include hover($surfaceContainer); + } +} diff --git a/ignis/scss/osd.scss b/ignis/scss/osd.scss new file mode 100644 index 0000000..25f1aea --- /dev/null +++ b/ignis/scss/osd.scss @@ -0,0 +1,8 @@ +.osd { + @include window; + min-width: 15rem; + margin-bottom: 5rem; + * { + all: unset + } +} \ No newline at end of file diff --git a/ignis/scss/powermenu.scss b/ignis/scss/powermenu.scss new file mode 100644 index 0000000..8011a64 --- /dev/null +++ b/ignis/scss/powermenu.scss @@ -0,0 +1,28 @@ +.powermenu { + @include window; +} + +.powermenu-button { + @include hover($surfaceContainer); + transition: 0.3s; + border-radius: 10rem; + min-width: 8rem; + min-height: 8rem; + + &:focus { + @include hover($primary); + color: $surface; + } +} + +.powermenu-button-label { + margin-top: 0.5rem; +} + +.powermenu-button-box { + margin: 0.5rem; +} + +.powermenu-overlay { + background-color: rgba(0, 0, 0, 0.5); +} diff --git a/ignis/scss/settings.scss b/ignis/scss/settings.scss new file mode 100644 index 0000000..31bcc08 --- /dev/null +++ b/ignis/scss/settings.scss @@ -0,0 +1,71 @@ +.settings-sidebar { + background-color: $surfaceContainer; + min-width: 300px; +} + +.settings-sidebar-label { + font-size: 2rem; + margin-left: 1rem; + margin-bottom: 0.5rem; + margin-top: 2rem; +} + +.settings-sidebar-entry { + all: unset; + background-color: $surfaceContainer; + padding: 1rem 2rem; + padding-left: 0.75rem; + font-size: 1.2rem; + + &:selected { + @if $darkmode == true { + background-color: lighten($surfaceContainer, 10%); + } + @else { + background-color: darken($surfaceContainer, 10%); + } + } +} + + +.settings-page-name { + font-size: 2rem; + margin-left: 2.5rem; + margin-bottom: 2rem; + margin-top: 2rem; +} + +.settings-group-name { + margin-left: 2.5rem; + color: $primary; +} + +.settings-row { + all: unset; + padding: 0.7rem 2.5rem; + transition: 0.2s; + + &:active { + @if $darkmode == true { + background-color: rgba(255, 255, 255, 0.05); + } + @else { + background-color: rgba(0, 0, 0, 0.05); + } + } +} + +.settings-row-label { + font-size: 1.2rem; +} + +.settings-row-sublabel { + font-size: 0.9rem; + font-weight: normal; + color: $onSurfaceVariant; +} + +.settings-user-name { + font-size: 2.3rem; + margin-left: 1.5rem; +} \ No newline at end of file diff --git a/ignis/services/__init__.py b/ignis/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ignis/services/__pycache__/__init__.cpython-312.pyc b/ignis/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..cbdecc1 Binary files /dev/null and b/ignis/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/services/__pycache__/__init__.cpython-313.pyc b/ignis/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..df76d3b Binary files /dev/null and b/ignis/services/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/services/material/__init__.py b/ignis/services/material/__init__.py new file mode 100644 index 0000000..fd9caa6 --- /dev/null +++ b/ignis/services/material/__init__.py @@ -0,0 +1,3 @@ +from .service import MaterialService + +__all__ = ["MaterialService"] diff --git a/ignis/services/material/__pycache__/__init__.cpython-312.pyc b/ignis/services/material/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..40de3b4 Binary files /dev/null and b/ignis/services/material/__pycache__/__init__.cpython-312.pyc differ diff --git a/ignis/services/material/__pycache__/__init__.cpython-313.pyc b/ignis/services/material/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..b342171 Binary files /dev/null and b/ignis/services/material/__pycache__/__init__.cpython-313.pyc differ diff --git a/ignis/services/material/__pycache__/constants.cpython-312.pyc b/ignis/services/material/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000..3526e9c Binary files /dev/null and b/ignis/services/material/__pycache__/constants.cpython-312.pyc differ diff --git a/ignis/services/material/__pycache__/constants.cpython-313.pyc b/ignis/services/material/__pycache__/constants.cpython-313.pyc new file mode 100644 index 0000000..ec24c5c Binary files /dev/null and b/ignis/services/material/__pycache__/constants.cpython-313.pyc differ diff --git a/ignis/services/material/__pycache__/service.cpython-312.pyc b/ignis/services/material/__pycache__/service.cpython-312.pyc new file mode 100644 index 0000000..5b48bc2 Binary files /dev/null and b/ignis/services/material/__pycache__/service.cpython-312.pyc differ diff --git a/ignis/services/material/__pycache__/service.cpython-313.pyc b/ignis/services/material/__pycache__/service.cpython-313.pyc new file mode 100644 index 0000000..25083d1 Binary files /dev/null and b/ignis/services/material/__pycache__/service.cpython-313.pyc differ diff --git a/ignis/services/material/__pycache__/util.cpython-312.pyc b/ignis/services/material/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000..65c5468 Binary files /dev/null and b/ignis/services/material/__pycache__/util.cpython-312.pyc differ diff --git a/ignis/services/material/__pycache__/util.cpython-313.pyc b/ignis/services/material/__pycache__/util.cpython-313.pyc new file mode 100644 index 0000000..bbf01ff Binary files /dev/null and b/ignis/services/material/__pycache__/util.cpython-313.pyc differ diff --git a/ignis/services/material/constants.py b/ignis/services/material/constants.py new file mode 100644 index 0000000..9a5d44a --- /dev/null +++ b/ignis/services/material/constants.py @@ -0,0 +1,11 @@ +import os +import ignis +from ignis.utils import Utils + + +MATERIAL_CACHE_DIR = f"{ignis.CACHE_DIR}/material" # type: ignore + +TEMPLATES = Utils.get_current_dir() + "/templates" +SAMPLE_WALL = Utils.get_current_dir() + "/sample_wall.png" + +os.makedirs(MATERIAL_CACHE_DIR, exist_ok=True) diff --git a/ignis/services/material/sample_wall.png b/ignis/services/material/sample_wall.png new file mode 100644 index 0000000..8d6dc01 Binary files /dev/null and b/ignis/services/material/sample_wall.png differ diff --git a/ignis/services/material/service.py b/ignis/services/material/service.py new file mode 100644 index 0000000..5399aab --- /dev/null +++ b/ignis/services/material/service.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +import os + +from jinja2 import Template +from PIL import Image +from materialyoucolor.quantize import QuantizeCelebi +from materialyoucolor.hct import Hct +from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot +from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors +from materialyoucolor.score.score import Score + +from ignis.utils import Utils +from ignis.app import IgnisApp +from ignis.base_service import BaseService +from ignis.services.wallpaper import CACHE_WALLPAPER_PATH +from gi.repository import GObject # type: ignore +from ignis.services.wallpaper import WallpaperService +from ignis.services.options import OptionsService + +from .constants import MATERIAL_CACHE_DIR, TEMPLATES, SAMPLE_WALL +from .util import rgba_to_hex, calculate_optimal_size + +app = IgnisApp.get_default() + + +class MaterialService(BaseService): + def __init__(self): + super().__init__() + + self._wallpaper = WallpaperService.get_default() + + options = OptionsService.get_default() + opt_group = options.create_group(name="material", exists_ok=True) + self._dark_mode_opt = opt_group.create_option( + name="dark_mode", default=True, exists_ok=True + ) + self._colors_opt = opt_group.create_option( + name="colors", default={}, exists_ok=True + ) + + if not os.path.exists(CACHE_WALLPAPER_PATH): + self.__on_colors_not_found() + if self.colors == {}: + self.__on_colors_not_found() + + @GObject.Property + def dark_mode(self) -> bool: + return self._dark_mode_opt.value + + @dark_mode.setter + def dark_mode(self, value: bool) -> None: + self._dark_mode_opt.value = value + self.generate_colors(CACHE_WALLPAPER_PATH) + + @GObject.Property + def colors(self) -> dict: + return self._colors_opt.value + + def __on_colors_not_found(self) -> None: + self._wallpaper.set_wallpaper(SAMPLE_WALL) + self.generate_colors(SAMPLE_WALL) + Utils.exec_sh_async("hyprctl reload") + + def get_colors_from_img(self, path: str, dark_mode: bool) -> dict[str, str]: + image = Image.open(path) + wsize, hsize = image.size + wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, 128) + if wsize_new < wsize or hsize_new < hsize: + image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC) # type: ignore + + pixel_len = image.width * image.height + image_data = image.getdata() + pixel_array = [image_data[_] for _ in range(0, pixel_len, 1)] + + colors = QuantizeCelebi(pixel_array, 128) + argb = Score.score(colors)[0] + + hct = Hct.from_int(argb) + scheme = SchemeTonalSpot(hct, dark_mode, 0.0) + + material_colors = {} + for color in vars(MaterialDynamicColors).keys(): + color_name = getattr(MaterialDynamicColors, color) + if hasattr(color_name, "get_hct"): + rgba = color_name.get_hct(scheme).to_rgba() + material_colors[color] = rgba_to_hex(rgba) + + return material_colors + + def generate_colors(self, path: str) -> None: + colors = self.get_colors_from_img(path, self.dark_mode) + dark_colors = self.get_colors_from_img(path, True) + self._colors_opt.value = colors + self.__render_templates(colors, dark_colors) + self.__setup(path) + + def __render_templates(self, colors: dict, dark_colors: dict) -> None: + for template in os.listdir(TEMPLATES): + self.render_template( + colors=colors, + dark_mode=self.dark_mode, + input_file=f"{TEMPLATES}/{template}", + output_file=f"{MATERIAL_CACHE_DIR}/{template}", + ) + + for template in os.listdir(TEMPLATES): + self.render_template( + colors=dark_colors, + dark_mode=True, + input_file=f"{TEMPLATES}/{template}", + output_file=f"{MATERIAL_CACHE_DIR}/dark_{template}", + ) + + def render_template( + self, + colors: dict, + input_file: str, + output_file: str, + dark_mode: bool | None = None, + ) -> None: + if dark_mode is None: + colors["dark_mode"] = str(self.dark_mode).lower() + else: + colors["dark_mode"] = str(dark_mode).lower() + with open(input_file) as file: + template_rendered = Template(file.read()).render(colors) + + with open(output_file, "w") as file: + file.write(template_rendered) + + def __reload_gtk_theme(self) -> None: + THEME_CMD = "gsettings set org.gnome.desktop.interface gtk-theme {}" + COLOR_SCHEME_CMD = "gsettings set org.gnome.desktop.interface color-scheme {}" + Utils.exec_sh_async(THEME_CMD.format("Adwaita")) + Utils.exec_sh_async(THEME_CMD.format("Material")) + Utils.exec_sh_async(COLOR_SCHEME_CMD.format("default")) + Utils.exec_sh_async(COLOR_SCHEME_CMD.format("prefer-dark")) + Utils.exec_sh_async(COLOR_SCHEME_CMD.format("default")) + + def __setup(self, image_path: str) -> None: + Utils.exec_sh_async("pkill -SIGUSR1 kitty") + self._wallpaper.set_wallpaper(image_path) + app.reload_css() + self.__reload_gtk_theme() diff --git a/ignis/services/material/templates/colors-hyprland.conf b/ignis/services/material/templates/colors-hyprland.conf new file mode 100644 index 0000000..1ae3635 --- /dev/null +++ b/ignis/services/material/templates/colors-hyprland.conf @@ -0,0 +1,7 @@ +$primary = rgb({{ primary | replace("#", "") }}) +$surface = rgb({{ surface | replace("#", "") }}) +$onSurface = rgb({{ onSurface | replace("#", "") }}) + +$primaryHex = #{{ primary }} +$surfaceHex = #{{ surface }} +$onSurfaceHex = #{{ onSurface }} \ No newline at end of file diff --git a/ignis/services/material/templates/colors-kitty.conf b/ignis/services/material/templates/colors-kitty.conf new file mode 100644 index 0000000..a28b4cc --- /dev/null +++ b/ignis/services/material/templates/colors-kitty.conf @@ -0,0 +1,27 @@ +foreground {{ onSurface }} +background {{ surface }} +cursor {{ onSurface }} + +color0 {{ surface }} +color8 {{ surface }} + +color1 {{ error }} +color9 {{ error }} + +color2 {{ inversePrimary }} +color10 {{ inversePrimary }} + +color3 {{ error }} +color11 {{ error }} + +color4 {{ primaryContainer }} +color12 {{ primaryContainer }} + +color5 {{ onSecondaryContainer }} +color13 {{ onSecondaryContainer }} + +color6 {{ primary }} +color14 {{ primary }} + +color7 {{ onSurface }} +color15 {{ onSurface }} \ No newline at end of file diff --git a/ignis/services/material/templates/colors.scss b/ignis/services/material/templates/colors.scss new file mode 100644 index 0000000..d6c1f68 --- /dev/null +++ b/ignis/services/material/templates/colors.scss @@ -0,0 +1,55 @@ +$primary_paletteKeyColor: {{ primary_paletteKeyColor }}; +$secondary_paletteKeyColor: {{ secondary_paletteKeyColor }}; +$tertiary_paletteKeyColor: {{ tertiary_paletteKeyColor }}; +$neutral_paletteKeyColor: {{ neutral_paletteKeyColor }}; +$neutral_variant_paletteKeyColor: {{ neutral_variant_paletteKeyColor }}; +$background: {{ background }}; +$onBackground: {{ onBackground }}; +$surface: {{ surface }}; +$surfaceDim: {{ surfaceDim }}; +$surfaceBright: {{ surfaceBright }}; +$surfaceContainerLowest: {{ surfaceContainerLowest }}; +$surfaceContainerLow: {{ surfaceContainerLow }}; +$surfaceContainer: {{ surfaceContainer }}; +$surfaceContainerHigh: {{ surfaceContainerHigh }}; +$surfaceContainerHighest: {{ surfaceContainerHighest }}; +$onSurface: {{ onSurface }}; +$surfaceVariant: {{ surfaceVariant }}; +$onSurfaceVariant: {{ onSurfaceVariant }}; +$inverseSurface: {{ inverseSurface }}; +$inverseOnSurface: {{ inverseOnSurface }}; +$outline: {{ outline }}; +$outlineVariant: {{ outlineVariant }}; +$shadow: {{ shadow }}; +$scrim: {{ scrim }}; +$surfaceTint: {{ surfaceTint }}; +$primary: {{ primary }}; +$onPrimary: {{ onPrimary }}; +$primaryContainer: {{ primaryContainer }}; +$onPrimaryContainer: {{ onPrimaryContainer }}; +$inversePrimary: {{ inversePrimary }}; +$secondary: {{ secondary }}; +$onSecondary: {{ onSecondary }}; +$secondaryContainer: {{ secondaryContainer }}; +$onSecondaryContainer: {{ onSecondaryContainer }}; +$tertiary: {{ tertiary }}; +$onTertiary: {{ onTertiary }}; +$tertiaryContainer: {{ tertiaryContainer }}; +$onTertiaryContainer: {{ onTertiaryContainer }}; +$error: {{ error }}; +$onError: {{ onError }}; +$errorContainer: {{ errorContainer }}; +$onErrorContainer: {{ onErrorContainer }}; +$primaryFixed: {{ primaryFixed }}; +$primaryFixedDim: {{ primaryFixedDim }}; +$onPrimaryFixed: {{ onPrimaryFixed }}; +$onPrimaryFixedVariant: {{ onPrimaryFixedVariant }}; +$secondaryFixed: {{ secondaryFixed }}; +$secondaryFixedDim: {{ secondaryFixedDim }}; +$onSecondaryFixed: {{ onSecondaryFixed }}; +$onSecondaryFixedVariant: {{ onSecondaryFixedVariant }}; +$tertiaryFixed: {{ tertiaryFixed }}; +$tertiaryFixedDim: {{ tertiaryFixedDim }}; +$onTertiaryFixed: {{ onTertiaryFixed }}; +$onTertiaryFixedVariant: {{ onTertiaryFixedVariant }}; +$darkmode: {{ dark_mode }}; \ No newline at end of file diff --git a/ignis/services/material/templates/gtk.css b/ignis/services/material/templates/gtk.css new file mode 100644 index 0000000..688ae16 --- /dev/null +++ b/ignis/services/material/templates/gtk.css @@ -0,0 +1,50 @@ +@define-color accent_color {{ primary }}; +@define-color accent_bg_color {{ primary }}; +@define-color accent_fg_color {{ surface }}; +@define-color destructive_color {{ errorContainer }}; +@define-color destructive_bg_color {{ errorContainer }}; +@define-color destructive_fg_color {{ onSurface }}; +@define-color success_color {{ tertiary }}; +@define-color success_bg_color {{ tertiary }}; +@define-color success_fg_color {{ onSurface }}; +@define-color warning_color {{ secondary }}; +@define-color warning_bg_color {{ secondary }}; +@define-color warning_fg_color {{ onSurface }}; +@define-color error_color {{ errorContainer }}; +@define-color error_bg_color {{ errorContainer }}; +@define-color error_fg_color {{ onSurface }}; +@define-color window_bg_color {{ surface }}; +@define-color window_fg_color {{ onSurface }}; +@define-color view_bg_color {{ surface }}; +@define-color view_fg_color {{ onSurface }}; +@define-color headerbar_bg_color {{ surface }}; +@define-color headerbar_fg_color {{ onSurface }}; +@define-color headerbar_border_color {{ outline }}; +@define-color headerbar_backdrop_color {{ surface }}; +@define-color headerbar_shade_color {{ outline }}; +@define-color sidebar_bg_color {{ surfaceContainer }}; +@define-color sidebar_fg_color {{ onSurface }}; +@define-color sidebar_backdrop_color {{ surfaceContainer }}; +@define-color card_bg_color {{ surfaceContainer }}; +@define-color card_fg_color {{ onSurface }}; +@define-color card_shade_color rgba(0, 0, 0, 0.07); +@define-color thumbnail_bg_color {{ surface }}; +@define-color thumbnail_fg_color {{ onSurface }}; +@define-color dialog_bg_color {{ surface }}; +@define-color dialog_fg_color {{ onSurface }}; +@define-color popover_bg_color {{ surfaceContainer }}; +@define-color popover_fg_color {{ onSurface }}; +@define-color shade_color rgba(0, 0, 0, 0.36); +@define-color scrollbar_outline_color {{ outline }}; + +.navigation-sidebar { + background-color: {{ surfaceContainer }}; +} + +switch:checked { + background-color: {{ primary }}; +} + +switch:checked slider { + background-color: {{ onPrimary }}; +} \ No newline at end of file diff --git a/ignis/services/material/templates/swaylock b/ignis/services/material/templates/swaylock new file mode 100644 index 0000000..451229a --- /dev/null +++ b/ignis/services/material/templates/swaylock @@ -0,0 +1,51 @@ +# ---------------------SETTINGS---------------------------# +daemonize +show-failed-attempts +clock +screenshot +ignore-empty-password +indicator +font=JetBrainsMono +indicator-radius=200 +indicator-thickness=20 + + +#----------------------EFFECTS----------------------------# +effect-blur=9x5 +effect-vignette=0.5:0.5 + +#--------------------INPUT IGNORE-------------------------# +grace-no-mouse +grace-no-touch + + +#--------------------DATE & TIME--------------------------# +datestr=%a,%e %B +timestr=%H:%M +fade-in=0.2 + + +#----------------------COLORS-----------------------------# +bs-hl-color={{ error }} +inside-color=#FFFFFF00 +ring-color=#FFFFFF00 +line-color=#FFFFFF00 +separator-color=#FFFFFF00 +key-hl-color={{ primaryFixedDim }} +text-color={{ primaryFixedDim }} +text-caps-lock-color={{ tertiary }} + +ring-clear-color=#FFFFFF00 +text-clear-color={{ onTertiary }} +inside-clear-color={{ tertiary }} +line-clear-color=#FFFFFF00 + +ring-ver-color=#FFFFFF00 +text-ver-color={{ onSecondary }} +inside-ver-color={{ secondary }} +line-ver-color=#FFFFFF00 + +ring-wrong-color=#FFFFFF00 +text-wrong-color={{ onError }} +inside-wrong-color={{ error }} +line-wrong-color=#FFFFFF00 \ No newline at end of file diff --git a/ignis/services/material/util.py b/ignis/services/material/util.py new file mode 100644 index 0000000..dce5ec7 --- /dev/null +++ b/ignis/services/material/util.py @@ -0,0 +1,18 @@ +import math + + +def rgba_to_hex(rgba: list) -> str: + return "#{:02x}{:02x}{:02x}".format(*rgba) + + +def calculate_optimal_size(width: int, height: int, bitmap_size: int) -> tuple: + image_area = width * height + bitmap_area = bitmap_size**2 + scale = math.sqrt(bitmap_area / image_area) if image_area > bitmap_area else 1 + new_width = round(width * scale) + new_height = round(height * scale) + if new_width == 0: + new_width = 1 + if new_height == 0: + new_height = 1 + return new_width, new_height diff --git a/ignis/style.scss b/ignis/style.scss new file mode 100644 index 0000000..9092992 --- /dev/null +++ b/ignis/style.scss @@ -0,0 +1,20 @@ +* { + font-family: JetBrainsMono; + font-weight: bold; +} +@import "../../.cache/ignis/material/colors.scss"; + +@import "scss/mixins/window.scss"; +@import "scss/mixins/hover.scss"; + +@import "scss/lib.scss"; + + +@import "./scss/bar.scss"; +@import "./scss/control_center.scss"; +@import "./scss/osd.scss"; +@import "./scss/notification_popup.scss"; +@import "./scss/launcher.scss"; +@import "./scss/settings.scss"; +@import "./scss/powermenu.scss"; +@import "./scss/notification_center.scss"; \ No newline at end of file diff --git a/kitty/kitty.conf b/kitty/kitty.conf new file mode 100644 index 0000000..78461c6 --- /dev/null +++ b/kitty/kitty.conf @@ -0,0 +1,80 @@ +map ctrl+plus change_font_size +2 +map ctrl+- change_font_size -2 +map ctrl+0 change_font_size 0 + +background_opacity 0.6 + +font_family JetBrainsMono +#font_family Nimbus Roman +window_margin_width 5 + +# The basic colors +foreground #cdd6f4 +background #1e1e2e +selection_foreground #1e1e2e +selection_background #f5e0dc + +# Cursor colors +cursor #f5e0dc +cursor_text_color #1e1e2e + +# URL underline color when hovering with mouse +url_color #f5e0dc + +# Kitty window border colors +active_border_color #b4befe +inactive_border_color #6c7086 +bell_border_color #f9e2af + +# OS Window titlebar colors +wayland_titlebar_color system +macos_titlebar_color system + +# Tab bar colors +active_tab_foreground #11111b +active_tab_background #cba6f7 +inactive_tab_foreground #cdd6f4 +inactive_tab_background #181825 +tab_bar_background #11111b + +# Colors for marks (marked text in the terminal) +mark1_foreground #1e1e2e +mark1_background #b4befe +mark2_foreground #1e1e2e +mark2_background #cba6f7 +mark3_foreground #1e1e2e +mark3_background #74c7ec + +# The 16 terminal colors + +# black +color0 #45475a +color8 #585b70 + +# red +color1 #f38ba8 +color9 #f38ba8 + +# green +color2 #a6e3a1 +color10 #a6e3a1 + +# yellow +color3 #f9e2af +color11 #f9e2af + +# blue +color4 #89b4fa +color12 #89b4fa + +# magenta +color5 #f5c2e7 +color13 #f5c2e7 + +# cyan +color6 #94e2d5 +color14 #94e2d5 + +# white +color7 #bac2de +color15 #a6adc8