From 8bbac416f9052ea7b708e9cf680dfa24f4085a24 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Tue, 13 Jan 2026 18:28:50 -0300 Subject: [PATCH 01/35] :wrench: chore: tidy up config files, add compositor-specific configs to gresource --- config/hypr/hypridle.conf | 13 -- config/hypr/hyprland.conf | 15 -- config/hypr/hyprlock.conf | 145 ------------------ config/hypr/hyprpaper.conf | 8 - config/hypr/scripts/change-wallpaper.sh | 68 -------- config/hypr/scripts/color-picker.sh | 19 --- config/hypr/scripts/exec.sh | 21 --- config/hypr/scripts/gen-pywal.sh | 19 --- config/hypr/scripts/screenshot.sh | 24 --- config/hypr/shell/autostart.conf | 21 --- config/hypr/shell/environment.conf | 32 ---- config/hypr/shell/hyprland.conf | 10 -- config/hypr/shell/layout.conf | 8 - config/hypr/shell/variables.conf | 13 -- config/hypr/user/autostart.conf | 6 - config/hypr/user/bindings.conf | 8 - config/hypr/user/decorations.conf | 6 - config/hypr/user/environment.conf | 6 - config/hypr/user/hyprland.conf | 15 -- config/hypr/user/input.conf | 13 -- config/hypr/user/layout.conf | 4 - config/hypr/user/monitors.conf | 17 -- config/hypr/user/rules.conf | 8 - config/hyprland/autostart.conf | 14 ++ config/{hypr/shell => hyprland}/bindings.conf | 48 +----- .../{hypr/shell => hyprland}/decorations.conf | 6 +- config/hyprland/environment.conf | 7 + config/hyprland/hyprland.conf | 8 + config/{hypr/shell => hyprland}/rules.conf | 27 ---- config/hyprland/vars.conf | 6 + config/kitty/kitty.conf | 10 -- config/kitty/user.conf | 2 - resources.gresource.xml | 14 ++ 33 files changed, 57 insertions(+), 584 deletions(-) delete mode 100644 config/hypr/hypridle.conf delete mode 100644 config/hypr/hyprland.conf delete mode 100644 config/hypr/hyprlock.conf delete mode 100644 config/hypr/hyprpaper.conf delete mode 100644 config/hypr/scripts/change-wallpaper.sh delete mode 100644 config/hypr/scripts/color-picker.sh delete mode 100644 config/hypr/scripts/exec.sh delete mode 100644 config/hypr/scripts/gen-pywal.sh delete mode 100644 config/hypr/scripts/screenshot.sh delete mode 100644 config/hypr/shell/autostart.conf delete mode 100644 config/hypr/shell/environment.conf delete mode 100644 config/hypr/shell/hyprland.conf delete mode 100644 config/hypr/shell/layout.conf delete mode 100644 config/hypr/shell/variables.conf delete mode 100644 config/hypr/user/autostart.conf delete mode 100644 config/hypr/user/bindings.conf delete mode 100644 config/hypr/user/decorations.conf delete mode 100644 config/hypr/user/environment.conf delete mode 100644 config/hypr/user/hyprland.conf delete mode 100644 config/hypr/user/input.conf delete mode 100644 config/hypr/user/layout.conf delete mode 100644 config/hypr/user/monitors.conf delete mode 100644 config/hypr/user/rules.conf create mode 100644 config/hyprland/autostart.conf rename config/{hypr/shell => hyprland}/bindings.conf (60%) rename config/{hypr/shell => hyprland}/decorations.conf (91%) create mode 100644 config/hyprland/environment.conf create mode 100644 config/hyprland/hyprland.conf rename config/{hypr/shell => hyprland}/rules.conf (66%) create mode 100644 config/hyprland/vars.conf delete mode 100644 config/kitty/kitty.conf delete mode 100644 config/kitty/user.conf diff --git a/config/hypr/hypridle.conf b/config/hypr/hypridle.conf deleted file mode 100644 index 271823a9..00000000 --- a/config/hypr/hypridle.conf +++ /dev/null @@ -1,13 +0,0 @@ - -general { - # lock_cmd = echo "Locked Hyprland Session" - unlock_cmd = notify-send "Welcome back to Hyprland, $USER!" - ignore_dbus_inhibit = false - ignore_systemd_inhibit = false -} - -listener { - timeout = 1800 # 1800 -> 30m | 3600 -> 1h | 7200 -> 2h - on-timeout = hyprlock - # on-resume = notify-send "Welcome back to Hyprland, $USER!" -} diff --git a/config/hypr/hyprland.conf b/config/hypr/hyprland.conf deleted file mode 100644 index f1b14a96..00000000 --- a/config/hypr/hyprland.conf +++ /dev/null @@ -1,15 +0,0 @@ - -########################################### -## Hyprland Core Settings for Colorshell ## -########################################## - -# From https://github.com/retrozinndev/Hyprland-Dots -# Made with lots of love 󰋑 , by retrozinndev -# Licensed under the BSD 3-Clause License - - -# Shell configurations (it's not recommended to modify) -source = ./shell/hyprland.conf - -# User configurations (please use the `user/` config directory -source = ./user/hyprland.conf diff --git a/config/hypr/hyprlock.conf b/config/hypr/hyprlock.conf deleted file mode 100644 index 1dec9919..00000000 --- a/config/hypr/hyprlock.conf +++ /dev/null @@ -1,145 +0,0 @@ -# Source colors from pywal -source = ~/.cache/wal/colors-hyprland.conf - -############## -# LOCKSCREEN # -############## - -# Wiki: https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock - - -# Variables -$font = Cantarell Regular -$clockFont = Cantarell Black -$minimalFont = Noto Sans Mono -$getActivePlayer = colorshell media bus-name | sed -e 's/^org.mpris.MediaPlayer2.//' - -general { - disable_loading_bar = true - hide_cursor = false - text_trim = false - fractional_scaling = 2 -} - -auth { - pam { - enabled = true - } - - fingerprint { - enabled = false - ready_message = Waiting for Fingerprint - present_message = Scanning - } -} - -background { - monitor = - path = $wallpaper - blur_passes = 3 - color = $background -} - -# Time -label { - monitor = - text = cmd[update:30000] echo -n "$(date +"%R")" # 24-hours - # text = cmd[update:30000] echo -n "$(date +"%I:%M %p")" # 12-hours (AM/PM) - color = $foreground - shadow_passes = 1 - shadow_size = 2 - shadow_color = $background - shadow_boost = 0.4 - font_size = 120 - font_family = $clockFont - position = 0, -60 - halign = center - valign = top -} - -# Date -label { - monitor = - text = cmd[update:43200000] echo -n "$(date +"%A, %d %B %Y")" - color = $foreground - shadow_passes = 1 - shadow_size = 2 - shadow_color = $background - shadow_boost = 0.4 - font_size = 20 - font_family = $font - position = 0, -250 - halign = center - valign = top -} - -# Logged user -label { - monitor = - font_size = 6 - font_family = $minimalFont - color = $foreground - text = cmd[update:0] echo -n "Logged as $USER in $(hostnamectl hostname)" - halign = center - valign = bottom - position = 0, 5 -} - -# Media -label { - monitor = - font_size = 12 - font_family = Cantarell - color = $foreground - text = cmd[update:1000] bash -c 'playerctl metadata && echo -e "󰎇 $(playerctl --player `$getActivePlayer` metadata title) - $(playerctl --player `$getActivePlayer` metadata artist)"' | tail -n 1 - shadow_passes = 1 - shadow_size = 2 - shadow_color = $background - shadow_boost = 0.4 - halign = center - valign = center - position = 0, 180 -} - -# Avatar -image { - monitor = - path = ~/.face - size = 72 - border_color = $color2 - border_size = 2 - position = 0, 100 - halign = center - valign = bottom - shadow_passes = 1 - shadow_size = 2 - shadow_color = $background - shadow_boost = 0.4 -} - -# Input (password) -input-field { - monitor = - size = 180, 35 - outline_thickness = 2 - dots_size = .15 - dots_spacing = .6 - dots_center = true - outer_color = $background - inner_color = $color3 - font_color = $foreground - fade_on_empty = false - placeholder_text = - hide_input = false - check_color = $color4 - fail_color = $color1 - fail_text = $FAIL ($ATTEMPTS) - capslock_color = $color1 - position = 0, 40 - halign = center - valign = bottom - shadow_passes = 1 - shadow_size = 2 - shadow_color = $background - shadow_boost = 0.2 -} diff --git a/config/hypr/hyprpaper.conf b/config/hypr/hyprpaper.conf deleted file mode 100644 index 7b89e41b..00000000 --- a/config/hypr/hyprpaper.conf +++ /dev/null @@ -1,8 +0,0 @@ -# default hyprpaper config from colorshell -# hypr-chan is a mascot of Hyprland, it's not made by retrozinndev - -$wallpaper = ~/wallpapers/Default Hypr-chan.jpg - -preload = $wallpaper -wallpaper = , $wallpaper -splash = true diff --git a/config/hypr/scripts/change-wallpaper.sh b/config/hypr/scripts/change-wallpaper.sh deleted file mode 100644 index 23a4c96d..00000000 --- a/config/hypr/scripts/change-wallpaper.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!usr/bin/env bash - -# Prompts the user with dmenu(or dmenu-like app, see hypr/scripts/get-dmenu.sh) -# to choose an image file inside defined $WALLPAPERS. If the user selects -# an entry, it automatically writes changes to the hyprpaper.conf file and -# hot-reloads if hyprpaper is running. -# -------------- -# Licensed under the BSD 3-Clause License -# Made by retrozinndev (João Dias) -# From https://github.com/retrozinndev/colorshell - -style="lighten" # lighten / darken -WALLPAPERS=`[[ -z "$WALLPAPERS" ]] && echo -n "$HOME/wallpapers" || echo -n "$WALLPAPERS"` - -function Write_changes() { - echo "[LOG] Writing to hyprpaper config file" - - echo \ -'$wallpaper'" = $wall - -splash = true -preload = "'$wallpaper'" -wallpaper = , "'$wallpaper'"" | sed -e "s/^(\\[n])//g" > $XDG_CONFIG_HOME/hypr/hyprpaper.conf -} - -function Reload_wallpaper() { - echo "[LOG] Hot-reloading wallpaper" - hyprctl hyprpaper unload all - hyprctl hyprpaper preload $wall - hyprctl hyprpaper wallpaper ", $wall" -} - -function Reload_pywal() { - echo "[LOG] Reloading pywal colorscheme" - wal -t --cols16 $style -i "$wall" -} - -if [[ -z $(ls -A -w1 $WALLPAPERS) ]]; then - notify-send -u normal -a "Wallpaper" "Wallpapers not found" "Couldn't find any wallpaper inside \`~/wallpapers\`, try putting an image you like in there to choose it!" - exit 1 -fi - -if [[ -z $@ ]]; then - # Prompt wallpaper list - selection=`ls -w1 "$WALLPAPERS" | wofi --show drun` - - # Check if input wallpaper is empty - if [[ -z $selection ]]; then - echo "No wallpaper has been selected by user!" - if [[ $RANDOM_WALLPAPER_WHEN_EMPTY == true ]]; then - wall="$WALLPAPERS/$(ls $WALLPAPERS | shuf -n 1)" - echo "Selected random from $WALLPAPERS: $wall" - else - echo "Skipping hyprpaper changes and exiting." - exit 0 - fi - else - wall="$WALLPAPERS/$selection" # wofi if no wallpaper specified - fi -else - wall=$@ -fi - -Reload_pywal -Reload_wallpaper -Write_changes - -exit 0 diff --git a/config/hypr/scripts/color-picker.sh b/config/hypr/scripts/color-picker.sh deleted file mode 100644 index 3af2ce88..00000000 --- a/config/hypr/scripts/color-picker.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -function send_notification() { - (notify-send -u normal -a "color-picker" "$1" "$2" > /dev/null 2>&1) || \ - (echo "$1: $2") -} - -# Check if hyprpicker is installed -if ! command -v hyprpicker > /dev/null; then - send_notification "An error occurred" "Looks like you don't have hyprpicker installed! Try installing it before using the Color Picker tool." - exit 1 -fi - -raw_output=`hyprpicker -al 2> /dev/null` -selected_color=`echo $raw_output | xargs | sed -e 's/ //g'` - -if ! [[ -z $selected_color ]]; then - send_notification "Selected Color" "The selected color is $selected_color, it was also copied to your clipboard!" -fi diff --git a/config/hypr/scripts/exec.sh b/config/hypr/scripts/exec.sh deleted file mode 100644 index af2a3f45..00000000 --- a/config/hypr/scripts/exec.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -# This script executes the provided program with UWSM -# if active, or else normally. -# --------------- -# Licensed under the BSD 3-Clause License -# Made by retrozinndev (João Dias) -# From: https://github.com/retrozinndev/colorshell - - -if uwsm check is-active; then - exec uwsm-app -- "$@" - exit 0 -fi - -if [[ $1 =~ [.]desktop$ ]]; then - gio launch $@ - exit 0 -fi - -exec "$@" diff --git a/config/hypr/scripts/gen-pywal.sh b/config/hypr/scripts/gen-pywal.sh deleted file mode 100644 index b7d9c417..00000000 --- a/config/hypr/scripts/gen-pywal.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -# This script loads/generate color schemes from current -# wallpaper using pywal16. -# ---------- -# Licensed under the BSD 3-Clause License -# Made by retrozinndev (João Dias) -# From https://github.com/retrozinndev/colorshell - -if ! [[ -f "$XDG_CONFIG_HOME/hypr/hyprpaper.conf" ]]; then - echo "[error] wallpaper file not found!" - exit 1 -fi - -raw=`cat "$XDG_CONFIG_HOME/hypr/hyprpaper.conf" | grep '$wallpaper =' | sed -e 's/^$wallpaper = //'` -wallpaper=${raw/\~/"$HOME"} -[[ -d "$XDG_CACHE_HOME/wal" ]] && wal -R || sh $XDG_CONFIG_HOME/hypr/scripts/change-wallpaper.sh "$wallpaper" - -sleep .5 && hyprctl reload diff --git a/config/hypr/scripts/screenshot.sh b/config/hypr/scripts/screenshot.sh deleted file mode 100644 index 56dfd7ca..00000000 --- a/config/hypr/scripts/screenshot.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -# This script handles taking a screenshot using the -# hyprshot tool. -# -------------- -# Licensed under the BSD 3-Clause License -# Made by retrozinndev (João Dias) -# From https://github.com/retrozinndev/colorshell - - -# exit slurp and quit if slurp(region selection) is running -killall slurp && exit 0 - -if [[ -z $(command -v hyprshot) ]]; then - echo "[err] you don't have hyprshot installed, please install it first" - exit 1 -fi - -if [[ "$1" == "full" ]]; then - hyprshot -m active -m output -o "$(xdg-user-dir PICTURES)/Screenshots" - exit 0 -fi - -hyprshot -m region -o "$(xdg-user-dir PICTURES)/Screenshots" diff --git a/config/hypr/shell/autostart.conf b/config/hypr/shell/autostart.conf deleted file mode 100644 index 41ecd843..00000000 --- a/config/hypr/shell/autostart.conf +++ /dev/null @@ -1,21 +0,0 @@ - -# colorshell configuration, please don't modify unless you know what you're doing! - -# Daemons -exec-once = $exec ibus start # use ibus as input method (fixes character compose not working in gtk apps) -exec-once = systemctl enable --user --now hyprpolkitagent # Hyprland's PolKit -exec-once = $exec /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 # GNOME PolKit (activates only if hyprpolkitagent doesn't work/is not installed) -exec-once = systemctl enable --user --now hypridle -exec-once = systemctl enable --user --now gnome-keyring-daemon -exec-once = $exec wl-paste --type text --watch cliphist store -exec-once = $exec wl-paste --type image --watch cliphist store - -# Tools -exec-once = systemctl enable --user --now hyprsunset -exec-once = systemctl enable --user --now hyprpaper - -# Scripts -exec-once = sh $XDG_CONFIG_HOME/hypr/scripts/gen-pywal.sh - -# Shell -exec-once = $exec colorshell.desktop diff --git a/config/hypr/shell/environment.conf b/config/hypr/shell/environment.conf deleted file mode 100644 index 47aa23af..00000000 --- a/config/hypr/shell/environment.conf +++ /dev/null @@ -1,32 +0,0 @@ - -# color-shell configuration, please don't modify unless you know what you're doing! - -# add colorshell to PATH by default -env = PATH, $PATH:$HOME/.local/bin - -# XDG Vars -env = XDG_CONFIG_HOME, $HOME/.config -env = XDG_CACHE_HOME, $HOME/.cache -env = XDG_DATA_HOME, $HOME/.local/share -env = XDG_STATE_HOME, $HOME/.local/state -env = XDG_CURRENT_DESKTOP, Hyprland -env = XDG_SESSION_TYPE, wayland - -# Cursor -env = XCURSOR_THEME, Adwaita -env = XCURSOR_SIZE, 24 -env = HYPRCURSOR_THEME, Adwaita -env = HYPRCURSOR_SIZE, 24 - -# Wayland stuff -env = MOZ_ENABLE_WAYLAND, 1 -env = ELECTRON_OZONE_PLATFORM_HINT, auto - -# QT -env = QT_QPA_PLATFORM, wayland -env = QT_QPA_PLATFORMTHEME, qt5ct -env = QT_AUTO_SCREEN_SCALE_FACTOR, 1 - -# Others -env = ADW_DISABLE_PORTAL, 1 # Fixes prefer-dark setting in some gtk flatpak apps -env = WALLPAPERS, $HOME/wallpapers diff --git a/config/hypr/shell/hyprland.conf b/config/hypr/shell/hyprland.conf deleted file mode 100644 index fafafa98..00000000 --- a/config/hypr/shell/hyprland.conf +++ /dev/null @@ -1,10 +0,0 @@ - -# color-shell configuration, please don't modify unless you know what you're doing! - -source = ./variables.conf -source = ./environment.conf -source = ./bindings.conf -source = ./decorations.conf -source = ./autostart.conf -source = ./rules.conf -source = ./layout.conf diff --git a/config/hypr/shell/layout.conf b/config/hypr/shell/layout.conf deleted file mode 100644 index f8614df6..00000000 --- a/config/hypr/shell/layout.conf +++ /dev/null @@ -1,8 +0,0 @@ - -# color-shell configuration, please don't modify unless you know what you're doing! - -dwindle { - pseudotile = false - preserve_split = true - smart_resizing = true -} diff --git a/config/hypr/shell/variables.conf b/config/hypr/shell/variables.conf deleted file mode 100644 index a1dc98d5..00000000 --- a/config/hypr/shell/variables.conf +++ /dev/null @@ -1,13 +0,0 @@ - -# colorshell configuration, please don't modify unless you know what you're doing! - -############### -## VARIABLES ## -############### -# Wiki: https://wiki.hyprland.org/Hypr-Ecosystem/hyprlang#defining-variables - -$mainMod = SUPER -$scripts = sh $XDG_CONFIG_HOME/hypr/scripts - -# Use this variable to execute apps dinamically (runs with uwsm if being used by compositor) -$exec = $scripts/exec.sh diff --git a/config/hypr/user/autostart.conf b/config/hypr/user/autostart.conf deleted file mode 100644 index c50f3c76..00000000 --- a/config/hypr/user/autostart.conf +++ /dev/null @@ -1,6 +0,0 @@ - -############### -## AUTOSTART ## -############### -# Wiki: https://wiki.hyprland.org/Configuring/Keywords/#executing - diff --git a/config/hypr/user/bindings.conf b/config/hypr/user/bindings.conf deleted file mode 100644 index 61daad40..00000000 --- a/config/hypr/user/bindings.conf +++ /dev/null @@ -1,8 +0,0 @@ -############## -## BINDINGS ## -############## -# Wiki: https://wiki.hyprland.org/Configuring/Binds - - -# Uncomment if you want to press SUPER to launch application search -# bind = $mainMod, $mainMod_L, exec, astal toggle apps-window diff --git a/config/hypr/user/decorations.conf b/config/hypr/user/decorations.conf deleted file mode 100644 index 5600fee1..00000000 --- a/config/hypr/user/decorations.conf +++ /dev/null @@ -1,6 +0,0 @@ -################ -## DECORATION ## -################ -# Wiki: https://wiki.hyprland.org/Configuring/Variables - - diff --git a/config/hypr/user/environment.conf b/config/hypr/user/environment.conf deleted file mode 100644 index e7e2aa07..00000000 --- a/config/hypr/user/environment.conf +++ /dev/null @@ -1,6 +0,0 @@ -################# -## ENVIRONMENT ## -################# -# Wiki: https://wiki.hyprland.org/Configuring/Keywords/#setting-the-environment - - diff --git a/config/hypr/user/hyprland.conf b/config/hypr/user/hyprland.conf deleted file mode 100644 index 13dee9d8..00000000 --- a/config/hypr/user/hyprland.conf +++ /dev/null @@ -1,15 +0,0 @@ - -######################## -## USER CONFIGURATION ## -######################## - -# This sources all user configuration files - -source = ./monitors.conf -source = ./environment.conf -source = ./input.conf -source = ./bindings.conf -source = ./layout.conf -source = ./decorations.conf -source = ./autostart.conf -source = ./rules.conf diff --git a/config/hypr/user/input.conf b/config/hypr/user/input.conf deleted file mode 100644 index 9f43fa2d..00000000 --- a/config/hypr/user/input.conf +++ /dev/null @@ -1,13 +0,0 @@ -########### -## INPUT ## -########### - -# Wiki: https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs - - -############## -## GESTURES ## -############## - -# Wiki: https://wiki.hyprland.org/Configuring/Variables/#gestures - diff --git a/config/hypr/user/layout.conf b/config/hypr/user/layout.conf deleted file mode 100644 index 8d1b8810..00000000 --- a/config/hypr/user/layout.conf +++ /dev/null @@ -1,4 +0,0 @@ -############ -## LAYOUT ## -############ -# Wiki: https://wiki.hyprland.org/Configuring/Dwindle-Layout/#config diff --git a/config/hypr/user/monitors.conf b/config/hypr/user/monitors.conf deleted file mode 100644 index d3553224..00000000 --- a/config/hypr/user/monitors.conf +++ /dev/null @@ -1,17 +0,0 @@ -############## -## MONITORS ## -############## - -# Wiki: https://wiki.hyprland.org/Configuring/Monitors - - -# Monitor -# arg0 -> monitor name(you can get monitor names with `hyprctl monitors`); -# arg1 -> resolution@hertz; -# arg2 -> positioning from the top-left corner; -# arg3 -> scaling; -# arg4 -> variable refresh rate(optional); -# - arg40 -> 1: vrr, 0: no vrr. - -# Example configuration: -# monitor = HDMI-A-1, 1920x1080@60, 0x0, 1, vrr, 0 diff --git a/config/hypr/user/rules.conf b/config/hypr/user/rules.conf deleted file mode 100644 index 2dd99a6f..00000000 --- a/config/hypr/user/rules.conf +++ /dev/null @@ -1,8 +0,0 @@ - -############################ -## WINDOW & LAYER RULES ## -############################ - -# See https://wiki.hyprland.org/Configuring/Window-Rules for -# more information on how to do this - diff --git a/config/hyprland/autostart.conf b/config/hyprland/autostart.conf new file mode 100644 index 00000000..4fa06c09 --- /dev/null +++ b/config/hyprland/autostart.conf @@ -0,0 +1,14 @@ + +# colorshell configuration, please don't modify unless you know what you're doing! + +# Daemons +exec-once = $exec ibus start # use ibus as input method (fixes character compose not working in gtk apps) +exec-once = $exec wl-paste --type text --watch cliphist store +exec-once = $exec wl-paste --type image --watch cliphist store + +# Tools +exec-once = systemctl start --user --now hyprsunset +exec-once = systemctl start --user --now hyprpaper + +# Shell +exec-once = $exec colorshell diff --git a/config/hypr/shell/bindings.conf b/config/hyprland/bindings.conf similarity index 60% rename from config/hypr/shell/bindings.conf rename to config/hyprland/bindings.conf index 8acf3bbf..6bcc3012 100644 --- a/config/hypr/shell/bindings.conf +++ b/config/hyprland/bindings.conf @@ -2,27 +2,19 @@ # some commands don't need $exec (uwsm) if they're just process communication tools bind = $mainMod, SPACE, exec, colorshell runner -bind = $mainMod, F11, fullscreen -bind = , Print, exec, $exec $scripts/screenshot.sh -bind = $mainMod, Print, exec, $exec $scripts/screenshot.sh full +bind = , Print, exec, colorshell screenshot +bind = $mainMod, Print, exec, colorshell screenshot full -# restarts colorshell +# Restart colorshell bind = $mainMod, F7, exec, timeout 0.6s colorshell quit && $exec colorshell.desktop || kill $(cat $XDG_RUNTIME_DIR/colorshell/.pid) ; $exec colorshell -bind = $mainMod, K, exec, colorshell run %terminal -bind = $mainMod, Q, killactive -bind = $mainMod, E, exec, colorshell run %file_manager -bind = $mainMod, F, togglefloating -bind = $mainMod, P, pseudo, -bind = $mainMod, J, togglesplit bind = $mainMod, N, exec, colorshell toggle control-center bind = $mainMod, M, exec, colorshell toggle center-window -bind = $mainMod, L, exec, $exec hyprlock +bind = $mainMod, L, exec, colorshell lock bind = $mainMod, V, exec, colorshell runner '>' bind = $mainMod, W, exec, colorshell runner '\##' -# bind = $mainMod, $mainMod_L, exec, colorshell toggle apps-window bind = $mainMod, $mainMod_l, exec, colorshell peek-workspace-num binde = , XF86AudioLowerVolume, exec, colorshell volume sink-decrease 5 # Decrease volume @@ -35,32 +27,6 @@ bind = , XF86AudioPlay, exec, colorshell media play-pause # Toggle Play/Pause me bind = , XF86MonBrightnessDown, exec, brightnessctl -c backlight s 5%- # Lower monitor brightness bind = , XF86MonBrightnessUp, exec, brightnessctl -c backlight s +5% # Increase monitor brightness -# Move focus with mainMod + arrow keys -bind = $mainMod, left, movefocus, l -bind = $mainMod, right, movefocus, r -bind = $mainMod, up, movefocus, u -bind = $mainMod, down, movefocus, d - - -# Move windows with keyboard keys -bind = $mainMod SHIFT, left, movewindow, l -bind = $mainMod SHIFT, right, movewindow, r -bind = $mainMod SHIFT, up, movewindow, u -bind = $mainMod SHIFT, down, movewindow, d -bind = $mainMod SHIFT, C, centerwindow - - -# Resize windows with arrow keys / hjkl -binde = $mainMod ALT, left, resizeactive, -60 0 -binde = $mainMod ALT, down, resizeactive, 0 60 -binde = $mainMod ALT, up, resizeactive, 0 -60 -binde = $mainMod ALT, right, resizeactive, 60 0 - -binde = $mainMod ALT, H, resizeactive, -60 0 -binde = $mainMod ALT, J, resizeactive, 0 60 -binde = $mainMod ALT, K, resizeactive, 0 -60 -binde = $mainMod ALT, L, resizeactive, 60 0 - # Switch workspaces with mainMod + [0-9] bind = $mainMod, 1, workspace, 1 @@ -86,12 +52,6 @@ bind = $mainMod SHIFT, 8, movetoworkspace, 8 bind = $mainMod SHIFT, 9, movetoworkspace, 9 bind = $mainMod SHIFT, 0, movetoworkspace, 10 -bind = CTRL $mainMod, right, workspace, e+1 -bind = CTRL $mainMod, left, workspace, e-1 - -bind = $mainMod, S, togglespecialworkspace, special -bind = $mainMod SHIFT, S, movetoworkspace, special:special - # Move/resize windows with mainMod + LMB/RMB and dragging bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow diff --git a/config/hypr/shell/decorations.conf b/config/hyprland/decorations.conf similarity index 91% rename from config/hypr/shell/decorations.conf rename to config/hyprland/decorations.conf index 6164bd6a..3552edaa 100644 --- a/config/hypr/shell/decorations.conf +++ b/config/hyprland/decorations.conf @@ -1,7 +1,9 @@ -# color-shell configuration, please don't modify unless you know what you're doing! +# colorshell configuration, please don't modify unless you know what you're doing! -source = ~/.cache/wal/colors-hyprland.conf +# hyprlang noerror true + source = ~/.cache/wal/colors-hyprland.conf +# hyprlang noerror false general { gaps_in = 6 diff --git a/config/hyprland/environment.conf b/config/hyprland/environment.conf new file mode 100644 index 00000000..4ea94583 --- /dev/null +++ b/config/hyprland/environment.conf @@ -0,0 +1,7 @@ +# colorshell configuration, please don't modify unless you know what you're doing! + +# XDG Vars +env = XDG_CONFIG_HOME, $HOME/.config +env = XDG_CACHE_HOME, $HOME/.cache +env = XDG_DATA_HOME, $HOME/.local/share +env = XDG_STATE_HOME, $HOME/.local/state diff --git a/config/hyprland/hyprland.conf b/config/hyprland/hyprland.conf new file mode 100644 index 00000000..bcffe052 --- /dev/null +++ b/config/hyprland/hyprland.conf @@ -0,0 +1,8 @@ +# colorshell configuration, please don't modify unless you know what you're doing! + +source = ./vars.conf +source = ./environment.conf +source = ./bindings.conf +source = ./decorations.conf +source = ./autostart.conf +source = ./rules.conf diff --git a/config/hypr/shell/rules.conf b/config/hyprland/rules.conf similarity index 66% rename from config/hypr/shell/rules.conf rename to config/hyprland/rules.conf index 97ea94c7..283b0ae4 100644 --- a/config/hypr/shell/rules.conf +++ b/config/hyprland/rules.conf @@ -1,35 +1,9 @@ - # colorshell configuration, please don't modify unless you know what you're doing! -# Float -windowrule = match:class nm-connection-editor, float on -windowrule = match:class org.pulseaudio.pavucontrol, float on -windowrule = match:class xdg-desktop-portal.*, float on -windowrule = match:class io.github.kaii_lb.Overskride, float on - - -# Resize -windowrule = match:class org.pulseaudio.pavucontrol, size 50% 50% -windowrule = match:class io.github.kaii_lb.Overskride, size 50% 50% -windowrule = match:class xdg-desktop-portal.*, size 68% 65% - - -# Position -windowrule = match:class org.pulseaudio.pavucontrol, move 49.27% 7.28% -windowrule = match:class io.github.kaii_lb.Overskride, move 49.27% 7.28% - - -# Workspace -windowrule = match:class org.pulseaudio.pavucontrol, workspace e - # Animations windowrule = match:class hyprpolkitagent, animation gnomed -windowrule = match:class org.pulseaudio.pavucontrol, animation slide right -windowrule = match:class io.github.kaii_lb.Overskride, animation slide right windowrule = match:class xdg-desktop-portal.*, animation gnomed -windowrule = match:class hyprsysteminfo, animation gnomed - layerrule = match:namespace selection, animation fade layerrule = match:namespace hyprpicker, animation fade layerrule = match:namespace hyprpaper, animation fade @@ -43,7 +17,6 @@ layerrule = match:namespace background-window-blur, animation fade layerrule = match:namespace .*-popup, animation fade layerrule = match:namespace floating-notifications, no_anim on - # Blur windowrule { name = fix-chromium-xwayland-popup-blur diff --git a/config/hyprland/vars.conf b/config/hyprland/vars.conf new file mode 100644 index 00000000..84307e06 --- /dev/null +++ b/config/hyprland/vars.conf @@ -0,0 +1,6 @@ +############### +## VARIABLES ## +############### + + +$exec = (bash -c "command -v uwsm && uwsm check is-active && echo 'uwsm-app '" | tail -n1 2>/dev/null) diff --git a/config/kitty/kitty.conf b/config/kitty/kitty.conf deleted file mode 100644 index d96eb711..00000000 --- a/config/kitty/kitty.conf +++ /dev/null @@ -1,10 +0,0 @@ -# Default colorshell's kitty configuration, -# please use the user.conf available in this -# same directory. - -include $XDG_CACHE_HOME/wal/colors-kitty.conf -include ./user.conf - -# Dinamically update colorscheme with pywal16 -allow_remote_control yes -listen_on unix:@kitty diff --git a/config/kitty/user.conf b/config/kitty/user.conf deleted file mode 100644 index 060ca578..00000000 --- a/config/kitty/user.conf +++ /dev/null @@ -1,2 +0,0 @@ -# User configuration -# Add your settings for kitty here diff --git a/resources.gresource.xml b/resources.gresource.xml index dec2c2bc..28a2015c 100644 --- a/resources.gresource.xml +++ b/resources.gresource.xml @@ -27,4 +27,18 @@ icons/shield-safe-symbolic.svg icons/user-trash-symbolic.svg + + + + config/hyprland/environment.conf + config/hyprland/vars.conf + config/hyprland/decorations.conf + config/hyprland/bindings.conf + config/hyprland/rules.conf + config/hyprland/hyprland.conf + + From 77537583a55e60e231b379ee1647d8cee86dcf54 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 14 Jan 2026 11:01:49 -0300 Subject: [PATCH 02/35] :wrench: chore: move compositor configuration to resources --- config/hyprland/bindings.conf | 57 ------------------- config/hyprland/hyprland.conf | 8 --- resources/config/hyprland/.last-updated | 1 + .../config}/hyprland/autostart.conf | 0 resources/config/hyprland/bindings.conf | 57 +++++++++++++++++++ .../config}/hyprland/decorations.conf | 0 .../config}/hyprland/environment.conf | 0 .../config}/hyprland/rules.conf | 0 .../config}/hyprland/vars.conf | 0 9 files changed, 58 insertions(+), 65 deletions(-) delete mode 100644 config/hyprland/bindings.conf delete mode 100644 config/hyprland/hyprland.conf create mode 100644 resources/config/hyprland/.last-updated rename {config => resources/config}/hyprland/autostart.conf (100%) create mode 100644 resources/config/hyprland/bindings.conf rename {config => resources/config}/hyprland/decorations.conf (100%) rename {config => resources/config}/hyprland/environment.conf (100%) rename {config => resources/config}/hyprland/rules.conf (100%) rename {config => resources/config}/hyprland/vars.conf (100%) diff --git a/config/hyprland/bindings.conf b/config/hyprland/bindings.conf deleted file mode 100644 index 6bcc3012..00000000 --- a/config/hyprland/bindings.conf +++ /dev/null @@ -1,57 +0,0 @@ -# colorshell-specific configuration, please don't modify unless you know what you're doing! -# some commands don't need $exec (uwsm) if they're just process communication tools - -bind = $mainMod, SPACE, exec, colorshell runner - -bind = , Print, exec, colorshell screenshot -bind = $mainMod, Print, exec, colorshell screenshot full - -# Restart colorshell -bind = $mainMod, F7, exec, timeout 0.6s colorshell quit && $exec colorshell.desktop || kill $(cat $XDG_RUNTIME_DIR/colorshell/.pid) ; $exec colorshell - -bind = $mainMod, N, exec, colorshell toggle control-center -bind = $mainMod, M, exec, colorshell toggle center-window -bind = $mainMod, L, exec, colorshell lock -bind = $mainMod, V, exec, colorshell runner '>' -bind = $mainMod, W, exec, colorshell runner '\##' - -bind = $mainMod, $mainMod_l, exec, colorshell peek-workspace-num - -binde = , XF86AudioLowerVolume, exec, colorshell volume sink-decrease 5 # Decrease volume -binde = , XF86AudioRaiseVolume, exec, colorshell volume sink-increase 5 # Increase volume -bind = , XF86AudioMute, exec, colorshell volume sink-mute # Mute -bind = , XF86AudioPrev, exec, colorshell media previous # Previous media -bind = , XF86AudioNext, exec, colorshell media next # Next media -bind = , XF86AudioPlay, exec, colorshell media play-pause # Toggle Play/Pause media - -bind = , XF86MonBrightnessDown, exec, brightnessctl -c backlight s 5%- # Lower monitor brightness -bind = , XF86MonBrightnessUp, exec, brightnessctl -c backlight s +5% # Increase monitor brightness - - -# Switch workspaces with mainMod + [0-9] -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 a workspace with mainMod + SHIFT + [0-9] -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 - -# Move/resize windows with mainMod + LMB/RMB and dragging -bindm = $mainMod, mouse:272, movewindow -bindm = $mainMod, mouse:273, resizewindow diff --git a/config/hyprland/hyprland.conf b/config/hyprland/hyprland.conf deleted file mode 100644 index bcffe052..00000000 --- a/config/hyprland/hyprland.conf +++ /dev/null @@ -1,8 +0,0 @@ -# colorshell configuration, please don't modify unless you know what you're doing! - -source = ./vars.conf -source = ./environment.conf -source = ./bindings.conf -source = ./decorations.conf -source = ./autostart.conf -source = ./rules.conf diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated new file mode 100644 index 00000000..5f18c3ee --- /dev/null +++ b/resources/config/hyprland/.last-updated @@ -0,0 +1 @@ +january 14, 10:20 diff --git a/config/hyprland/autostart.conf b/resources/config/hyprland/autostart.conf similarity index 100% rename from config/hyprland/autostart.conf rename to resources/config/hyprland/autostart.conf diff --git a/resources/config/hyprland/bindings.conf b/resources/config/hyprland/bindings.conf new file mode 100644 index 00000000..7ebea8e4 --- /dev/null +++ b/resources/config/hyprland/bindings.conf @@ -0,0 +1,57 @@ +# colorshell-specific configuration, please don't modify unless you know what you're doing! +# some commands don't need $exec (uwsm) if they're just process communication tools + +bind = SUPER, SPACE, exec, colorshell runner + +bind = , Print, exec, colorshell screenshot +bind = SUPER, Print, exec, colorshell screenshot full + +# Restart colorshell +bind = SUPER, F7, exec, timeout 0.6s colorshell quit && $exec colorshell.desktop || kill $(cat $XDG_RUNTIME_DIR/colorshell/.pid) ; $exec colorshell + +bind = SUPER, N, exec, colorshell toggle control-center +bind = SUPER, M, exec, colorshell toggle center-window +bind = SUPER, L, exec, colorshell lock +bind = SUPER, V, exec, colorshell runner '>' +bind = SUPER, W, exec, colorshell runner '\##' + +bind = SUPER, SUPER_l, exec, colorshell peek-workspace-num + +binde = , XF86AudioLowerVolume, exec, colorshell volume sink-decrease 5 # Decrease volume +binde = , XF86AudioRaiseVolume, exec, colorshell volume sink-increase 5 # Increase volume +bind = , XF86AudioMute, exec, colorshell volume sink-mute # Mute +bind = , XF86AudioPrev, exec, colorshell media previous # Previous media +bind = , XF86AudioNext, exec, colorshell media next # Next media +bind = , XF86AudioPlay, exec, colorshell media play-pause # Toggle Play/Pause media + +bind = , XF86MonBrightnessDown, exec, brightnessctl -c backlight s 5%- # Lower monitor brightness +bind = , XF86MonBrightnessUp, exec, brightnessctl -c backlight s +5% # Increase monitor brightness + + +# Switch workspaces with mainMod + [0-9] +bind = SUPER, 1, workspace, 1 +bind = SUPER, 2, workspace, 2 +bind = SUPER, 3, workspace, 3 +bind = SUPER, 4, workspace, 4 +bind = SUPER, 5, workspace, 5 +bind = SUPER, 6, workspace, 6 +bind = SUPER, 7, workspace, 7 +bind = SUPER, 8, workspace, 8 +bind = SUPER, 9, workspace, 9 +bind = SUPER, 0, workspace, 10 + +# Move active window to a workspace with mainMod + SHIFT + [0-9] +bind = SUPER SHIFT, 1, movetoworkspace, 1 +bind = SUPER SHIFT, 2, movetoworkspace, 2 +bind = SUPER SHIFT, 3, movetoworkspace, 3 +bind = SUPER SHIFT, 4, movetoworkspace, 4 +bind = SUPER SHIFT, 5, movetoworkspace, 5 +bind = SUPER SHIFT, 6, movetoworkspace, 6 +bind = SUPER SHIFT, 7, movetoworkspace, 7 +bind = SUPER SHIFT, 8, movetoworkspace, 8 +bind = SUPER SHIFT, 9, movetoworkspace, 9 +bind = SUPER SHIFT, 0, movetoworkspace, 10 + +# Move/resize windows with mainMod + LMB/RMB and dragging +bindm = SUPER, mouse:272, movewindow +bindm = SUPER, mouse:273, resizewindow diff --git a/config/hyprland/decorations.conf b/resources/config/hyprland/decorations.conf similarity index 100% rename from config/hyprland/decorations.conf rename to resources/config/hyprland/decorations.conf diff --git a/config/hyprland/environment.conf b/resources/config/hyprland/environment.conf similarity index 100% rename from config/hyprland/environment.conf rename to resources/config/hyprland/environment.conf diff --git a/config/hyprland/rules.conf b/resources/config/hyprland/rules.conf similarity index 100% rename from config/hyprland/rules.conf rename to resources/config/hyprland/rules.conf diff --git a/config/hyprland/vars.conf b/resources/config/hyprland/vars.conf similarity index 100% rename from config/hyprland/vars.conf rename to resources/config/hyprland/vars.conf From 5089be9a7c4d87196f55879bbe8795f1276bc521 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 14 Jan 2026 11:02:30 -0300 Subject: [PATCH 03/35] :wrench: chore(resources): remove unnecessary config, add hyprland/.last-updated --- resources.gresource.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources.gresource.xml b/resources.gresource.xml index 28a2015c..44970f72 100644 --- a/resources.gresource.xml +++ b/resources.gresource.xml @@ -30,12 +30,12 @@ + config/hyprland/.last-updated config/hyprland/environment.conf config/hyprland/vars.conf config/hyprland/decorations.conf config/hyprland/bindings.conf config/hyprland/rules.conf - config/hyprland/hyprland.conf + + config/hyprlock.conf + + config/hyprland/.last-updated diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index 5f18c3ee..b7b7001d 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -january 14, 10:20 +january 15, 12:28 diff --git a/resources/config/hyprland/autostart.conf b/resources/config/hyprland/autostart.conf index 4fa06c09..c5b0471b 100644 --- a/resources/config/hyprland/autostart.conf +++ b/resources/config/hyprland/autostart.conf @@ -9,6 +9,3 @@ exec-once = $exec wl-paste --type image --watch cliphist store # Tools exec-once = systemctl start --user --now hyprsunset exec-once = systemctl start --user --now hyprpaper - -# Shell -exec-once = $exec colorshell diff --git a/resources/config/hyprland/bindings.conf b/resources/config/hyprland/bindings.conf index 7ebea8e4..c04b8295 100644 --- a/resources/config/hyprland/bindings.conf +++ b/resources/config/hyprland/bindings.conf @@ -1,5 +1,4 @@ # colorshell-specific configuration, please don't modify unless you know what you're doing! -# some commands don't need $exec (uwsm) if they're just process communication tools bind = SUPER, SPACE, exec, colorshell runner @@ -26,32 +25,3 @@ bind = , XF86AudioPlay, exec, colorshell media play-pause # Toggle Play/Pause me bind = , XF86MonBrightnessDown, exec, brightnessctl -c backlight s 5%- # Lower monitor brightness bind = , XF86MonBrightnessUp, exec, brightnessctl -c backlight s +5% # Increase monitor brightness - - -# Switch workspaces with mainMod + [0-9] -bind = SUPER, 1, workspace, 1 -bind = SUPER, 2, workspace, 2 -bind = SUPER, 3, workspace, 3 -bind = SUPER, 4, workspace, 4 -bind = SUPER, 5, workspace, 5 -bind = SUPER, 6, workspace, 6 -bind = SUPER, 7, workspace, 7 -bind = SUPER, 8, workspace, 8 -bind = SUPER, 9, workspace, 9 -bind = SUPER, 0, workspace, 10 - -# Move active window to a workspace with mainMod + SHIFT + [0-9] -bind = SUPER SHIFT, 1, movetoworkspace, 1 -bind = SUPER SHIFT, 2, movetoworkspace, 2 -bind = SUPER SHIFT, 3, movetoworkspace, 3 -bind = SUPER SHIFT, 4, movetoworkspace, 4 -bind = SUPER SHIFT, 5, movetoworkspace, 5 -bind = SUPER SHIFT, 6, movetoworkspace, 6 -bind = SUPER SHIFT, 7, movetoworkspace, 7 -bind = SUPER SHIFT, 8, movetoworkspace, 8 -bind = SUPER SHIFT, 9, movetoworkspace, 9 -bind = SUPER SHIFT, 0, movetoworkspace, 10 - -# Move/resize windows with mainMod + LMB/RMB and dragging -bindm = SUPER, mouse:272, movewindow -bindm = SUPER, mouse:273, resizewindow diff --git a/resources/config/hyprlock.conf b/resources/config/hyprlock.conf new file mode 100644 index 00000000..ed95a563 --- /dev/null +++ b/resources/config/hyprlock.conf @@ -0,0 +1,145 @@ +# Source colors from pywal +source = ~/.cache/wal/colors-hyprland.conf + +############## +# LOCKSCREEN # +############## + +# Wiki: https://wiki.hyprland.org/Hypr-Ecosystem/hyprlock + + +# Variables +$font = Adwaita Sans Regular +$clockFont = Adwaita Sans Black +$minimalFont = Noto Sans Mono +$getActivePlayer = colorshell media bus-name | sed -e 's/^org.mpris.MediaPlayer2.//' + +general { + disable_loading_bar = true + hide_cursor = false + text_trim = false + fractional_scaling = 2 +} + +auth { + pam { + enabled = true + } + + fingerprint { + enabled = false + ready_message = Waiting for Fingerprint + present_message = Scanning + } +} + +background { + monitor = + path = $wallpaper + blur_passes = 3 + color = $background +} + +# Time +label { + monitor = + text = cmd[update:30000] echo -n "$(date +"%R")" # 24-hours + # text = cmd[update:30000] echo -n "$(date +"%I:%M %p")" # 12-hours (AM/PM) + color = $foreground + shadow_passes = 1 + shadow_size = 2 + shadow_color = $background + shadow_boost = 0.4 + font_size = 120 + font_family = $clockFont + position = 0, -60 + halign = center + valign = top +} + +# Date +label { + monitor = + text = cmd[update:43200000] echo -n "$(date +"%A, %d %B %Y")" + color = $foreground + shadow_passes = 1 + shadow_size = 2 + shadow_color = $background + shadow_boost = 0.4 + font_size = 20 + font_family = $font + position = 0, -250 + halign = center + valign = top +} + +# Logged user +label { + monitor = + font_size = 6 + font_family = $minimalFont + color = $foreground + text = cmd[update:0] echo -n "Logged as $USER in $(hostnamectl hostname)" + halign = center + valign = bottom + position = 0, 5 +} + +# Media +label { + monitor = + font_size = 12 + font_family = Cantarell + color = $foreground + text = cmd[update:1000] bash -c 'playerctl metadata && echo -e "󰎇 $(playerctl --player `$getActivePlayer` metadata title) - $(playerctl --player `$getActivePlayer` metadata artist)"' | tail -n 1 + shadow_passes = 1 + shadow_size = 2 + shadow_color = $background + shadow_boost = 0.4 + halign = center + valign = center + position = 0, 180 +} + +# Avatar +image { + monitor = + path = ~/.face + size = 72 + border_color = $color2 + border_size = 2 + position = 0, 100 + halign = center + valign = bottom + shadow_passes = 1 + shadow_size = 2 + shadow_color = $background + shadow_boost = 0.4 +} + +# Input (password) +input-field { + monitor = + size = 180, 35 + outline_thickness = 2 + dots_size = .15 + dots_spacing = .6 + dots_center = true + outer_color = $background + inner_color = $color3 + font_color = $foreground + fade_on_empty = false + placeholder_text = + hide_input = false + check_color = $color4 + fail_color = $color1 + fail_text = $FAIL ($ATTEMPTS) + capslock_color = $color1 + position = 0, 40 + halign = center + valign = bottom + shadow_passes = 1 + shadow_size = 2 + shadow_color = $background + shadow_boost = 0.2 +} From 6f238a0d3a09375d6802d2bdf97bdded391256e1 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 15 Jan 2026 16:08:18 -0300 Subject: [PATCH 06/35] :wrench: chore(cli): add `lock` and `screenshot` commands --- src/cli/modules/lock.ts | 1 + src/cli/modules/screenshot.ts | 1 + src/modules/arg-handler.ts | 46 +++++++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/cli/modules/lock.ts create mode 100644 src/cli/modules/screenshot.ts diff --git a/src/cli/modules/lock.ts b/src/cli/modules/lock.ts new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/src/cli/modules/lock.ts @@ -0,0 +1 @@ +// TODO diff --git a/src/cli/modules/screenshot.ts b/src/cli/modules/screenshot.ts new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/src/cli/modules/screenshot.ts @@ -0,0 +1 @@ +// TODO diff --git a/src/modules/arg-handler.ts b/src/modules/arg-handler.ts index f0ccf8b9..84e0666b 100644 --- a/src/modules/arg-handler.ts +++ b/src/modules/arg-handler.ts @@ -13,6 +13,9 @@ import { execApp } from "./apps"; import Media from "./media"; import AstalIO from "gi://AstalIO"; import AstalMpris from "gi://AstalMpris"; +import { exec, execAsync } from "ags/process"; +import GLib from "gi://GLib?version=2.0"; +import { Notifications } from "./notifications"; export type RemoteCaller = { @@ -42,14 +45,16 @@ ${DEVEL ? ` Development Tools: dev: tools to help debugging colorshell ` : ""} -Other options: +Others: runner [initial_text]: open the application runner, optionally add an initial search. run app[.desktop] [client_modifiers]: run applications from the cli, see "run help". + lock: quick-lock your user with hyprlock. + screenshot [full]: select an area to screenshot(optionally add "full" to take a complete screenshot). peek-workspace-num [millis]: peek the workspace numbers on bar window. v, version: display current colorshell version. h, help: shows this help message. -2025 (c) retrozinndev's colorshell, licensed under the BSD 3-Clause License. +2026 (c) colorshell, licensed under the BSD 3-Clause License. https://github.com/retrozinndev/colorshell `.trim(); @@ -122,6 +127,43 @@ export function handleArguments(cmd: RemoteCaller, args: Array): number }); cmd.print_literal("Toggled workspace numbers"); return 0; + + case "lock": + execApp( + `hyprlock --config ${Shell.runtimeDir.peek_path()!}/config/hyprlock.conf` + ); + return 0; + + case "screenshot": + const outputDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) + ?? `${GLib.get_home_dir()}/Pictures/Screenshots`; + + try { + exec("killall slurp"); // kill any active selection layer + } catch(_) {} + + if(args[1] !== undefined && /^f(ull)?$/.test(args[1])) { + execAsync(`hyprshot -m active -m output -o ${outputDir}`).catch(e => + Notifications.getDefault().sendNotification({ + appName: "hyprshot", + summary: "Failed to take screenshot", + body: (e as Error)?.message + }) + ); + return 0; + } + + execAsync(`hyprshot -m region -o ${outputDir}`).catch(e => { + if((e as Error).message.toLowerCase().split('\n')[0] === "selection cancelled") + return; + + Notifications.getDefault().sendNotification({ + appName: "hyprshot", + summary: "Failed to take screenshot", + body: (e as Error)?.message + }) + }); + return 0; } cmd.printerr_literal("Error: command not found! try checking help"); From 5af8e7b08a57044f388b5fd7d9ce6689b2d5fbfa Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 15 Jan 2026 16:09:27 -0300 Subject: [PATCH 07/35] :wrench: chore: organize runtime config files under the shell runtime directory instead of data also moved socket to XDG_RUNTIME_DIR/colorshell/.sock --- scripts/socket.sh | 2 +- src/app.ts | 76 +++++++++++++++++++---------- src/modules/compositors/hyprland.ts | 23 +++++++-- 3 files changed, 70 insertions(+), 31 deletions(-) diff --git a/scripts/socket.sh b/scripts/socket.sh index 883d1b75..227334b9 100644 --- a/scripts/socket.sh +++ b/scripts/socket.sh @@ -5,7 +5,7 @@ if gdbus introspect --session \ --object-path /io/github/retrozinndev/colorshell > /dev/null 2>&1; then if command -v socat > /dev/null 2>&1; then - echo "$@" | socat - "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/colorshell.sock" + echo "$@" | socat - "${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}/colorshell/.sock" exit 0 else echo "[warn] \`socat\` not installed, falling back to remote instance communication" diff --git a/src/app.ts b/src/app.ts index 2730c613..fb8f40fe 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,7 +21,7 @@ import { createBinding, createComputed, createRoot, getScope, onCleanup, Scope } import { OSDModes, triggerOSD } from "./window/osd"; import { programArgs, programInvocationName } from "system"; import { setConsoleLogDomain } from "console"; -import { createScopedConnection, createSubscription, encoder, secureBaseBinding } from "./modules/utils"; +import { createScopedConnection, createSubscription, encoder, makeDirectory, secureBaseBinding } from "./modules/utils"; import { exec } from "ags/process"; import { NightLight } from "./modules/nightlight"; import { Backlights } from "./modules/backlight"; @@ -47,17 +47,23 @@ const runnerPlugins: Array = [ const defaultWindows: Array = [ "bar" ]; -GLib.unsetenv("LD_PRELOAD"); +GLib.unsetenv("LD_PRELOAD"); // so child processes won't run with gtk-layer-shell @register({ GTypeName: "Shell" }) export class Shell extends Adw.Application { private static instance: Shell; + public static runtimeDir: Gio.File = Gio.File.new_for_path(`${ + GLib.get_user_runtime_dir() ?? `/run/user/${exec("id -u").trim()}`}/colorshell`); + public static dataDir: Gio.File = Gio.File.new_for_path(`${ + GLib.get_user_data_dir() ?? `${GLib.get_home_dir()}/.local/share`}/colorshell`); + public static cacheDir: Gio.File = Gio.File.new_for_path(`${ + GLib.get_user_cache_dir() ?? `${GLib.get_home_dir()}/.cache`}/colorshell`); + #scope!: Scope; #connections = new Map | number>(); #providers: Array = []; - #gresource: Gio.Resource|null = null; #socketService!: Gio.SocketService; #socketFile!: Gio.File; @@ -177,38 +183,57 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re private init(): void { console.log(`Colorshell: Initializing things`); - Adw.init(); + !Shell.runtimeDir.query_exists(null) && + Shell.runtimeDir.make_directory_with_parents(null); + + // TODO: find a way to get the pid of colorshell (it's ridiculous that glib doesn't have a method for this, really) + //const pidFile = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/.pid`); + //pidFile.replace_contents(encoder.encode(""), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); // load gresource from build-defined path try { - const gresourcesPath: string = GRESOURCES_FILE.startsWith('/') ? GRESOURCES_FILE : (GRESOURCES_FILE.split('/').filter(s => - s !== "" - ).map(path => { - // support environment variables at runtime - if(/^\$/.test(path)) { - const env = GLib.getenv(path.replace(/^\$/, "")); - if(env === null) - throw new Error(`Couldn't get environment variable: ${path}`); - - return env; - } - return path; - }).join('/')); - this.#gresource = Gio.Resource.load(gresourcesPath); - Gio.resources_register(this.#gresource); - - // add icons - Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!) - .add_resource_path("/io/github/retrozinndev/colorshell/icons") + const gresourcesPath: string = !/^\//.test(GRESOURCES_FILE) ? + (GRESOURCES_FILE.split('/').filter(s => s !== "").map(path => { + // support environment variables at runtime + if(/^\$/.test(path)) { + const env = GLib.getenv(path.replace(/^\$/, "")); + if(env === null) + throw new Error(`Couldn't get environment variable: ${path}`); + + return env; + } + return path; + }).join('/')) + : GRESOURCES_FILE; + + const gresource = Gio.Resource.load(gresourcesPath); + Gio.resources_register(gresource); } catch(_e) { const e = _e as Error; console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`); } + // add icons + Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!) + .add_resource_path("/io/github/retrozinndev/colorshell/icons") + + makeDirectory(`${Shell.runtimeDir.peek_path()!}/config`); + Gio.resources_enumerate_children( + "/io/github/retrozinndev/colorshell/config", + Gio.ResourceLookupFlags.NONE + ).forEach(name => { + if(!/\..*$/.test(name)) + return; + + const data = Gio.resources_lookup_data(`/io/github/retrozinndev/colorshell/config/${name}`, null), + file = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/config/${name}`); + + file.replace_contents_bytes_async(data, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null, null); + }); + initCompositor(); - this.#socketFile = Gio.File.new_for_path(`${GLib.get_user_runtime_dir() ?? - `/run/user/${exec("id -u").trim()}`}/colorshell.sock`); + this.#socketFile = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/.sock`); if(this.#socketFile.query_exists(null)) { console.log(`Colorshell: Deleting previous instance's socket`); @@ -281,7 +306,6 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re this.#connections.set(this, this.connect("shutdown", () => this.#scope.dispose())); NightLight.getDefault(); - Media.getDefault(); Clipboard.getDefault(); diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 0932e76d..9c185c11 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -6,13 +6,15 @@ import GLib from "gi://GLib?version=2.0"; import { Socket } from "../socket"; import { exec } from "ags/process"; import Gio from "gi://Gio?version=2.0"; -import { decoder, makeDirectory } from "../utils"; +import { decoder, makeDirectory, playSystemBell } from "../utils"; +import { Shell } from "../../app"; @register({ GTypeName: "ClshCompositorHyprland" }) export class CompositorHyprland extends Compositor { #eventSock: Socket; - #configDir: Gio.File = Gio.File.new_for_path(`${GLib.get_user_data_dir()}/colorshell/hyprland`); + #configDir: Gio.File = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/config/hyprland`); + #ignoreConfigReload: boolean = false; hyprland: AstalHyprland.Hyprland = AstalHyprland.get_default(); constructor() { @@ -86,9 +88,15 @@ export class CompositorHyprland extends Compositor { this._focusedClient = null; this.notify("focused-client"); break; + case "configreloaded": - this.source(); + if(!this.#ignoreConfigReload) + this.source(); break; + + case "beep": + playSystemBell(); + break; } } @@ -98,6 +106,12 @@ export class CompositorHyprland extends Compositor { private source(): void { + if(this.#ignoreConfigReload) { + this.#ignoreConfigReload = false; + return; + } + + this.#ignoreConfigReload = true; const names = Gio.resources_enumerate_children( "/io/github/retrozinndev/colorshell/config/hyprland", Gio.ResourceLookupFlags.NONE @@ -148,7 +162,7 @@ export class CompositorHyprland extends Compositor { ) ] satisfies [string, GLib.Bytes]); - makeDirectory(`${GLib.get_user_data_dir()}/colorshell/hyprland`); + makeDirectory(`${Shell.runtimeDir.peek_path()!}/config/hyprland`); files.forEach(([name, data]) => { const file = Gio.File.new_for_path(`${this.#configDir.peek_path()!}/${name}`); @@ -187,6 +201,7 @@ export namespace CompositorHyprland { | "windowtitlev2" | "workspacev2" | "focusedmon" + | "beep" | "focusedmonv2"; export type Client = { From 6813062f8377950c8e5f5c576e820b78a00cbae9 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 15 Jan 2026 16:57:41 -0300 Subject: [PATCH 08/35] :wrench: chore(install, update): do not modify/add config files --- install.sh | 41 +++++++++------------------------------ update.sh | 57 ++++++++++++++++-------------------------------------- 2 files changed, 26 insertions(+), 72 deletions(-) diff --git a/install.sh b/install.sh index ec2abf2d..fde83003 100755 --- a/install.sh +++ b/install.sh @@ -3,11 +3,11 @@ trap "printf \"\nOk, quitting beacuse you entered an exit signal. (SIGINT).\n\"; exit 1" SIGINT trap "printf \"\nOh noo!! Some application just killed the script! (SIGTERM)\"; exit 2" SIGTERM -XDG_DATA_HOME=`[[ -z "$XDG_DATA_HOME" ]] && echo -n "$HOME/.local/share" || echo -n "$XDG_DATA_HOME"` -XDG_CACHE_HOME=`[[ -z "$XDG_CACHE_HOME" ]] && echo -n $HOME/.cache || echo -n $XDG_CACHE_HOME` -XDG_CONFIG_HOME=`[[ -z "$XDG_CONFIG_HOME" ]] && echo -n "$HOME/.config" || echo -n "$XDG_CONFIG_HOME"` -BIN_HOME=`[[ -z "$BIN_HOME" ]] && echo -n "$HOME/.local/bin" || echo -n "$BIN_HOME"` -APPS_HOME=`[[ -z "$APPS_HOME" ]] && echo -n "$XDG_DATA_HOME/applications" || echo -n "$APPS_HOME"` +XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} +XDG_CACHE_HOME=${XDG_CACHE_HOME:-"$HOME/.cache"} +XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$HOME/.config"} +BIN_HOME=${BIN_HOME:-"$HOME/.local/bin"} +APPS_HOME=${APPS_HOME:-"$XDG_DATA_HOME/applications"} skip_prompts=`[[ "$1" == -y ]] && echo -n true` is_standalone=`(git remote -v > /dev/null 2>&1) || echo -n true` @@ -45,16 +45,7 @@ sleep .5 echo "Welcome to the colorshell installation script!" # Warn user of possible issues -Send_log warn "!! By running this script, you assume total responsability for any issues that may occur with your filesystem" - -[[ -z $skip_prompts ]] && \ - Send_log warn "Your current Hyprland and kitty configuration will be overwritten, accept the backup prompt if you still want them" - -[[ -z $skip_prompts ]] && \ - Ask "Do you want to backup what is going to be modified/overwritten?" - -[[ $answer == y ]] || [[ $skip_prompts ]] && \ - Backup_config $repo_directory +Send_log warn "!! By running this, you're assuming total responsability for any issues that may occur with your filesystem" [[ -z "$skip_prompts" ]] && \ Ask "Do you want to start the shell installation?" @@ -68,7 +59,7 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then Send_log "repo is already cloned! let's just fetch the latest changes..." git -C "$repo_directory" stash # if there are changes, let's just stash them git -C "$repo_directory" checkout ryo - git -C "$repo_directory" fetch && git -C "$repo_directory" pull --rebase + git -C "$repo_directory" fetch && git -C "$repo_directory" pull --rebase # rebase just in case else git clone https://github.com/retrozinndev/colorshell.git "$repo_directory" fi @@ -85,23 +76,9 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then git -C "$repo_directory" checkout $latest_tag > /dev/null 2>&1 fi - Send_log "Starting installation..." - - Send_log "Installing default configurations" - for dir in $(ls -A -w1 "$repo_directory/config"); do - dest=$XDG_CONFIG_HOME/$dir - - Send_log "Installing $dir in $dest" - mkdir -p `dirname "$dest"` # create parents - - [[ -d "$dest" ]] || [[ -f "$dest" ]] && \ - rm -rf $dest - - cp -rf $repo_directory/config/$dir "$dest" # copy - done - + Send_log "Starting build process..." Send_log "Updating dependencies" - pnpm -C "$repo_directory" i && pnpm -C "$repo_directory" update + pnpm -C "$repo_directory" i && pnpm -C "$repo_directory" update > /dev/null Send_log "Building colorshell" pnpm -C "$repo_directory" build:release diff --git a/update.sh b/update.sh index 61aed3da..4ec54c50 100755 --- a/update.sh +++ b/update.sh @@ -3,25 +3,17 @@ trap "printf \"\nOk, quitting beacuse you entered an exit signal. (SIGINT).\n\"; exit 1" SIGINT trap "printf \"\nOh noo!! Some application just killed the script! (SIGTERM)\"; exit 2" SIGTERM -XDG_DATA_HOME=`[[ -z "$XDG_DATA_HOME" ]] && echo -n "$HOME/.local/share" || echo -n "$XDG_DATA_HOME"` -XDG_CACHE_HOME=`[[ -z "$XDG_CACHE_HOME" ]] && echo -n $HOME/.cache || echo -n $XDG_CACHE_HOME` -XDG_CONFIG_HOME=`[[ -z "$XDG_CONFIG_HOME" ]] && echo -n "$HOME/.config" || echo -n "$XDG_CONFIG_HOME"` -BIN_HOME=`[[ -z "$BIN_HOME" ]] && echo -n "$HOME/.local/bin" || echo -n "$BIN_HOME"` -APPS_HOME=`[[ -z "$APPS_HOME" ]] && echo -n "$XDG_DATA_HOME/applications" || echo -n "$APPS_HOME"` +XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} +XDG_CACHE_HOME=${XDG_CACHE_HOME:-"$HOME/.cache"} +XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$HOME/.config"} +BIN_HOME=${BIN_HOME:-"$HOME/.local/bin"} +APPS_HOME=${APPS_HOME:-"$XDG_DATA_HOME/applications"} skip_prompts=`[[ "$1" == -y ]] && echo -n true` is_standalone=`(git remote -v > /dev/null 2>&1) || echo -n true` temp_dir="$XDG_CACHE_HOME/colorshell-installer" repo_directory=`[[ "$is_standalone" ]] && echo -n "$temp_dir/repo" || echo -n "."` -shell_configs=( - "hypr/shell" - "hypr/hypridle.conf" - "hypr/scripts" - "hypr/hyprland.conf" - "hypr/hyprlock.conf" - "kitty/kitty.conf" -) # source utils script before installation @@ -60,10 +52,9 @@ else fi # Warn user of possible issues -Send_log warn "!! By running this, you assume total responsability for \ -issues that can occur to your filesystem" -Send_log "The updater will only change shell configs, like hypr/shell \ -and kitty/kitty.conf, not user ones(hypr/user, kitty/user.conf)" +Send_log warn "!! By running this, you're assuming total responsability for any \ +issues that may occur to your filesystem" +Send_log "The updater won't modify your config files, it'll only update colorshell" [[ -z $skip_prompts ]] && \ Ask "Do you want to update colorshell?" @@ -82,34 +73,20 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then fi fi - Ask "Nice! Update to latest stable version instead of unstable(latest commit)?" + Send_log "Fetching latest release from colorshell repository" + # use `head -n1` because for some reason, github api shows the same release 3 times :'( + latest_tag=`curl -s "$repo_api_url/releases" | jq -r '. | select(.[].prerelease == false) | .[0].tag_name' | head -n1` + + Send_log "Done fetching" + Ask "Nice! Use stable $latest_tag instead of the unstable/pre-release version?" if [[ -z "$skip_prompts" ]] && [[ "$answer" == y ]]; then - Send_log "fetching latest release from colorshell repository" - # use `head -n1` because for some reason, github api shows the same release 3 times :'( - latest_tag=`curl -s "$repo_api_url/releases" | jq -r '. | select(.[].prerelease == false) | .[0].tag_name' | head -n1` - - Send_log "Done fetching" - Send_log "Checking out latest non-pre-release version: $latest_tag" + Send_log "Checking out $latest_tag" git -C "$repo_directory" checkout $latest_tag > /dev/null 2>&1 fi - Send_log "Updating..." - - Send_log "Updating configurations" - for dir in ${shell_configs[@]}; do - dest=$XDG_CONFIG_HOME/$dir - - Send_log "Installing $dir in $dest" - mkdir -p `dirname "$dest"` # create parents - - [[ -d "$dest" ]] || [[ -f "$dest" ]] && \ - rm -rf $dest - - cp -rf $repo_directory/config/$dir "$dest" # copy - done - - Send_log "Updating dependencies" + Send_log "Starting update process..." + Send_log "Updating colorshell dependencies" pnpm -C "$repo_directory" i && pnpm -C "$repo_directory" update Send_log "Building colorshell" From d6f36d8bb0a2cdbdad34f6ddf171d65118c77c5d Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sat, 17 Jan 2026 18:34:58 -0300 Subject: [PATCH 09/35] :wrench: chore(control-center/quick-actions): run hyprlock with config parameter --- src/window/control-center/widgets/QuickActions.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/window/control-center/widgets/QuickActions.tsx b/src/window/control-center/widgets/QuickActions.tsx index 869316ba..b5ef8ef7 100644 --- a/src/window/control-center/widgets/QuickActions.tsx +++ b/src/window/control-center/widgets/QuickActions.tsx @@ -7,6 +7,7 @@ import { createPoll } from "ags/time"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; +import { Shell } from "../../../app"; const userFace: Gio.File = Gio.File.new_for_path(`${GLib.get_home_dir()}/.face`); @@ -16,7 +17,7 @@ function LockButton(): Gtk.Button { return { Windows.getDefault().close("control-center"); - execApp("hyprlock"); + execApp(`hyprlock --config ${Shell.runtimeDir.peek_path()!}/config/hyprlock.conf`); }} /> as Gtk.Button; } From bc8e36b42738ee67941a1c6e59444d6cd953d543 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Tue, 20 Jan 2026 12:01:42 -0300 Subject: [PATCH 10/35] :sparkles: feat: implement input module, start clipboard daemon in it's own module --- src/app.ts | 2 + src/modules/clipboard.ts | 12 ++++++ src/modules/input.ts | 87 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/modules/input.ts diff --git a/src/app.ts b/src/app.ts index fb8f40fe..49c62ea7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -33,6 +33,7 @@ import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; import Adw from "gi://Adw?version=1"; import AstalWp from "gi://AstalWp"; +import { Input } from "./modules/input"; const runnerPlugins: Array = [ @@ -305,6 +306,7 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re this.#scope = getScope(); this.#connections.set(this, this.connect("shutdown", () => this.#scope.dispose())); + Input.getDefault(); NightLight.getDefault(); Media.getDefault(); Clipboard.getDefault(); diff --git a/src/modules/clipboard.ts b/src/modules/clipboard.ts index bd292b21..1d19aa7a 100644 --- a/src/modules/clipboard.ts +++ b/src/modules/clipboard.ts @@ -44,6 +44,7 @@ class Clipboard extends GObject.Object { #history = new Array; #changesTimeout: (AstalIO.Time|undefined); #ignoreChanges: boolean = false; + #procs: Array = []; @signal(GObject.TYPE_JSOBJECT) copied(_item: object) {} @signal() wiped() {}; @@ -55,6 +56,17 @@ class Clipboard extends GObject.Object { constructor() { super(); + this.#procs = [ + Gio.Subprocess.new( + ["wl-paste", "--type", "text", "--watch", "cliphist", "store"], + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + ), + Gio.Subprocess.new( + ["wl-paste", "--type", "image", "--watch", "cliphist", "store"], + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + ) + ]; + this.#dbFile = this.getCliphistDatabase(); this.#dbMonitor = monitorFile(this.#dbFile.get_path()!, () => { diff --git a/src/modules/input.ts b/src/modules/input.ts new file mode 100644 index 00000000..ec5f3fbd --- /dev/null +++ b/src/modules/input.ts @@ -0,0 +1,87 @@ +import Gio from "gi://Gio?version=2.0"; +import GLib from "gi://GLib?version=2.0"; +import IBus from "gi://IBus?version=1.0" +import { Notifications } from "./notifications"; +import { exec } from "ags/process"; + + +export class Input { + private static instance: Input; + + #proc: Gio.Subprocess|null = null; + + /** how many times IBus has crashed */ + protected ibusAttempts: number = 0; + /** if IBus crashes this much, this instance won't attempt to restart it again */ + public maxIbusAttempts: number = 5; + + + constructor() { + IBus.init(); + this.restartIBus(); + } + + /** @param keep whether to restart ibus after a crash (restart attempts are limited in {@link maxIbusAttempts}) */ + public restartIBus(keep: boolean = true): void { + this.#proc = Gio.Subprocess.new( + ["ibus-daemon", "--xim", "--replace", "--desktop", GLib.getenv("XDG_CURRENT_DESKTOP")?.toLowerCase()!, "--restart" ], + Gio.SubprocessFlags.STDERR_PIPE | Gio.SubprocessFlags.STDOUT_PIPE + ); + + this.#proc.wait_async(null, (self, res) => { + try { + self!.wait_finish(res); + } catch(e) { + if(this.ibusAttempts === this.maxIbusAttempts) { + Notifications.getDefault().sendNotification({ + appName: "colorshell", + summary: "Failed to restore IBus", + body: `Attempted to restart IBus, but it crashed in all the ${this.maxIbusAttempts + } tries, try checking for a config error or a dependency in fault.` + }); + + return; + } + + Notifications.getDefault().sendNotification({ + appName: "colorshell", + summary: "IBus crashed", + body: `An error occurred and IBus had to exit: ${(e as Error).message + }${keep ? ". The daemon will be automatically restarted" : ""}` + }); + + keep && this.restartIBus(); + } + }); + } + + public exitIBus(): void { + try { + exec("ibus exit"); + } catch(e) { + console.error("Input: IBus: Failed to exit the IBus Daemon:", e); + } + } + + public async exitIBusAsync(): Promise { + return new Promise((resolve, reject) => { + GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + try { + this.exitIBus(); + resolve(); + } catch(e) { + reject(e); + } + + return false; + }); + }); + } + + public static getDefault(): Input { + if(!this.instance) + this.instance = new Input(); + + return this.instance; + } +} From e8cb3bf069ab2ddfa536c920e8dc48d24579d292 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 22 Jan 2026 22:28:43 -0300 Subject: [PATCH 11/35] :wrench: chore(modules/input): use fcitx5 as the ime --- resources.gresource.xml | 1 + resources/config/hyprland/.last-updated | 2 +- resources/config/hyprland/environment.conf | 6 ++ resources/styles/_bar.scss | 6 ++ src/app.ts | 5 +- src/modules/idle.ts | 1 + src/modules/input.ts | 87 +++++++++++----------- 7 files changed, 62 insertions(+), 46 deletions(-) create mode 100644 src/modules/idle.ts diff --git a/resources.gresource.xml b/resources.gresource.xml index 6a70ab8e..b84c86b6 100644 --- a/resources.gresource.xml +++ b/resources.gresource.xml @@ -37,6 +37,7 @@ config/hyprland/.last-updated config/hyprland/environment.conf + config/hyprland/autostart.conf config/hyprland/vars.conf config/hyprland/decorations.conf config/hyprland/bindings.conf diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index b7b7001d..21c2a46d 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -january 15, 12:28 +january 22, 16:54 diff --git a/resources/config/hyprland/environment.conf b/resources/config/hyprland/environment.conf index 4ea94583..55b4f3b1 100644 --- a/resources/config/hyprland/environment.conf +++ b/resources/config/hyprland/environment.conf @@ -5,3 +5,9 @@ env = XDG_CONFIG_HOME, $HOME/.config env = XDG_CACHE_HOME, $HOME/.cache env = XDG_DATA_HOME, $HOME/.local/share env = XDG_STATE_HOME, $HOME/.local/state + +# Input methods +env = GTK_IM_MODULE, fcitx5 +env = QT_IM_MODULE, fcitx5 +env = SDL_IM_MODULE, fcitx5 +env = XMODIFIERS, @im=fcitx5 diff --git a/resources/styles/_bar.scss b/resources/styles/_bar.scss index 2a7461c0..c1bca924 100644 --- a/resources/styles/_bar.scss +++ b/resources/styles/_bar.scss @@ -92,7 +92,9 @@ $color-hover: colors.$bg-primary; } .focused-client { + margin: 3px 0; padding: 0 6px; + border-radius: 16px; & image { margin-right: 6px; @@ -114,6 +116,10 @@ $color-hover: colors.$bg-primary; margin-top: -2px; } } + + &:hover { + background: rgba(colors.$fg-primary, .3); + } } .clock.open { diff --git a/src/app.ts b/src/app.ts index 49c62ea7..6eaa6a08 100644 --- a/src/app.ts +++ b/src/app.ts @@ -26,6 +26,7 @@ import { exec } from "ags/process"; import { NightLight } from "./modules/nightlight"; import { Backlights } from "./modules/backlight"; import { initCompositor } from "./compositors"; +import { Input } from "./modules/input"; import GObject, { register } from "ags/gobject"; import Media from "./modules/media"; @@ -33,7 +34,6 @@ import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; import Adw from "gi://Adw?version=1"; import AstalWp from "gi://AstalWp"; -import { Input } from "./modules/input"; const runnerPlugins: Array = [ @@ -48,7 +48,7 @@ const runnerPlugins: Array = [ const defaultWindows: Array = [ "bar" ]; -GLib.unsetenv("LD_PRELOAD"); // so child processes won't run with gtk-layer-shell +GLib.unsetenv("LD_PRELOAD"); // so child processes won't use gtk-layer-shell by default @register({ GTypeName: "Shell" }) @@ -184,6 +184,7 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re private init(): void { console.log(`Colorshell: Initializing things`); + !Shell.runtimeDir.query_exists(null) && Shell.runtimeDir.make_directory_with_parents(null); diff --git a/src/modules/idle.ts b/src/modules/idle.ts new file mode 100644 index 00000000..9ffd3b67 --- /dev/null +++ b/src/modules/idle.ts @@ -0,0 +1 @@ +// TODO: configurable idle daemon diff --git a/src/modules/input.ts b/src/modules/input.ts index ec5f3fbd..c859ee68 100644 --- a/src/modules/input.ts +++ b/src/modules/input.ts @@ -1,8 +1,6 @@ +import { exec } from "ags/process"; import Gio from "gi://Gio?version=2.0"; -import GLib from "gi://GLib?version=2.0"; -import IBus from "gi://IBus?version=1.0" import { Notifications } from "./notifications"; -import { exec } from "ags/process"; export class Input { @@ -10,33 +8,42 @@ export class Input { #proc: Gio.Subprocess|null = null; - /** how many times IBus has crashed */ - protected ibusAttempts: number = 0; - /** if IBus crashes this much, this instance won't attempt to restart it again */ - public maxIbusAttempts: number = 5; + /** how many times the IME has crashed */ + protected attempts: number = 0; + /** if the IME crashes this much, this instance won't attempt to start it again */ + public maxAttempts: number = 5; constructor() { - IBus.init(); - this.restartIBus(); + this.restart(); + } + + public static getDefault(): Input { + if(!this.instance) + this.instance = new Input(); + + return this.instance; } - /** @param keep whether to restart ibus after a crash (restart attempts are limited in {@link maxIbusAttempts}) */ - public restartIBus(keep: boolean = true): void { + /** @param keep whether to restart the IME daemon after a crash/exit (restart attempts are limited by {@link maxIbusAttempts}) */ + public restart(keep: boolean = true): void { + if(this.#proc) + return; + this.#proc = Gio.Subprocess.new( - ["ibus-daemon", "--xim", "--replace", "--desktop", GLib.getenv("XDG_CURRENT_DESKTOP")?.toLowerCase()!, "--restart" ], - Gio.SubprocessFlags.STDERR_PIPE | Gio.SubprocessFlags.STDOUT_PIPE + ["fcitx5", "-r", "-s", "2"], + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE ); - this.#proc.wait_async(null, (self, res) => { + this.#proc.wait_async(null, (_, res) => { try { - self!.wait_finish(res); + this.#proc!.wait_finish(res); } catch(e) { - if(this.ibusAttempts === this.maxIbusAttempts) { + if(this.attempts === this.maxAttempts) { Notifications.getDefault().sendNotification({ appName: "colorshell", - summary: "Failed to restore IBus", - body: `Attempted to restart IBus, but it crashed in all the ${this.maxIbusAttempts + summary: "Failed to restore IME", + body: `Attempted to restart the IME Daemon, but it crashed in all the ${this.maxAttempts } tries, try checking for a config error or a dependency in fault.` }); @@ -45,43 +52,37 @@ export class Input { Notifications.getDefault().sendNotification({ appName: "colorshell", - summary: "IBus crashed", - body: `An error occurred and IBus had to exit: ${(e as Error).message + summary: "IME crashed", + body: `An error occurred and the Daemon had to exit: ${(e as Error).message }${keep ? ". The daemon will be automatically restarted" : ""}` }); - keep && this.restartIBus(); + keep && this.restart(keep); } + + console.log("Input: Fcitx5: Exited normally"); }); } - public exitIBus(): void { + /** force the IME daemon to quit */ + public exit(): void { try { - exec("ibus exit"); + exec("fcitx5-remote --check -e"); } catch(e) { - console.error("Input: IBus: Failed to exit the IBus Daemon:", e); + // so we throw a prettier error + throw new Error("Input: Fcitx5: Failed to quit the daemon. Is it running?"); } } - public async exitIBusAsync(): Promise { - return new Promise((resolve, reject) => { - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - try { - this.exitIBus(); - resolve(); - } catch(e) { - reject(e); - } - - return false; - }); - }); - } - - public static getDefault(): Input { - if(!this.instance) - this.instance = new Input(); + protected getDaemonPid(): number|null { + try { + const str = exec("pgrep '^fcitx5$' | head -n1")?.trim(); + if(str.trim() === "") + return null; - return this.instance; + return Number.parseInt(str.trim()); + } catch(e) { + return null; + } } } From 39a1222d558b7c3bd483b11605ac6e05e15189a9 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 28 Jan 2026 18:26:06 -0300 Subject: [PATCH 12/35] :wrench: chore(modules, modules/compositors): add screenshot module, add `:size` prop to `CompositorClient` colorshell screenshot is now independent of hyprshot, it uses grim and slurp directly instead; you can now also take a screenshot of the active(focused) window with ALT + PrintScrn --- src/modules/arg-handler.ts | 48 +++----- src/modules/compositors/hyprland.ts | 3 +- src/modules/compositors/index.ts | 9 ++ src/modules/reload-handler.ts | 11 +- src/modules/screenshot.ts | 177 ++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 41 deletions(-) create mode 100644 src/modules/screenshot.ts diff --git a/src/modules/arg-handler.ts b/src/modules/arg-handler.ts index 84e0666b..f5407aa6 100644 --- a/src/modules/arg-handler.ts +++ b/src/modules/arg-handler.ts @@ -2,20 +2,18 @@ import { Gtk } from "ags/gtk4"; import { Wireplumber } from "./volume"; import { Windows } from "../windows"; import { restartInstance } from "./reload-handler"; -import { timeout } from "ags/time"; import { Runner } from "../runner/Runner"; import { showWorkspaceNumber } from "../window/bar/widgets/Workspaces"; import { playSystemBell } from "./utils"; import { Shell } from "../app"; +import { Screenshot } from "./screenshot"; import { generalConfig } from "../config"; import { execApp } from "./apps"; +import { exec } from "ags/process"; import Media from "./media"; -import AstalIO from "gi://AstalIO"; import AstalMpris from "gi://AstalMpris"; -import { exec, execAsync } from "ags/process"; import GLib from "gi://GLib?version=2.0"; -import { Notifications } from "./notifications"; export type RemoteCaller = { @@ -23,7 +21,7 @@ export type RemoteCaller = { print_literal: (message: string) => void }; -let wsTimeout: AstalIO.Time|undefined; +let wsPeekTimeout: GLib.Source|undefined; const help = `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, \ made using GTK4, AGS, Gnim and Astal libraries by Aylur. @@ -115,16 +113,16 @@ export function handleArguments(cmd: RemoteCaller, args: Array): number return 0; case "peek-workspace-num": - if(wsTimeout) { + if(wsPeekTimeout) { cmd.print_literal("Workspace numbers are already showing"); return 0; } showWorkspaceNumber(true); - wsTimeout = timeout(Number.parseInt(args[1]) || 2200, () => { + wsPeekTimeout = setTimeout(() => { showWorkspaceNumber(false); - wsTimeout = undefined; - }); + wsPeekTimeout = undefined; + }, Number.parseInt(args[1]) || 2200); cmd.print_literal("Toggled workspace numbers"); return 0; @@ -135,34 +133,22 @@ export function handleArguments(cmd: RemoteCaller, args: Array): number return 0; case "screenshot": - const outputDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) - ?? `${GLib.get_home_dir()}/Pictures/Screenshots`; - try { exec("killall slurp"); // kill any active selection layer } catch(_) {} - if(args[1] !== undefined && /^f(ull)?$/.test(args[1])) { - execAsync(`hyprshot -m active -m output -o ${outputDir}`).catch(e => - Notifications.getDefault().sendNotification({ - appName: "hyprshot", - summary: "Failed to take screenshot", - body: (e as Error)?.message - }) - ); - return 0; + if(args[1] !== undefined) { + if(/^f(ull)?$/.test(args[1])) { + Screenshot.getDefault().take(Screenshot.Mode.FULL).catch(e => console.error(e)); + return 0; + } + if(/^a(ctive)?$/.test(args[1])) { + Screenshot.getDefault().take(Screenshot.Mode.ACTIVE_WINDOW).catch(e => console.error(e)); + return 0; + } } - execAsync(`hyprshot -m region -o ${outputDir}`).catch(e => { - if((e as Error).message.toLowerCase().split('\n')[0] === "selection cancelled") - return; - - Notifications.getDefault().sendNotification({ - appName: "hyprshot", - summary: "Failed to take screenshot", - body: (e as Error)?.message - }) - }); + Screenshot.getDefault().take().catch(e => console.error(e)); // take screenshot in default mode return 0; } diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 9c185c11..0a0a050c 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -77,7 +77,8 @@ export class CompositorHyprland extends Compositor { class: focusedClient.class ?? "", initialClass: focusedClient.initialClass ?? "", mapped: focusedClient.mapped, - position: [focusedClient.at[0], focusedClient.at[1]], + position: focusedClient.at, + size: focusedClient.size, title: focusedClient.title ?? "" }); diff --git a/src/modules/compositors/index.ts b/src/modules/compositors/index.ts index bdb75eea..3770daf2 100644 --- a/src/modules/compositors/index.ts +++ b/src/modules/compositors/index.ts @@ -121,6 +121,7 @@ export namespace Compositor { #title: string = ""; #mapped: boolean = true; #position: [number, number] = [0, 0]; + #size: [number, number] = [1, 1]; #xwayland: boolean = false; @getter(gtype(String)) @@ -144,6 +145,9 @@ export namespace Compositor { @getter(Boolean) get mapped() { return this.#mapped; } + @getter(Array) + get size() { return this.#size; } + constructor(props: { address?: string; title?: string; @@ -152,6 +156,8 @@ export namespace Compositor { initialClass?: string; /** [x, y] */ position?: [number, number]; + /** [width, height] */ + size?: [number, number]; }) { super(); @@ -169,6 +175,9 @@ export namespace Compositor { if(props.position !== undefined) this.#position = props.position; + if(props.size !== undefined) + this.#size = props.size; + this.#initialClass = props.initialClass !== undefined ? props.initialClass : props.class; diff --git a/src/modules/reload-handler.ts b/src/modules/reload-handler.ts index 18161f25..7f686f17 100644 --- a/src/modules/reload-handler.ts +++ b/src/modules/reload-handler.ts @@ -1,15 +1,8 @@ -import { uwsmIsActive } from "./apps"; - -import Gio from "gi://Gio?version=2.0"; +import { execApp } from "./apps"; import { Shell } from "../app"; export function restartInstance(): void { - Gio.Subprocess.new( - ( uwsmIsActive ? - [ "uwsm", "app", "--", "colorshell" ] - : [ "colorshell" ]), - Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE - ); + execApp("colorshell"); Shell.getDefault().quit(); } diff --git a/src/modules/screenshot.ts b/src/modules/screenshot.ts new file mode 100644 index 00000000..2501246f --- /dev/null +++ b/src/modules/screenshot.ts @@ -0,0 +1,177 @@ +import { gtype, property, register, signal } from "ags/gobject"; +import { execAsync } from "ags/process"; +import { Compositor } from "./compositors"; +import GObject from "gi://GObject?version=2.0"; +import Gio from "gi://Gio?version=2.0"; +import GLib from "gi://GLib?version=2.0"; + + +/** screen-shotting tool for colorshell, based on grim and slurp */ +@register({ GTypeName: "ClshScreenshotTool" }) +export class Screenshot extends GObject.Object { + private static instance: Screenshot; + declare $signals: Screenshot.SignalSignatures; + + @signal(String) + tookScreenshot(_: string) {} + + @property(gtype(Number)) + mode: Screenshot.Mode = Screenshot.Mode.SELECT; + + @property(Boolean) + includeCursors: boolean = false; + + @property(Gio.File) + outputDir: Gio.File = Gio.File.new_for_path(`${ + GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) ?? + `${GLib.get_home_dir()}/Pictures` + }/Screenshots`); + + + public static getDefault(): Screenshot { + if(!this.instance) + this.instance = new Screenshot(); + + return this.instance; + } + + /** takes a screenshot using grim+slurp with the specified geometry and mode. + * @returns the screenshot image output path; or null if the screenshot was cancelled\ + * (only possible in SELECT mode) */ + async take(mode: Screenshot.Mode = this.mode, area?: Screenshot.Area): Promise { + const output = `${this.outputDir.peek_path()!}/${this.genFileName()}_screenshot`; + + if(mode === Screenshot.Mode.SELECT) { + const selection = area ?? await this.select(); + + if(selection === null) + return null; + + try { + await this.grim(output, selection); + } catch(e) { + console.error(e); + return null; + } + + return output; + } + + if(mode === Screenshot.Mode.ACTIVE_WINDOW) { + const activeClient = Compositor.getDefault().focusedClient; + + if(activeClient) { + const clientArea = new Screenshot.Area({ + x: activeClient.position[0], + y: activeClient.position[1], + width: activeClient.size[0], + height: activeClient.size[1] + }); + + // TODO: implement Workspace access via Client object to change to its workspace if not currently in it + + try { + await this.grim(output, clientArea); + } catch(e) { + console.error(e); + return null; + } + return output; + } + } + + try { + await this.grim(output); + return output; + } catch(e) { + console.error(e); + } + + return null; + } + + /** creates a new area selection layer for the user to interact with. + * + * @returns a `Screenshot.Area` object, containing the selected area geometry; or \ + * null if the user cancelled the selection */ + async select(): Promise { + let region!: string; + try { + region = await execAsync("slurp"); + } catch(e) { + return null; // if the user cancelled the selection + } + + return this.slurpToArea(region); + } + + protected async grim(output: string, area?: Screenshot.Area): Promise { + try { + await execAsync(`grim ${area ? `-g "${this.areaToSlurp(area)}"` : ""} ${output}`); + } catch(e) { + throw new Error(`Screenshot: Failed to take a screenshot. Stderr: ${(e as Error).message}`); + } + } + + /** generates a screenshot file name based in the current local time */ + protected genFileName(): string { + return GLib.DateTime.new_now_local().format("%Y-%m-%d-%H-%M-%S")!; + } + + /** translates a slurp geometry string to a `Screenshot.Area` object */ + protected slurpToArea(geometry: string): Screenshot.Area { + if(!/^(\d)+,(\d)+ (\d)+x(\d)+$/.test(geometry)) + throw new Error("Screenshot: the provided slurp geometry is invalid and could not be translated"); + + const [pos, size] = geometry.split(' '); + const [x, y] = pos.split(',').map(s => Number.parseInt(s)), + [width, height] = size.split('x').map(s => Number.parseInt(s)); + + return new Screenshot.Area({ x, y, width, height }); + } + + /** translates a `Screenshot.Area` object to a valid slurp geometry */ + protected areaToSlurp(area: Screenshot.Area): string { + return `${area.x},${area.y} ${area.width}x${area.height}`; + } +} + +export namespace Screenshot { + export enum Mode { + /** open a selection layer to select the screenshot area */ + SELECT = 0, + /** full screenshot */ + FULL = 1, + /** take a screenshot of the active client(window); if there's none, fallback to FULL */ + ACTIVE_WINDOW = 2 + } + + export class Area { + public readonly x: number; + public readonly y: number; + public readonly width: number; + public readonly height: number; + + constructor(props: { + x: number, + y: number, + width: number, + height: number + }) { + this.x = props.x; + this.y = props.y; + this.width = props.width; + this.height = props.height; + } + } + + export interface SignalSignatures extends GObject.Object.SignalSignatures { + /** triggered when a screenshot is finished. + * @param mode the screenshot area mode + * @param path the output file of the screenshot + * */ + "took-screenshot": (mode: Screenshot.Mode, path: string) => void; + "notify::mode": () => void; + "notify::include-cursors": () => void; + } +} From 046d4039d2b31d14e76e4e89be8b20fadb7cda20 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 28 Jan 2026 18:26:49 -0300 Subject: [PATCH 13/35] :wrench: chore(config/hyprland): add ALT + PrintScrn bind to take active client screenshot --- resources/config/hyprland/.last-updated | 2 +- resources/config/hyprland/bindings.conf | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index 21c2a46d..1e3f2de7 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -january 22, 16:54 +january 28, 18:10 diff --git a/resources/config/hyprland/bindings.conf b/resources/config/hyprland/bindings.conf index c04b8295..5cee3531 100644 --- a/resources/config/hyprland/bindings.conf +++ b/resources/config/hyprland/bindings.conf @@ -3,10 +3,11 @@ bind = SUPER, SPACE, exec, colorshell runner bind = , Print, exec, colorshell screenshot +bind = ALT, Print, exec, colorshell screenshot active bind = SUPER, Print, exec, colorshell screenshot full # Restart colorshell -bind = SUPER, F7, exec, timeout 0.6s colorshell quit && $exec colorshell.desktop || kill $(cat $XDG_RUNTIME_DIR/colorshell/.pid) ; $exec colorshell +bind = SUPER, F7, exec, colorshell reload bind = SUPER, N, exec, colorshell toggle control-center bind = SUPER, M, exec, colorshell toggle center-window From 56a4f8e3a1f3a5d549356ed0cc81282ffc2bf580 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 29 Jan 2026 17:34:15 -0300 Subject: [PATCH 14/35] :boom: fix(modules/screenshot): actually copy to clipboard, add file extension to name also added docs for the `screenshot active` command --- src/modules/arg-handler.ts | 2 +- src/modules/screenshot.ts | 37 ++++++++++++++++++- .../control-center/widgets/QuickActions.tsx | 4 +- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/modules/arg-handler.ts b/src/modules/arg-handler.ts index f5407aa6..0cf86f07 100644 --- a/src/modules/arg-handler.ts +++ b/src/modules/arg-handler.ts @@ -47,7 +47,7 @@ Others: runner [initial_text]: open the application runner, optionally add an initial search. run app[.desktop] [client_modifiers]: run applications from the cli, see "run help". lock: quick-lock your user with hyprlock. - screenshot [full]: select an area to screenshot(optionally add "full" to take a complete screenshot). + screenshot [full|active]: select an area to screenshot(add "full" to take a full screenshot or "active" to take from the active client). peek-workspace-num [millis]: peek the workspace numbers on bar window. v, version: display current colorshell version. h, help: shows this help message. diff --git a/src/modules/screenshot.ts b/src/modules/screenshot.ts index 2501246f..4e4eb99c 100644 --- a/src/modules/screenshot.ts +++ b/src/modules/screenshot.ts @@ -1,6 +1,7 @@ import { gtype, property, register, signal } from "ags/gobject"; import { execAsync } from "ags/process"; import { Compositor } from "./compositors"; +import { Notifications } from "./notifications"; import GObject from "gi://GObject?version=2.0"; import Gio from "gi://Gio?version=2.0"; import GLib from "gi://GLib?version=2.0"; @@ -15,12 +16,18 @@ export class Screenshot extends GObject.Object { @signal(String) tookScreenshot(_: string) {} + /** default screenshot mode to fallback to if none is specified */ @property(gtype(Number)) mode: Screenshot.Mode = Screenshot.Mode.SELECT; + /** include mouse cursors in the screenshot */ @property(Boolean) includeCursors: boolean = false; + /** whether to copy the screenshot image to clipboard */ + @property(Boolean) + copyToClipboard: boolean = true; + @property(Gio.File) outputDir: Gio.File = Gio.File.new_for_path(`${ GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) ?? @@ -39,7 +46,7 @@ export class Screenshot extends GObject.Object { * @returns the screenshot image output path; or null if the screenshot was cancelled\ * (only possible in SELECT mode) */ async take(mode: Screenshot.Mode = this.mode, area?: Screenshot.Area): Promise { - const output = `${this.outputDir.peek_path()!}/${this.genFileName()}_screenshot`; + const output = `${this.outputDir.peek_path()!}/${this.genFileName()}_screenshot.png`; if(mode === Screenshot.Mode.SELECT) { const selection = area ?? await this.select(); @@ -107,7 +114,33 @@ export class Screenshot extends GObject.Object { protected async grim(output: string, area?: Screenshot.Area): Promise { try { - await execAsync(`grim ${area ? `-g "${this.areaToSlurp(area)}"` : ""} ${output}`); + await execAsync(`grim ${ + area ? `-g "${this.areaToSlurp(area)}"` : "" + } ${this.includeCursors ? "-c" : ""} ${output}`); + execAsync(`sh -c "cat '${output}' | wl-copy"`).catch(e => + Notifications.getDefault().sendNotification({ + appName: "colorshell", + summary: "Failed to copy Screenshot", + body: `The recent screenshot you took couldn't be automatically copied because of an error: ${(e as Gio.IOErrorEnum).message}` + }) + ); + Notifications.getDefault().sendNotification({ + appName: "colorshell", + summary: "Screenshot", + body: `The screenshot was saved as ${output}`, + image: output, + actions: [{ + id: "view", + text: "View", + onAction: () => execAsync(`xdg-open "${output}"`).catch(e => + Notifications.getDefault().sendNotification({ + appName: "colorshell", + summary: "Failed to open screenshot", + body: `Failed to open image with xdg-open: ${(e as Error).message}`, + }) + ) + }] + }); } catch(e) { throw new Error(`Screenshot: Failed to take a screenshot. Stderr: ${(e as Error).message}`); } diff --git a/src/window/control-center/widgets/QuickActions.tsx b/src/window/control-center/widgets/QuickActions.tsx index b5ef8ef7..f846f8af 100644 --- a/src/window/control-center/widgets/QuickActions.tsx +++ b/src/window/control-center/widgets/QuickActions.tsx @@ -8,6 +8,7 @@ import { createPoll } from "ags/time"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; import { Shell } from "../../../app"; +import { Screenshot } from "../../../modules/screenshot"; const userFace: Gio.File = Gio.File.new_for_path(`${GLib.get_home_dir()}/.face`); @@ -35,7 +36,8 @@ function ScreenshotButton(): Gtk.Button { return { Windows.getDefault().close("control-center"); - execApp(`sh ${GLib.get_user_config_dir()}/hypr/scripts/screenshot.sh`); + setTimeout(() => Screenshot.getDefault().select(), 1000); + }} /> as Gtk.Button; } From 088646f62f8bacd2591b7bb131cddc0936553881 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 29 Jan 2026 17:38:50 -0300 Subject: [PATCH 15/35] :boom: fix(control-center/quick-actions): take screenshot instead of only opening a selection layer --- src/window/control-center/widgets/QuickActions.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/window/control-center/widgets/QuickActions.tsx b/src/window/control-center/widgets/QuickActions.tsx index f846f8af..e1a7b888 100644 --- a/src/window/control-center/widgets/QuickActions.tsx +++ b/src/window/control-center/widgets/QuickActions.tsx @@ -4,11 +4,10 @@ import { Wallpaper } from "../../../modules/wallpaper"; import { execApp } from "../../../modules/apps"; import { Accessor } from "ags"; import { createPoll } from "ags/time"; - -import GLib from "gi://GLib?version=2.0"; -import Gio from "gi://Gio?version=2.0"; import { Shell } from "../../../app"; import { Screenshot } from "../../../modules/screenshot"; +import GLib from "gi://GLib?version=2.0"; +import Gio from "gi://Gio?version=2.0"; const userFace: Gio.File = Gio.File.new_for_path(`${GLib.get_home_dir()}/.face`); @@ -36,7 +35,7 @@ function ScreenshotButton(): Gtk.Button { return { Windows.getDefault().close("control-center"); - setTimeout(() => Screenshot.getDefault().select(), 1000); + setTimeout(() => Screenshot.getDefault().take(), 1000); }} /> as Gtk.Button; From f95a93ffb3a9b71b8ad8992d55d8a9f87894c0c1 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Tue, 3 Feb 2026 10:27:02 -0300 Subject: [PATCH 16/35] :sparkles: feat(compositors/hyprland): automatically reapply config on reload --- src/modules/compositors/hyprland.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 0a0a050c..82030ed6 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -91,8 +91,13 @@ export class CompositorHyprland extends Compositor { break; case "configreloaded": - if(!this.#ignoreConfigReload) + if(!this.#ignoreConfigReload) { + this.#ignoreConfigReload = true; this.source(); + return; + } + + this.#ignoreConfigReload &&= false; break; case "beep": @@ -107,12 +112,6 @@ export class CompositorHyprland extends Compositor { private source(): void { - if(this.#ignoreConfigReload) { - this.#ignoreConfigReload = false; - return; - } - - this.#ignoreConfigReload = true; const names = Gio.resources_enumerate_children( "/io/github/retrozinndev/colorshell/config/hyprland", Gio.ResourceLookupFlags.NONE From 9561dede6b7c9db1fe838c75937351f07becb12c Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sat, 7 Feb 2026 20:24:57 -0300 Subject: [PATCH 17/35] :wastebasket: chore: remove unused config sync script --- install.sh | 4 +-- scripts/sync-config.sh | 60 ------------------------------------------ scripts/utils.sh | 2 +- 3 files changed, 3 insertions(+), 63 deletions(-) delete mode 100644 scripts/sync-config.sh diff --git a/install.sh b/install.sh index fde83003..4e72f463 100755 --- a/install.sh +++ b/install.sh @@ -56,7 +56,7 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then rm -rf $repo_directory 2> /dev/null Send_log "Cloning repository in \`$repo_directory\`..." if [[ -d $repo_directory/.git ]]; then - Send_log "repo is already cloned! let's just fetch the latest changes..." + Send_log "Repo is already cloned! let's just fetch the latest changes..." git -C "$repo_directory" stash # if there are changes, let's just stash them git -C "$repo_directory" checkout ryo git -C "$repo_directory" fetch && git -C "$repo_directory" pull --rebase # rebase just in case @@ -68,7 +68,7 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then Ask "Nice! Do you want to use the stable version instead of the unstable(latest commit)?" if [[ -z "$skip_prompts" ]] && [[ "$answer" == y ]]; then - Send_log "fetching latest release from colorshell repository" + Send_log "Fetching latest release from colorshell repository" latest_tag=`curl -s "$repo_api_url/releases" | jq -r '. | select(.[].prerelease == false) | .[0].tag_name'` Send_log "Done fetching" diff --git a/scripts/sync-config.sh b/scripts/sync-config.sh deleted file mode 100644 index e4ba8a72..00000000 --- a/scripts/sync-config.sh +++ /dev/null @@ -1,60 +0,0 @@ -source ./scripts/utils.sh - -config_dirs=( - "hypr/scripts" - "hypr/shell" - "hypr/hyprlock.conf" - "hypr/hyprland.conf" - "hypr/hypridle.conf" - "kitty/kitty.conf" -) -outdir="./config" - -Clean_local() { - Send_log "info" "Cleaning local config..." - for dir in ${config_dirs[@]}; do - rm -rf $outdir/$dir - done -} - -Update_local() { - mkdir -p $outdir - for dir in ${config_dirs[@]}; do - if [[ -d "$XDG_CONFIG_HOME/$dir" ]] || [[ -f "$XDG_CONFIG_HOME/$dir" ]]; then - Send_log "Copying ${dir^}" - mkdir -p `dirname "$outdir/$dir"` - cp -r $XDG_CONFIG_HOME/$dir $outdir/$dir - else - Send_log "warn" "Looks like the ${dir^} dir is in fault! Skipping..." - fi - done -} - -Print_header - -printf "\n" -echo "!!WARNING!! Running this script may override all configuration data in current repo with host ones." -echo "This script is intended to be used only by the repository owner" -printf "\n" - -echo "Please run this script in it's current directory to avoid issues" -echo "Tip: Press [Ctrl] + [C] to stop script at any time" - -printf "\n" - -Ask "Update local repository with host configurations?" -if [[ ! $answer == y ]]; then - Send_log "Exiting" - exit 1 -fi - -printf "\n" - -Clean_local -Update_local - -if command -v git > /dev/null; then - git status -fi - -exit 0 diff --git a/scripts/utils.sh b/scripts/utils.sh index 834d08e1..0e152ea9 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -56,7 +56,7 @@ function Print_header() { function Ask() { read -n 1 -p "$@ [y/n] " answer printf '\n' - if [[ ! $answer =~ [yn] ]]; then + if [[ ! $answer =~ [ynYN] ]]; then Ask "$@" # restart if different from accepted chars fi From 12547cb48c16f068d0f0856b7af0a6270a09623d Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 09:55:46 -0300 Subject: [PATCH 18/35] :wrench: chore(scripts/socket, compositors/hyprland): return socat's exit code, disable compositor event debugging --- scripts/socket.sh | 2 +- src/modules/compositors/hyprland.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/socket.sh b/scripts/socket.sh index 227334b9..d9c4138d 100644 --- a/scripts/socket.sh +++ b/scripts/socket.sh @@ -6,7 +6,7 @@ if gdbus introspect --session \ if command -v socat > /dev/null 2>&1; then echo "$@" | socat - "${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}/colorshell/.sock" - exit 0 + exit ${?:-"0"} else echo "[warn] \`socat\` not installed, falling back to remote instance communication" fi diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 82030ed6..706ab45c 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -59,13 +59,12 @@ export class CompositorHyprland extends Compositor { info = undefined; } - //console.log(event, info); // debugging + //console.log(`${event}:`, info); // debugging this.handleEvents(event, data); }); } private handleEvents(event: CompositorHyprland.Event, data: string): void { - console.log(event); switch(event as CompositorHyprland.Event) { case "activewindowv2": const address = data; From 8f6b7e2dea3795fa2bab6d26bc85756473142d00 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 10:26:23 -0300 Subject: [PATCH 19/35] :wrench: chore(config/hyprland, resources): remove unnecessary autostart config, unbind colorshell binds colorshell now unbinds shell-exclusive key combinations before adding them, so it avoids duplicate actions if the user had set to something else previously --- resources.gresource.xml | 1 - resources/config/hyprland/.last-updated | 2 +- resources/config/hyprland/autostart.conf | 11 ----------- resources/config/hyprland/bindings.conf | 5 +++++ 4 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 resources/config/hyprland/autostart.conf diff --git a/resources.gresource.xml b/resources.gresource.xml index b84c86b6..6a70ab8e 100644 --- a/resources.gresource.xml +++ b/resources.gresource.xml @@ -37,7 +37,6 @@ config/hyprland/.last-updated config/hyprland/environment.conf - config/hyprland/autostart.conf config/hyprland/vars.conf config/hyprland/decorations.conf config/hyprland/bindings.conf diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index 1e3f2de7..5913a33b 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -january 28, 18:10 +february 09, 10:20 diff --git a/resources/config/hyprland/autostart.conf b/resources/config/hyprland/autostart.conf deleted file mode 100644 index c5b0471b..00000000 --- a/resources/config/hyprland/autostart.conf +++ /dev/null @@ -1,11 +0,0 @@ - -# colorshell configuration, please don't modify unless you know what you're doing! - -# Daemons -exec-once = $exec ibus start # use ibus as input method (fixes character compose not working in gtk apps) -exec-once = $exec wl-paste --type text --watch cliphist store -exec-once = $exec wl-paste --type image --watch cliphist store - -# Tools -exec-once = systemctl start --user --now hyprsunset -exec-once = systemctl start --user --now hyprpaper diff --git a/resources/config/hyprland/bindings.conf b/resources/config/hyprland/bindings.conf index 5cee3531..716f1171 100644 --- a/resources/config/hyprland/bindings.conf +++ b/resources/config/hyprland/bindings.conf @@ -9,10 +9,15 @@ bind = SUPER, Print, exec, colorshell screenshot full # Restart colorshell bind = SUPER, F7, exec, colorshell reload +unbind = SUPER, N bind = SUPER, N, exec, colorshell toggle control-center +unbind = SUPER, M bind = SUPER, M, exec, colorshell toggle center-window +unbind = SUPER, L bind = SUPER, L, exec, colorshell lock +unbind = SUPER, V bind = SUPER, V, exec, colorshell runner '>' +unbind = SUPER, W bind = SUPER, W, exec, colorshell runner '\##' bind = SUPER, SUPER_l, exec, colorshell peek-workspace-num From 2c1a5cefb8bb8f6efaf12ebc3d818590ed3e4782 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 21:19:19 -0300 Subject: [PATCH 20/35] :wrench: chore(modules/wallpaper, modules/utils): run hyprpaper as a child process to the shell, add process util functions --- src/modules/utils.ts | 36 +++++++++++++++++++++++++++ src/modules/wallpaper.ts | 53 ++++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/modules/utils.ts b/src/modules/utils.ts index a59b328b..76ce59a1 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -83,6 +83,42 @@ export function getChildren(widget: Gtk.Widget): Array { return children; } +/** search for a process by its name. (exact match) + * @returns the pid for the first search result, `undefined` if no process was found */ +export function getPID(search: string): number|undefined { + let result!: string; + + try { + result = exec(`pgrep -x "${search}"`).trim().replaceAll('\n', ''); + } catch(e) { + if((e as Gio.IOErrorEnum).code !== 1) + console.error("Couldn't search for process by name:", e); + + return undefined; + } + + console.log(result); + const pid = Number.parseInt(result); + + if(!isNaN(pid)) + return pid; + + return undefined; +} + +/** forces a process to quit with the desired signal. + * @param pid the process id + * @param signal process signal code. default: `2` */ +export function killProc(pid: number, signal: number = 2): boolean { + try { + exec(`kill -s ${signal} ${pid}`); + } catch(_) { + return false; + } + + return true; +} + export function omitObjectKeys(obj: ObjT, keys: keyof ObjT|Array): object { const finalObject = { ...obj }; diff --git a/src/modules/wallpaper.ts b/src/modules/wallpaper.ts index 7cdea08c..e88b6155 100644 --- a/src/modules/wallpaper.ts +++ b/src/modules/wallpaper.ts @@ -4,7 +4,7 @@ import GObject, { register, getter, gtype, property, setter } from "ags/gobject" import Gio from "gi://Gio?version=2.0"; import GLib from "gi://GLib?version=2.0"; -import { createSubscription, encoder } from "./utils"; +import { createSubscription, encoder, getPID, killProc } from "./utils"; import { Notifications } from "./notifications"; import { generalConfig } from "../config"; import { createRoot, getScope, Scope } from "ags"; @@ -54,6 +54,7 @@ export class Wallpaper extends GObject.Object { #splash: boolean = true; #hyprpaperFile: Gio.File; #wallpapersPath: string; + #proc: Gio.Subprocess|null = null; @getter(Boolean) public get splash() { return this.#splash; } @@ -88,7 +89,14 @@ export class Wallpaper extends GObject.Object { this.getWallpaper().then((wall) => { if(wall?.trim()) this.#wallpaper = wall.trim(); - }); + }); + + const pid = getPID("hyprpaper"); + console.log(pid); + if(pid != null) + killProc(pid); + + this.restartDaemon(); createRoot(() => { this.#scope = getScope(); @@ -164,13 +172,12 @@ export class Wallpaper extends GObject.Object { Notifications.getDefault().sendNotification({ appName: "colorshell", summary: "Wallpaper configuration", - body: "This setting will only take effect after a hyprpaper restart. If you're using systemd, \ -click the action to restart hyprpaper", + body: "This change will only take effect after a hyprpaper restart. Click the \"restart\" button to restart the wallpaper daemon", actions: [{ - text: "Restart service", - id: "restart-service", + text: "Restart", + id: "restart-daemon", onAction: () => { - execAsync("systemctl --user restart hyprpaper").catch((e) => { + this.restartDaemon().catch(e => { Notifications.getDefault().sendNotification({ appName: "colorshell", summary: "Failed to restart service", @@ -196,6 +203,38 @@ click the action to restart hyprpaper", return this.instance; } + + /** tries to kill the wallpaper daemon. + * @returns `true` on success, or else, `false` */ + public async quitDaemon(): Promise { + if(!this.#proc) + return false; + + return new Promise((resolve, reject) => { + // wait for it to close, so we can resolve() the Promise + this.#proc!.wait_async(null, (_, res) => { + let result!: boolean; + try { + result = this.#proc!.wait_finish(res); + } catch(e) { + reject(e); + return; + } + + resolve(result); + }); + + this.#proc!.force_exit(); + }); + } + + public async restartDaemon(): Promise { + if(this.#proc) + await this.quitDaemon(); + + this.#proc = Gio.Subprocess.new(["hyprpaper"], Gio.SubprocessFlags.NONE); + } + private writeChanges(): void { this.#hyprpaperFile.replace_contents_async(encoder.encode( `# This file was automatically generated by colorshell From 5f4a5923023b9bbd3b88b63d355d3d10c88407a5 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 21:23:18 -0300 Subject: [PATCH 21/35] :boom: fix(config/hyprland): do not set `GTK_IM_MODULE`, as fcitx5 recommends not setting it --- resources/config/hyprland/environment.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/config/hyprland/environment.conf b/resources/config/hyprland/environment.conf index 55b4f3b1..c4822475 100644 --- a/resources/config/hyprland/environment.conf +++ b/resources/config/hyprland/environment.conf @@ -7,7 +7,6 @@ env = XDG_DATA_HOME, $HOME/.local/share env = XDG_STATE_HOME, $HOME/.local/state # Input methods -env = GTK_IM_MODULE, fcitx5 env = QT_IM_MODULE, fcitx5 env = SDL_IM_MODULE, fcitx5 env = XMODIFIERS, @im=fcitx5 From 1d718a8424afc12d3c525166737464e23344e0c0 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 21:24:14 -0300 Subject: [PATCH 22/35] :wrench: chore(config/hyprland): update `.last-updated` file --- resources/config/hyprland/.last-updated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index 5913a33b..3330ba0d 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -february 09, 10:20 +february 09, 21:23 From d4cdf90c8942073e870da2463e4efb85fbb0f1cd Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 22:15:52 -0300 Subject: [PATCH 23/35] :boom: fix(config/hyprland): correctly set IME env variables --- resources/config/hyprland/.last-updated | 2 +- resources/config/hyprland/environment.conf | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/config/hyprland/.last-updated b/resources/config/hyprland/.last-updated index 3330ba0d..26182fe5 100644 --- a/resources/config/hyprland/.last-updated +++ b/resources/config/hyprland/.last-updated @@ -1 +1 @@ -february 09, 21:23 +february 09, 21:57 diff --git a/resources/config/hyprland/environment.conf b/resources/config/hyprland/environment.conf index c4822475..d04716fb 100644 --- a/resources/config/hyprland/environment.conf +++ b/resources/config/hyprland/environment.conf @@ -7,6 +7,7 @@ env = XDG_DATA_HOME, $HOME/.local/share env = XDG_STATE_HOME, $HOME/.local/state # Input methods -env = QT_IM_MODULE, fcitx5 -env = SDL_IM_MODULE, fcitx5 -env = XMODIFIERS, @im=fcitx5 +env = QT_IM_MODULE, fcitx +env = QT_IM_MODULES, wayland;fcitx +env = SDL_IM_MODULE, fcitx +env = XMODIFIERS, @im=fcitx From 33e635540b2417ac14433f4db22c96d60f3a8a09 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 22:17:12 -0300 Subject: [PATCH 24/35] :wrench: chore(modules/nightlight): handle daemon initialization --- src/modules/nightlight.ts | 49 +++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/modules/nightlight.ts b/src/modules/nightlight.ts index 05ca022d..4b77b747 100644 --- a/src/modules/nightlight.ts +++ b/src/modules/nightlight.ts @@ -3,9 +3,11 @@ import { userData } from "../config"; import GObject, { getter, register, setter } from "ags/gobject"; import GLib from "gi://GLib?version=2.0"; +import Gio from "gi://Gio?version=2.0"; +import { getPID, killProc } from "./utils"; -@register({ GTypeName: "NightLight" }) +@register({ GTypeName: "ClshNightLight" }) export class NightLight extends GObject.Object { private static instance: NightLight; @@ -14,10 +16,11 @@ export class NightLight extends GObject.Object { public static readonly identityTemperature = 6000; public static readonly maxGamma = 100; - #watchInterval: GLib.Source; + #watchInterval?: GLib.Source; #temperature: number = NightLight.identityTemperature; #gamma: number = NightLight.maxGamma; #identity: boolean = false; + #proc: Gio.Subprocess|null = null; @getter(Number) public get temperature() { return this.#temperature; } @@ -44,12 +47,20 @@ export class NightLight extends GObject.Object { constructor() { super(); - this.loadData(); - this.#watchInterval = setInterval(() => this.syncData(), 10000); + const pid = getPID("hyprsunset"); + if(pid != null) + killProc(pid); + + this.restartDaemon().then(() => { + this.loadData(); + this.#watchInterval = setInterval(() => this.syncData(), 10000); + }).catch(e => { + console.error("Night Light: Failed to initialize daemon(is it installed?):", e); + }); } vfunc_dispose(): void { - this.#watchInterval.destroy(); + this.#watchInterval?.destroy(); } public static getDefault(): NightLight { @@ -59,6 +70,34 @@ export class NightLight extends GObject.Object { return this.instance; } + public async quitDaemon(): Promise { + if(!this.#proc) + return false; + + return new Promise((resolve, reject) => { + this.#proc!.wait_async(null, (_, res) => { + let result!: boolean; + try { + result = this.#proc!.wait_finish(res); + } catch(e) { + reject(e); + return; + } + + resolve(result); + }); + + this.#proc!.force_exit(); + }); + } + + public async restartDaemon(): Promise { + if(this.#proc) + await this.quitDaemon(); + + this.#proc = Gio.Subprocess.new(["hyprsunset"], Gio.SubprocessFlags.STDOUT_SILENCE); + } + private syncData(): void { execAsync("hyprctl hyprsunset temperature").then(t => { if(t.trim() !== "" && t.trim().length <= 5) { From 6fc76e83274e7064ed9c74fe17d0ff14caafd7d4 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 9 Feb 2026 22:17:48 -0300 Subject: [PATCH 25/35] :wrench: chore(modules): cleanup, hide unnecessary output from daemon child processes --- src/modules/auth.ts | 2 +- src/modules/backlight.ts | 148 +++++++++++++++++++-------------------- src/modules/clipboard.ts | 70 +++++++++--------- src/modules/input.ts | 41 +++++------ src/modules/utils.ts | 3 - src/modules/wallpaper.ts | 2 +- 6 files changed, 125 insertions(+), 141 deletions(-) diff --git a/src/modules/auth.ts b/src/modules/auth.ts index 3332a706..f1ed75eb 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -9,7 +9,7 @@ import GLib from "gi://GLib?version=2.0"; import AstalAuth from "gi://AstalAuth?version=0.1"; -@register({ GTypeName: "AuthAgent" }) +@register({ GTypeName: "ClshPolKit" }) export class Auth extends PolkitAgent.Listener { private static instance: Auth; #handle: any; diff --git a/src/modules/backlight.ts b/src/modules/backlight.ts index 2fa160b3..f755dc3b 100644 --- a/src/modules/backlight.ts +++ b/src/modules/backlight.ts @@ -1,104 +1,97 @@ import { monitorFile, readFile } from "ags/file"; import { exec } from "ags/process"; import GObject, { getter, ParamSpec, register, setter, signal } from "ags/gobject"; - import Gio from "gi://Gio?version=2.0"; -export namespace Backlights { - - const BacklightParamSpec = (name: string, flags: GObject.ParamFlags) => - GObject.ParamSpec.jsobject(name, null, null, flags) as ParamSpec; +@register({ GTypeName: "ClshBacklights" }) +export class Backlights extends GObject.Object { + private static instance: Backlights; - let instance: Backlights; + #backlights: Array = []; + #default: Backlights.Backlight|null = null; + #available: boolean = false; - export function getDefault(): Backlights { - if(!instance) - instance = new Backlights(); - return instance; - } + @getter(Array as unknown as ParamSpec>) + get backlights() { return this.#backlights; } - @register({ GTypeName: "Backlights" }) - class _Backlights extends GObject.Object { + @getter(GObject.Object) + get default() { return this.#default!; } - #backlights: Array = []; - #default: Backlight|null = null; - #available: boolean = false; - + /** true if there are any backlights available */ + @getter(Boolean) + get available() { return this.#available; } - @getter(Array as unknown as ParamSpec>) - get backlights() { return this.#backlights; } + public scan(): Array { + const dir = Gio.File.new_for_path(`/sys/class/backlight`), + backlights: Array = []; - @getter(BacklightParamSpec) - get default() { return this.#default!; } + let fileEnum: Gio.FileEnumerator; - /** true if there are any backlights available */ - @getter(Boolean) - get available() { return this.#available; } + try { + fileEnum = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); + for(const backlight of fileEnum) { + try { + backlights.push(new Backlights.Backlight(backlight.get_name())); + } catch(_) {} + } + } catch(_) { + return []; + } - public scan(): Array { - const dir = Gio.File.new_for_path(`/sys/class/backlight`), - backlights: Array = []; + if(backlights.length < 1) { + if(this.#available) { + this.#available = false; + this.notify("available"); + } - let fileEnum: Gio.FileEnumerator; + this.#default = null; + this.notify("default"); + } - try { - fileEnum = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); - for(const backlight of fileEnum) { - try { - backlights.push(new Backlight(backlight.get_name())); - } catch(_) {} - } - } catch(_) { - return []; + if(backlights.length > 0) { + if(this.#backlights.length < 1) { + this.#available = true; + this.notify("available"); } - if(backlights.length < 1) { - if(this.#available) { - this.#available = false; - this.notify("available"); - } - - this.#default = null; + if(!this.#default || !backlights.filter(bk => bk.path === this.#default?.path)[0]) { + this.#default = backlights[0]; this.notify("default"); } + } - if(backlights.length > 0) { - if(this.#backlights.length < 1) { - this.#available = true; - this.notify("available"); - } + this.#backlights = backlights; + this.notify("backlights"); - if(!this.#default || !backlights.filter(bk => bk.path === this.#default?.path)[0]) { - this.#default = backlights[0]; - this.notify("default"); - } - } + return backlights; + } - this.#backlights = backlights; - this.notify("backlights"); + public setDefault(bk: Backlights.Backlight): void { + this.#default = bk; + this.notify("default"); + } - return backlights; - } + constructor(scan: boolean = true) { + super(); + scan && this.scan(); + } - public setDefault(bk: Backlight): void { - this.#default = bk; - this.notify("default"); - } + public static getDefault(): Backlights { + if(!this.instance) + this.instance = new Backlights(); - constructor(scan: boolean = true) { - super(); - scan && this.scan(); - } + return this.instance; } +} + +export namespace Backlights { @register({ GTypeName: "Backlight" }) - class _Backlight extends GObject.Object { + export class Backlight extends GObject.Object { - declare $signals: GObject.Object.SignalSignatures & { - "brightness-changed": (value: number) => void - }; + declare $signals: Backlight.SignalSignatures; readonly #name: string; #path: string; @@ -116,7 +109,7 @@ export namespace Backlights { get path() { return this.#path; } @getter(Boolean) - get isDefault() { return this.path === getDefault().default?.path; } + get isDefault() { return this.path === Backlights.getDefault().default?.path; } @getter(Number) get brightness() { return this.#brightness; }; @@ -142,7 +135,7 @@ export namespace Backlights { throw new Error(`Brightness: Couldn't find brightness for "${name}"`); // notify :is-default on default backlight change - this.#conn = getDefault().connect("notify::default", () => + this.#conn = Backlights.getDefault().connect("notify::default", () => this.notify("is-default")); this.#name = name; @@ -184,7 +177,7 @@ export namespace Backlights { vfunc_dispose(): void { this.#monitor.cancel(); - getDefault().disconnect(this.#conn); + Backlights.getDefault().disconnect(this.#conn); } public emit( @@ -202,8 +195,9 @@ export namespace Backlights { } } - export const Backlights = _Backlights; - export const Backlight = _Backlight; - export type Backlight = InstanceType; - export type Backlights = InstanceType; + export namespace Backlight { + export interface SignalSignatures extends GObject.Object.SignalSignatures { + "brightness-changed": (value: number) => void; + } + } } diff --git a/src/modules/clipboard.ts b/src/modules/clipboard.ts index 1d19aa7a..1f7f7708 100644 --- a/src/modules/clipboard.ts +++ b/src/modules/clipboard.ts @@ -8,40 +8,16 @@ import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; -export enum ClipboardItemType { - TEXT = 0, - IMAGE = 1 -} - -export class ClipboardItem { - id: number; - type: ClipboardItemType; - preview: string; - - constructor(id: number, type: ClipboardItemType, preview: string) { - this.id = id; - this.type = type; - this.preview = preview; - } -} - -export { Clipboard }; - /** Cliphist Manager and event listener * This only supports wipe and store events from cliphist */ @register({ GTypeName: "Clipboard" }) -class Clipboard extends GObject.Object { +export class Clipboard extends GObject.Object { private static instance: Clipboard; - declare $signals: GObject.Object.SignalSignatures & { - "copied": Clipboard["copied"]; - "wiped": Clipboard["wiped"]; - }; - #dbFile: Gio.File; #dbMonitor: Gio.FileMonitor; #updateDone: boolean = false; - #history = new Array; + #history = new Array; #changesTimeout: (AstalIO.Time|undefined); #ignoreChanges: boolean = false; #procs: Array = []; @@ -59,11 +35,11 @@ class Clipboard extends GObject.Object { this.#procs = [ Gio.Subprocess.new( ["wl-paste", "--type", "text", "--watch", "cliphist", "store"], - Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + Gio.SubprocessFlags.STDOUT_SILENCE ), Gio.Subprocess.new( ["wl-paste", "--type", "image", "--watch", "cliphist", "store"], - Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + Gio.SubprocessFlags.STDOUT_SILENCE ) ]; @@ -127,7 +103,7 @@ stderr for more info.`); return proc.get_exit_status() === 0; } - public async selectItem(itemToSelect: number|ClipboardItem): Promise { + public async selectItem(itemToSelect: number|Clipboard.Item): Promise { const item = await this.getItemContent(itemToSelect); let res: boolean = true; @@ -139,7 +115,7 @@ stderr for more info.`); /** Gets history item's content by its ID. * @returns the clipboard item's content */ - public async getItemContent(item: number|ClipboardItem): Promise { + public async getItemContent(item: number|Clipboard.Item): Promise { const id = (typeof item === "number") ? item : item.id; @@ -183,10 +159,10 @@ stderr for more info.`); return Gio.File.new_for_path(`${GLib.get_user_cache_dir()}/cliphist/db`); } - private getContentType(preview: string): ClipboardItemType { + private getContentType(preview: string): Clipboard.ItemType { return /^\[\[.*binary data.*x.*\]\]$/u.test(preview) ? - ClipboardItemType.IMAGE - : ClipboardItemType.TEXT; + Clipboard.ItemType.IMAGE + : Clipboard.ItemType.TEXT; } public async wipeHistory(noExec?: boolean): Promise { @@ -232,7 +208,7 @@ stderr for more info.`); id: Number.parseInt(id), preview, type: this.getContentType(preview) - } as ClipboardItem; + } as Clipboard.Item; this.#history.unshift(clipItem); @@ -250,7 +226,7 @@ stderr for more info.`); id: Number.parseInt(id), preview, type: this.getContentType(preview) - } as ClipboardItem; + } as Clipboard.Item; this.#history.push(clipItem); @@ -270,3 +246,27 @@ stderr for more info.`); return this.instance; } } + +export namespace Clipboard { + export enum ItemType { + TEXT = 0, + IMAGE = 1 + } + + export class Item { + id: number; + type: ItemType; + preview: string; + + constructor(id: number, type: ItemType, preview: string) { + this.id = id; + this.type = type; + this.preview = preview; + } + } + + export interface SignalSignatures extends GObject.Object.SignalSignatures { + "copied": (item: Clipboard.Item) => void; + "wiped": () => void; + } +} diff --git a/src/modules/input.ts b/src/modules/input.ts index c859ee68..7ce82a82 100644 --- a/src/modules/input.ts +++ b/src/modules/input.ts @@ -1,6 +1,7 @@ import { exec } from "ags/process"; import Gio from "gi://Gio?version=2.0"; import { Notifications } from "./notifications"; +import { getPID, killProc } from "./utils"; export class Input { @@ -15,6 +16,10 @@ export class Input { constructor() { + const pid = getPID("fcitx5"); + if(pid != null) + killProc(pid); + this.restart(); } @@ -25,14 +30,24 @@ export class Input { return this.instance; } + /** force the IME daemon to quit */ + public exit(): void { + try { + exec("fcitx5-remote --check -e"); + } catch(e) { + // so we throw a prettier error + throw new Error("Input: Fcitx5: Failed to quit the daemon. Is it running?"); + } + } + /** @param keep whether to restart the IME daemon after a crash/exit (restart attempts are limited by {@link maxIbusAttempts}) */ public restart(keep: boolean = true): void { if(this.#proc) return; this.#proc = Gio.Subprocess.new( - ["fcitx5", "-r", "-s", "2"], - Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE + ["fcitx5", "-r"], + Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE ); this.#proc.wait_async(null, (_, res) => { @@ -63,26 +78,4 @@ export class Input { console.log("Input: Fcitx5: Exited normally"); }); } - - /** force the IME daemon to quit */ - public exit(): void { - try { - exec("fcitx5-remote --check -e"); - } catch(e) { - // so we throw a prettier error - throw new Error("Input: Fcitx5: Failed to quit the daemon. Is it running?"); - } - } - - protected getDaemonPid(): number|null { - try { - const str = exec("pgrep '^fcitx5$' | head -n1")?.trim(); - if(str.trim() === "") - return null; - - return Number.parseInt(str.trim()); - } catch(e) { - return null; - } - } } diff --git a/src/modules/utils.ts b/src/modules/utils.ts index 76ce59a1..c13c824c 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -91,9 +91,6 @@ export function getPID(search: string): number|undefined { try { result = exec(`pgrep -x "${search}"`).trim().replaceAll('\n', ''); } catch(e) { - if((e as Gio.IOErrorEnum).code !== 1) - console.error("Couldn't search for process by name:", e); - return undefined; } diff --git a/src/modules/wallpaper.ts b/src/modules/wallpaper.ts index e88b6155..fb0d4ea5 100644 --- a/src/modules/wallpaper.ts +++ b/src/modules/wallpaper.ts @@ -232,7 +232,7 @@ export class Wallpaper extends GObject.Object { if(this.#proc) await this.quitDaemon(); - this.#proc = Gio.Subprocess.new(["hyprpaper"], Gio.SubprocessFlags.NONE); + this.#proc = Gio.Subprocess.new(["hyprpaper"], Gio.SubprocessFlags.STDOUT_SILENCE); } private writeChanges(): void { From 92bbc356a7937f82ded0299f5369c862b16c21bc Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 11 Feb 2026 10:55:26 -0300 Subject: [PATCH 26/35] :wrench: chore: save colorshell pid under `XDG_RUNTIME_DIR/colorshell/.pid` --- src/app.ts | 64 +++++++++++++++++++++++++++++++++++++--- src/modules/wallpaper.ts | 2 +- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/app.ts b/src/app.ts index 6eaa6a08..e516aeb4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -35,6 +35,9 @@ import Gio from "gi://Gio?version=2.0"; import Adw from "gi://Adw?version=1"; import AstalWp from "gi://AstalWp"; +Gio._promisify(Gio.DBus, "get", "get_finish"); +Gio._promisify(Gio.DBusProxy, "new", "new_finish"); +Gio._promisify(Gio.DBusConnection.prototype, "call", "call_finish"); const runnerPlugins: Array = [ PluginApps, @@ -67,6 +70,7 @@ export class Shell extends Adw.Application { #providers: Array = []; #socketService!: Gio.SocketService; #socketFile!: Gio.File; + #pid: number|null = null; get scope() { return this.#scope; } @@ -182,15 +186,67 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re }); } + private async getAppPID(): Promise { + if(this.#pid != null) + return this.#pid; + + const session = await (Gio.DBus.get(Gio.BusType.SESSION, null) as unknown as Promise); + const proxy = await (Gio.DBusProxy.new( + session, + Gio.DBusProxyFlags.NONE, + null, + "io.github.retrozinndev.colorshell", + "/io/github/retrozinndev/colorshell", + "org.freedesktop.Application", + null + ) as unknown as Promise); + + const owner = proxy.get_name_owner(); + + if(!owner) + throw new Error("DBus: Couldn't get name owner to retrieve PID"); + + // @ts-ignore + const params = GLib.Variant.new_tuple([GLib.Variant.new_string(owner)]); + + const pidVariant: GLib.Variant<"(u)"> = await session.call( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + params, + GLib.VariantType.new("(u)"), + Gio.DBusCallFlags.NONE, + 300, + null + ); + + if(!pidVariant) + throw new Error("DBus: call to org.freedesktop.DBus.GetConnectionUnixProcessID returned a nullish value"); + + this.#pid = pidVariant.get_child_value(0).get_uint32(); + + return this.#pid; + } + private init(): void { - console.log(`Colorshell: Initializing things`); + console.log("Colorshell: Initializing things"); !Shell.runtimeDir.query_exists(null) && Shell.runtimeDir.make_directory_with_parents(null); - // TODO: find a way to get the pid of colorshell (it's ridiculous that glib doesn't have a method for this, really) - //const pidFile = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/.pid`); - //pidFile.replace_contents(encoder.encode(""), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); + const pidFile = Gio.File.new_for_path(`${Shell.runtimeDir.peek_path()!}/.pid`); + this.getAppPID().then((pid) => { + try { + pidFile.replace_contents(encoder.encode( + pid.toString() + ), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); + } catch(e) { + console.error(e); + } + }).catch(e => { + console.error(e); + }); // load gresource from build-defined path try { diff --git a/src/modules/wallpaper.ts b/src/modules/wallpaper.ts index fb0d4ea5..95e5a9c2 100644 --- a/src/modules/wallpaper.ts +++ b/src/modules/wallpaper.ts @@ -92,7 +92,7 @@ export class Wallpaper extends GObject.Object { }); const pid = getPID("hyprpaper"); - console.log(pid); + if(pid != null) killProc(pid); From d2505baf5ecf889c1a85a512262e1403a0849779 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 11 Feb 2026 11:37:49 -0300 Subject: [PATCH 27/35] :sparkles: feat(compositor/hyprland): support xdg-system-bell colorshell now beeps when the compositor sends a `bell` signal! --- src/modules/compositors/hyprland.ts | 4 ++-- src/modules/utils.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 706ab45c..0838cfa3 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -99,7 +99,7 @@ export class CompositorHyprland extends Compositor { this.#ignoreConfigReload &&= false; break; - case "beep": + case "bell": playSystemBell(); break; } @@ -200,7 +200,7 @@ export namespace CompositorHyprland { | "windowtitlev2" | "workspacev2" | "focusedmon" - | "beep" + | "bell" | "focusedmonv2"; export type Client = { diff --git a/src/modules/utils.ts b/src/modules/utils.ts index c13c824c..bbc689c0 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -94,7 +94,6 @@ export function getPID(search: string): number|undefined { return undefined; } - console.log(result); const pid = Number.parseInt(result); if(!isNaN(pid)) From 249bd5a5627e91ae7cd399f1d0226006fd2add00 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 12 Feb 2026 12:13:28 -0300 Subject: [PATCH 28/35] :wrench: chore(modules/apps): remove special characters from client class to search for icons --- src/modules/apps.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/modules/apps.ts b/src/modules/apps.ts index 46e0fba4..d6f3dc86 100644 --- a/src/modules/apps.ts +++ b/src/modules/apps.ts @@ -64,6 +64,10 @@ export function getIconByAppName(appName: string): (string|undefined) { if(lookupIcon(appName.toLowerCase())) return appName.toLowerCase(); + + const nameNoSpecialChars = appName.toLowerCase().replace(/[!?:,~]/g, ""); // solution for cases like "osu!" + if(lookupIcon(nameNoSpecialChars)) + return nameNoSpecialChars; const nameReverseDNS = appName.split('.'); const lastItem = nameReverseDNS[nameReverseDNS.length - 1]; @@ -103,6 +107,17 @@ export function getAppIcon(app: (string|AstalApps.Application)): (string|undefin export function getSymbolicIcon(app: (string|AstalApps.Application)): (string|undefined) { const icon = getAppIcon(app); + if(icon === undefined) + return undefined; + + const commonSymbolic = `${icon}-symbolic`; + if(lookupIcon(commonSymbolic)) + return commonSymbolic; + + const nameNoSpecialChars = `${icon.replace(/[!?:,~]/g, "")}-symbolic`; + if(lookupIcon(nameNoSpecialChars)) + return nameNoSpecialChars; + return (icon && lookupIcon(`${icon}-symbolic`)) ? `${icon}-symbolic` : undefined; From fee6cd98a85a7685df79821a4aa0f10fddbabe9a Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 12 Feb 2026 17:44:31 -0300 Subject: [PATCH 29/35] :boom: fix(modules/socket): io pending errors while reading it now waits for a previous reading operation to end before starting a new one, avoiding the error message --- src/modules/socket.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/modules/socket.ts b/src/modules/socket.ts index 5e0d62e8..76d2a2ed 100644 --- a/src/modules/socket.ts +++ b/src/modules/socket.ts @@ -28,8 +28,8 @@ export class Socket extends GObject. protected server: Gio.SocketService|null = null; protected address: Gio.UnixSocketAddress|null = null; protected scope: Scope = createRoot(() => getScope()); - protected encoder: TextEncoder|null = null; - protected decoder: TextDecoder|null = null; + protected readonly encoder = new TextEncoder(); + protected readonly decoder = new TextDecoder("utf-8"); @signal(String) protected received(_: string) {} @@ -113,39 +113,40 @@ export class Socket extends GObject. sock.set_blocking(false); sock.set_keepalive(true); // keep listening to the socket + let pending: boolean = false; GLib.io_add_watch( GLib.IOChannel.unix_new(sock.get_fd()), - GLib.PRIORITY_DEFAULT, + GLib.PRIORITY_LOW, GLib.IOCondition.IN | GLib.IOCondition.PRI | GLib.IOCondition.HUP, (_, cond: GLib.IOCondition) => { if(cond === GLib.IOCondition.HUP) { - conn.close(); + conn.close(null); console.log(`Socket: Connection was hang up`); return false; } - if(conn.is_closed()) { - console.log("Socket: The listening input stream has been closed, ignoring current call"); + console.warn("Socket: The socket connection has been closed, current call is being ignored"); return false; } - if(conn.inputStream.is_closed()) { - console.log("Socket: The input stream got closed, the i/o watch will be removed"); + if(conn.inputStream.is_closed() || conn.outputStream.is_closed()) { + console.warn("Socket: One of the streams got closed, the IO watch will be removed"); return false; } - if(conn.outputStream.is_closed()) { - console.log("Socket: The input stream got closed, the i/o watch will be removed"); - return false; + if(pending) { + console.log("Socket: Stream: There already is a read on its way, this call was ignored"); + return true; } + pending = true; conn.inputStream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null, (_, res) => { let str!: string; + pending = false; try { - str = (this.decoder ?? (this.decoder = new TextDecoder)) - .decode(conn.inputStream.read_bytes_finish(res).toArray()); + str = this.decoder.decode(conn.inputStream.read_bytes_finish(res).toArray()); } catch(e) { console.error(`Socket: An error occurred while reading the input stream bytes: ${(e as Error).message}`); console.debug(e); @@ -185,6 +186,7 @@ export class Socket extends GObject. async simpleSend(message: string, wait: boolean = false): Promise { return new Promise((resolve, reject) => { const client = Gio.SocketClient.new(); + client.set_family(Gio.SocketFamily.UNIX); client.set_socket_type(Gio.SocketType.STREAM); client.connect_async(this.address!, null, (_, res) => { @@ -197,7 +199,7 @@ export class Socket extends GObject. } conn.outputStream.write_bytes_async( - (this.encoder ?? (this.encoder = new TextEncoder())).encode(message), + this.encoder.encode(message), GLib.PRIORITY_DEFAULT, null, (_, res) => { @@ -275,7 +277,7 @@ export class Socket extends GObject. } conn.outputStream.write_bytes_async( - (this.encoder ?? (this.encoder = new TextEncoder())).encode(message), + this.encoder.encode(message), GLib.PRIORITY_DEFAULT, null, (_, res) => { From dc1a2886ee4748f0f9bb70cf733c6ffb7d90d0a4 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Fri, 13 Feb 2026 17:47:49 -0300 Subject: [PATCH 30/35] :wastebasket: wrench(config/hyprland): remove unused `vars.conf` --- resources.gresource.xml | 1 - resources/config/hyprland/vars.conf | 6 ------ 2 files changed, 7 deletions(-) delete mode 100644 resources/config/hyprland/vars.conf diff --git a/resources.gresource.xml b/resources.gresource.xml index 6a70ab8e..34d6657a 100644 --- a/resources.gresource.xml +++ b/resources.gresource.xml @@ -37,7 +37,6 @@ config/hyprland/.last-updated config/hyprland/environment.conf - config/hyprland/vars.conf config/hyprland/decorations.conf config/hyprland/bindings.conf config/hyprland/rules.conf diff --git a/resources/config/hyprland/vars.conf b/resources/config/hyprland/vars.conf deleted file mode 100644 index 84307e06..00000000 --- a/resources/config/hyprland/vars.conf +++ /dev/null @@ -1,6 +0,0 @@ -############### -## VARIABLES ## -############### - - -$exec = (bash -c "command -v uwsm && uwsm check is-active && echo 'uwsm-app '" | tail -n1 2>/dev/null) From 5a08798d897e024fc5dc6a3b2689eafed897bb6d Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Fri, 13 Feb 2026 17:49:23 -0300 Subject: [PATCH 31/35] :wrench: chore(modules/socket): use separate methods to avoid spaggetti code, other improvements --- src/modules/compositors/hyprland.ts | 4 +- src/modules/socket.ts | 217 +++++++++++++++++----------- 2 files changed, 131 insertions(+), 90 deletions(-) diff --git a/src/modules/compositors/hyprland.ts b/src/modules/compositors/hyprland.ts index 0838cfa3..c4b0a9af 100644 --- a/src/modules/compositors/hyprland.ts +++ b/src/modules/compositors/hyprland.ts @@ -136,7 +136,7 @@ export class CompositorHyprland extends Compositor { const userLastUpdatedFile = Gio.File.new_for_path(`${this.#configDir.peek_path()!}/.last-updated`); const names = Gio.resources_enumerate_children( "/io/github/retrozinndev/colorshell/config/hyprland", - Gio.ResourceLookupFlags.NONE + null ); if(userLastUpdatedFile.query_exists(null)) { @@ -157,7 +157,7 @@ export class CompositorHyprland extends Compositor { name, Gio.resources_lookup_data( `/io/github/retrozinndev/colorshell/config/hyprland/${name}`, - Gio.ResourceLookupFlags.NONE + null ) ] satisfies [string, GLib.Bytes]); diff --git a/src/modules/socket.ts b/src/modules/socket.ts index 76d2a2ed..58f7a2f0 100644 --- a/src/modules/socket.ts +++ b/src/modules/socket.ts @@ -37,8 +37,8 @@ export class Socket extends GObject. @signal(String) protected sent(_: string) {} - @signal(Gio.IOErrorEnum) - protected panic(_: Gio.IOErrorEnum) {} + @signal(Object) + protected panic(_: Error) {} /** @param listen whether to listen to the socket as soon as the class is instantiated * @param contentSeparator server response separator, by default it's a line-break */ @@ -76,7 +76,7 @@ export class Socket extends GObject. try { str = data.read_upto_finish(res)[0]; } catch(e) { - this.emit("panic", e as Gio.IOErrorEnum); + this.emit("panic", e as Error); return; } @@ -108,59 +108,123 @@ export class Socket extends GObject. return; } - const sock = conn.get_socket(); - sock.set_timeout(0); - sock.set_blocking(false); - sock.set_keepalive(true); // keep listening to the socket - - let pending: boolean = false; - GLib.io_add_watch( - GLib.IOChannel.unix_new(sock.get_fd()), - GLib.PRIORITY_LOW, - GLib.IOCondition.IN | GLib.IOCondition.PRI | GLib.IOCondition.HUP, - (_, cond: GLib.IOCondition) => { - if(cond === GLib.IOCondition.HUP) { - conn.close(null); - console.log(`Socket: Connection was hang up`); - return false; - } + this.watchOutput(conn, (data) => { + // separate messages by line-break + data.split(outputSeparator).filter(s => s.trim() !== "").forEach(msg => + this.emit("received", msg) + ); + }); + }); + } - if(conn.is_closed()) { - console.warn("Socket: The socket connection has been closed, current call is being ignored"); - return false; - } + /** watch a socket for new messages using `GIOWatch`. + * @param socket the socket or connection to watch + * @param callback called when the watch triggers one of the `conditions` + * @param conditions `GIOConditions` to watch the socket IO for. default: `IN`|`PRI`|`HUP` + * (when `HUP`, the watch is internally removed, so you just need to disconnect from the socket) + * + * @returns a `GCancellable`, which can be used to stop the `GIOWatch` of the method */ + protected watchSocket( + socket: Gio.Socket|Gio.SocketConnection, + callback?: (condition: GLib.IOCondition) => void, + conditions: GLib.IOCondition = GLib.IOCondition.IN|GLib.IOCondition.PRI|GLib.IOCondition.HUP + ): Gio.Cancellable { + socket = socket instanceof Gio.SocketConnection ? + socket.get_socket() + : socket; + + const cancellable = Gio.Cancellable.new(); + const chaneru = GLib.IOChannel.unix_new(socket.get_fd()); + const source = GLib.io_create_watch(chaneru, conditions); + + source.set_priority(GLib.PRIORITY_LOW); + source.set_callback(((_: GLib.IOChannel, condition: GLib.IOCondition) => { + if(condition === GLib.IOCondition.HUP) { + cancellable.cancel(); + callback?.(condition); + return false; + } - if(conn.inputStream.is_closed() || conn.outputStream.is_closed()) { - console.warn("Socket: One of the streams got closed, the IO watch will be removed"); - return false; - } + if(callback) + return callback(condition); - if(pending) { - console.log("Socket: Stream: There already is a read on its way, this call was ignored"); - return true; - } + return true; + }) as never); + source.attach(null); + + const id = GObject.Object.prototype.connect.call(cancellable, "cancelled", () => { + cancellable.disconnect(id); + source.destroy(); + }); - pending = true; - conn.inputStream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null, (_, res) => { - let str!: string; - pending = false; + return cancellable; + } - try { - str = this.decoder.decode(conn.inputStream.read_bytes_finish(res).toArray()); - } catch(e) { - console.error(`Socket: An error occurred while reading the input stream bytes: ${(e as Error).message}`); - console.debug(e); - return; - } + /** watch a socket connection for output + * when `timeout` is reached, the `GIOWatch` will be destroyed and `GCancellable::cancelled` eimtted. + * + * @param conn the `GSocketConnection` to watch for output + * @param callback called when the connection receives data + * @param timeout maximum time to wait for a response in milliseconds. set to -1 to run indefinitely. + * + * @returns a `GCancellable`, which can be used to cancel the `GIOWatch` */ + protected watchOutput( + conn: Gio.SocketConnection, + callback?: (data: string) => void, + timeout: number = -1, + onTimeout?: () => void + ): Gio.Cancellable { + let pending: boolean = false; + const cancellable = this.watchSocket(conn, (cond) => { + if(cond === GLib.IOCondition.HUP) { + conn.close(null); + console.log("Socket: IO and Socket Connection were closed"); + cancellable.cancel(); + return false; + } + + if(conn.is_closed()) { + console.warn("Socket: The Socket connection is closed"); + cancellable.cancel(); + return false; + } + + if(conn.inputStream.is_closed() || conn.outputStream.is_closed()) { + console.warn("Socket: One of the streams got closed, the IO watch will be removed"); + cancellable.cancel(); + return false; + } + + if(pending) { + console.debug("Socket: Stream: There already is a read on its way, this call was ignored"); + cancellable.cancel(); + return true; + } - str.split(outputSeparator).filter(s => s.trim() !== "").forEach(msg => - this.emit("received", msg) - ); - }); - return true; + pending = true; + conn.inputStream.read_bytes_async(2048, GLib.PRIORITY_DEFAULT, null, (_, res) => { + let str!: string; + pending = false; + + try { + str = this.decoder.decode(conn.inputStream.read_bytes_finish(res).toArray()); + } catch(e) { + console.error("Socket: An error occurred while reading the input stream bytes:", e); + return; } - ); + + callback?.(str); + }); + return true; }); + + if(timeout >= 0) + setTimeout(() => { + cancellable.cancel(); + onTimeout?.(); + }, timeout); + + return cancellable; } /** allows to create a connection paired with the instance scope. @@ -180,7 +244,7 @@ export class Socket extends GObject. * sends a message to the socket using a GSocketConnection. * * @param message contents to send to the socket - * @param wait whether to wait for the socket to finish the connection + * @param wait whether to wait for the socket to send a response * * @returns a `string` promise, that returns the socket's response to the message, can be null. */ async simpleSend(message: string, wait: boolean = false): Promise { @@ -200,7 +264,7 @@ export class Socket extends GObject. conn.outputStream.write_bytes_async( this.encoder.encode(message), - GLib.PRIORITY_DEFAULT, + GLib.PRIORITY_LOW, null, (_, res) => { try { @@ -212,43 +276,20 @@ export class Socket extends GObject. return; } - let output: string = ""; - const chaneru = GLib.IOChannel.unix_new(conn.get_socket().get_fd()); - GLib.io_add_watch( - chaneru, - GLib.PRIORITY_DEFAULT, - GLib.IOCondition.IN | GLib.IOCondition.PRI | GLib.IOCondition.HUP, - (_, cond) => { - if(cond === GLib.IOCondition.HUP) { - resolve(output); - conn.close(); - return false; - } - - const data = Gio.DataInputStream.new(conn.inputStream); - data.read_upto_async('\x00', -1, GLib.PRIORITY_DEFAULT, null, (_, res) => { - let out!: string; - try { - out = data.read_upto_finish(res)[0]; - output = out; - } catch(e) { - resolve(output); - return; - } - - }); - - if(wait) { - resolve(output); - conn.close(); - return false; - } - - return true; - } - ); + if(!wait) { + resolve(null); + return; + } + + this.watchOutput(conn, (data) => { + resolve(data); + conn.close(); + }, 10000, () => { + const err = new Error("Socket: 10 Seconds timeout reached"); + reject(err); + this.emit("panic", err); + }); } catch(e) { - this.emit("panic", e as Gio.IOErrorEnum); } } ); @@ -292,7 +333,7 @@ export class Socket extends GObject. resolve(conn.inputStream); } catch(e) { - this.emit("panic", e as Gio.IOErrorEnum); + this.emit("panic", e as Error); } } ); @@ -326,7 +367,7 @@ export namespace Socket { /** client-only signal. emitted when a message is sent to the server */ "sent": (content: string) => void; /** emitted when a stream error occurs, if it ever happen */ - "panic": (error: Gio.IOErrorEnum) => void; + "panic": (error: Error) => void; } export enum Type { From 02ae38950d4712f3329b595ef365a253c8e3f52c Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sat, 14 Feb 2026 13:37:34 -0300 Subject: [PATCH 32/35] :boom: fix(modules/nightlight): hyprsunset not starting with colorshell we wait a little bit to re-launch hyprsunset after killing the existing instance --- src/modules/nightlight.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/modules/nightlight.ts b/src/modules/nightlight.ts index 4b77b747..ee9a5f43 100644 --- a/src/modules/nightlight.ts +++ b/src/modules/nightlight.ts @@ -51,12 +51,15 @@ export class NightLight extends GObject.Object { if(pid != null) killProc(pid); - this.restartDaemon().then(() => { - this.loadData(); - this.#watchInterval = setInterval(() => this.syncData(), 10000); - }).catch(e => { - console.error("Night Light: Failed to initialize daemon(is it installed?):", e); - }); + // we wait, because the process can take some time to quit + setTimeout(() => { + this.restartDaemon().then(() => { + this.loadData(); + this.#watchInterval = setInterval(() => this.syncData(), 10000); + }).catch(e => { + console.error("Night Light: Failed to initialize daemon(is it installed?):", e); + }); + }, 500); } vfunc_dispose(): void { From 631fb4e6ee1970fe489fae7244cf0fc8c7d61715 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sat, 14 Feb 2026 14:04:29 -0300 Subject: [PATCH 33/35] :wrench: chore(resources, install): auto-add default config files if they're not found Hyprland and hyprpaper default configuration files will be installed if they're not found at their dirs. Also, colorshell now asks if the user want's to add an autostart/exec-once line to their Hyprland config file --- install.sh | 24 +++++++++++++++++++++--- resources/config/hyprpaper.conf | 9 +++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 resources/config/hyprpaper.conf diff --git a/install.sh b/install.sh index 4e72f463..3074ef19 100755 --- a/install.sh +++ b/install.sh @@ -96,10 +96,28 @@ if [[ "$answer" == y ]] || [[ "$skip_prompts" ]]; then mkdir -p $APPS_HOME cp -f $repo_directory/build/release/colorshell.desktop $APPS_HOME + # Check if user has a Hyprland config file + if [[ ! -f "$XDG_CONFIG_HOME/hypr/hyprland.conf" ]] && [[ -f "/usr/share/hypr/hyprland.conf" ]]; then + Send_log "Looks like Hyprland wasn't launched yet! Copying default config file..." + mkdir -p "$XDG_CONFIG_HOME/hypr" + cp -f "/usr/share/hypr/hyprland.conf" "$XDG_CONFIG_HOME/hypr/hyprland.conf" + Send_log "Adding exec for colorshell in Hyprland config..." + echo -ne "\nexec-once = ~/.local/bin/colorshell" >> "$XDG_CONFIG_HOME/hypr/hyprland.conf" + else + Ask "Do you want to autostart colorshell with Hyprland?" + if [ "$answer" == "y" ]; then + Send_log "Adding exec-once for colorshell to Hyprland..." + echo -ne "\nexec-once = ~/.local/bin/colorshell" >> "$XDG_CONFIG_HOME/hypr/hyprland.conf" + fi + fi - Send_log "Adding default wallpaper in ~/wallpapers" - mkdir -p $HOME/wallpapers - cp -f $repo_directory/resources/wallpaper_default.jpg "$HOME/wallpapers/Default Hypr-chan.jpg" + # Check for wallpaper configuration + if [[ ! -f "$XDG_CONFIG_HOME/hypr/hyprpaper.conf" ]]; then + Send_log "No hyprpaper config found, using colorshell's default..." + mkdir -p $HOME/wallpapers + cp -f $repo_directory/resources/wallpaper_default.jpg "$HOME/wallpapers/Default Hypr-chan.jpg" + cp -f $repo_directory/resources/config/hyprpaper.conf "$XDG_CONFIG_HOME/hypr/hyprpaper.conf" + fi if [[ -z "$skip_prompts" ]]; then echo "Colorshell is installed! :D" diff --git a/resources/config/hyprpaper.conf b/resources/config/hyprpaper.conf new file mode 100644 index 00000000..2bbdeb9c --- /dev/null +++ b/resources/config/hyprpaper.conf @@ -0,0 +1,9 @@ +# Default hyprpaper configuration for colorshell +# Added by the install.sh script, because there was no wallpaper set before + +splash = true +wallpaper { + monitor = * + path = $WALL_PATH + fit_mode = cover +} From 568d0f49eccdf8ba588632cc7bc3731d9b9ccc70 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 16 Feb 2026 14:29:13 -0300 Subject: [PATCH 34/35] :sparkles: feat(modules/recording): configurable "include_audio" feature for screen recording! --- src/config.ts | 5 +++++ src/modules/recording.ts | 32 ++++++++++++-------------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/config.ts b/src/config.ts index d0c89f16..23f548cd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -38,6 +38,11 @@ const generalConfigDefaults = { save_on_shutdown: true }, + screen_recording: { + /** include desktop audio output when screen-recording */ + include_audio: true + }, + wallpaper: { /** wallpaper positioning mode (hyprpaper) */ positioning: "cover" satisfies WallpaperPositioning, diff --git a/src/modules/recording.ts b/src/modules/recording.ts index ddb82ebb..1f3973fe 100644 --- a/src/modules/recording.ts +++ b/src/modules/recording.ts @@ -2,13 +2,14 @@ import { execAsync } from "ags/process"; import { getter, register, signal } from "ags/gobject"; import { Gdk } from "ags/gtk4"; import { createRoot, getScope, Scope } from "ags"; -import { makeDirectory } from "./utils"; +import { createSubscription, makeDirectory } from "./utils"; import { Notifications } from "./notifications"; import { time } from "./utils"; import GObject from "ags/gobject"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; +import { generalConfig } from "../config"; @register({ GTypeName: "Recording" }) @@ -24,7 +25,6 @@ export class Recording extends GObject.Object { /** Default extension: mp4(h264) */ #extension: string = "mp4"; - #recordAudio: boolean = false; #area: (Gdk.Rectangle|null) = null; #startedAt: number = -1; #process: (Gio.Subprocess|null) = null; @@ -82,19 +82,10 @@ export class Recording extends GObject.Object { /** Recording output file name. null if screen is not being recorded */ public get output() { return this.#output; } - /** Currently unsupported property */ - public get recordAudio() { return this.#recordAudio; } - public set recordAudio(newValue: boolean) { - if(this.recording) return; - - this.#recordAudio = newValue; - this.notify("record-audio"); - } constructor() { super(); - const videosDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS); - if(videosDir) this.#path = `${videosDir}/Recordings`; + this.#path = `${GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS)}/Recordings`; } public static getDefault() { @@ -122,6 +113,7 @@ export class Recording extends GObject.Object { this.#process = Gio.Subprocess.new([ "wf-recorder", ...(area ? [ `-g`, areaString ] : []), + ...(generalConfig.getProperty("screen_recording.include_audio") ? ["-a"] : []), "-f", `${this.path}/${this.output!}` ], Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE); @@ -133,11 +125,7 @@ export class Recording extends GObject.Object { this.#startedAt = time.get().to_unix(); this.notify("started-at"); - const timeSub = time.subscribe(() => { - this.notify("recording-time"); - }); - - this.#recordingScope.onCleanup(timeSub); + createSubscription(time, () => this.notify("recording-time")); }); } @@ -168,11 +156,15 @@ export class Recording extends GObject.Object { onAction: () => { execAsync(["xdg-open", `${path}/${output}`]); } + }, { + text: "Open file directory", + id: "view-directory", + onAction: () => execAsync(`xdg-open '${path}'`) } ], - appName: "Screen Recording", - summary: "Screen Recording saved", - body: `Saved as ${path}/${output}` + appName: "colorshell", + summary: "Screen Recording", + body: `Saved recording as "${path}/${output}"` }); } }; From ebc7a2195fd2e483a96aba48fb8160a464f658f6 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Mon, 16 Feb 2026 14:47:52 -0300 Subject: [PATCH 35/35] :boom: fix(modules/config): returning undefined even if `expectType` is "any" --- src/modules/config.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/modules/config.ts b/src/modules/config.ts index 9718683f..177ab664 100644 --- a/src/modules/config.ts +++ b/src/modules/config.ts @@ -250,7 +250,7 @@ export class Config extends GObject.Object { )); } - private _getProperty(path: string, entries: Record, expectType?: ValueTypes): (any|undefined) { + private _getProperty(path: string, entries: Record, expectType?: ValueTypes, ignoreUndefined: boolean = false): (any|undefined) { let property: any = entries; const pathArray = path.split('.').filter(str => str); @@ -260,20 +260,9 @@ export class Config extends GObject.Object { property = property[currentPath as keyof typeof property]; } - if(expectType !== "any" && typeof property !== expectType) { - // return default value if not defined by user - property = this.defaults; - - for(let i = 0; i < pathArray.length; i++) { - const currentPath = pathArray[i]; - - property = property[currentPath as keyof typeof property]; - } - } - - if(expectType !== "any" && typeof property !== expectType) { - console.debug(`Config: property with path \`${path}\` not found in defaults/user-entries, returning \`undefined\``); - property = undefined; + if(!ignoreUndefined && property === undefined) { + const defaultValue = this._getProperty(path, this.defaults, expectType, true); + return defaultValue; } return property;