diff options
| author | Ja.KooLit <85185940+JaKooLit@users.noreply.github.com> | 2025-07-25 21:31:54 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-25 21:31:54 +0900 |
| commit | dd889d3aac40075ee73f76290c69ccc698673817 (patch) | |
| tree | 67dc1026bc09a93cb5250e1c28adda15c592aff4 | |
| parent | 637025eeb60391e5cc17c883aa6ee95799acac76 (diff) | |
| parent | aa3c94f1ad44038d6a71ee8e12a704e6089e31f6 (diff) | |
Merge pull request #775 from JaKooLit/development
Development to main
122 files changed, 5371 insertions, 179 deletions
@@ -44,9 +44,6 @@ https://github.com/user-attachments/assets/49bc12b2-abaf-45de-a21c-67aacd9bb872 ### 📹 A video walkthroughs - at the bottom -### 🎞️ AGS Overview DEMO -- in case you wonder, here is a short demo of AGS overview [Youtube LINK](https://youtu.be/zY5SLNPBJTs) - </details> --- @@ -182,8 +179,6 @@ chmod +x upgrade.sh #### 🤷♂️ TO DO! - [ ] Tweak dots - 🚧 in constant progress -- ~~[ ] Quite possibly switch to starship? Although starship has limited themes compared to oh-my-zsh.~~ no plans for now - #### 🔮 Discord Server - kindly join my [Discord](https://discord.com/invite/kool-tech-world) diff --git a/config/hypr/UserConfigs/ENVariables.conf b/config/hypr/UserConfigs/ENVariables.conf index 2f53c74c..f24cc306 100644 --- a/config/hypr/UserConfigs/ENVariables.conf +++ b/config/hypr/UserConfigs/ENVariables.conf @@ -54,6 +54,7 @@ env = ELECTRON_OZONE_PLATFORM_HINT,auto # auto selects Wayland if possible, X11 #env = LIBVA_DRIVER_NAME,nvidia #env = __GLX_VENDOR_LIBRARY_NAME,nvidia #env = NVD_BACKEND,direct +#env = GSK_RENDERER,ngl # additional ENV's for nvidia. Caution, activate with care #env = GBM_BACKEND,nvidia-drm diff --git a/config/hypr/UserConfigs/Startup_Apps.conf b/config/hypr/UserConfigs/Startup_Apps.conf index 702f9a90..f8af55e4 100644 --- a/config/hypr/UserConfigs/Startup_Apps.conf +++ b/config/hypr/UserConfigs/Startup_Apps.conf @@ -30,6 +30,7 @@ exec-once = swaync #exec-once = blueman-applet #exec-once = rog-control-center exec-once = waybar +exec-once = qs # quickshell AGS Desktop Overview alternative #clipboard manager exec-once = wl-paste --type text --watch cliphist store diff --git a/config/hypr/UserConfigs/UserKeybinds.conf b/config/hypr/UserConfigs/UserKeybinds.conf index 871f2ffb..70202ba4 100644 --- a/config/hypr/UserConfigs/UserKeybinds.conf +++ b/config/hypr/UserConfigs/UserKeybinds.conf @@ -12,13 +12,14 @@ $UserScripts = $HOME/.config/hypr/UserScripts $UserConfigs = $HOME/.config/hypr/UserConfigs # settings for User defaults apps - set your default terminal and file manager on this file -source= $UserConfigs/01-UserDefaults.conf +source= $UserConfigs/01-UserDefaults.conf # common shortcuts #bindr = $mainMod, $mainMod_L, exec, pkill rofi || rofi -show drun -modi drun,filebrowser,run,window # Super Key to Launch rofi menu bind = $mainMod, D, exec, pkill rofi || true && rofi -show drun -modi drun,filebrowser,run,window # Main Menu (APP Launcher) bind = $mainMod, B, exec, xdg-open "https://" # default browser -bind = $mainMod, A, exec, pkill rofi || true && ags -t 'overview' # desktop overview (if installed) +#bind = $mainMod, A, exec, pkill rofi || true && ags -t 'overview' # desktop overview (if installed) +#bind = $mainMod, A, global, quickshell:overviewToggle # desktop overview (if installed) bind = $mainMod, Return, exec, $term #terminal bind = $mainMod, E, exec, $files #file manager @@ -39,7 +40,7 @@ bind = $mainMod SHIFT, F, fullscreen # whole full screen bind = $mainMod CTRL, F, fullscreen, 1 # fake full screen bind = $mainMod, SPACE, togglefloating, #Float Mode bind = $mainMod ALT, SPACE, exec, hyprctl dispatch workspaceopt allfloat #All Float Mode -bind = $mainMod SHIFT, Return, exec, [float; move 15% 5%; size 70% 60%] $term # Dropdown terminal +bind = $mainMod SHIFT, Return, exec, $scriptsDir/Dropterminal.sh $term # Dropdown terminal # Desktop zooming or magnifier bind = $mainMod ALT, mouse_down, exec, hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')" diff --git a/config/hypr/UserConfigs/UserSettings.conf b/config/hypr/UserConfigs/UserSettings.conf index 29dbc572..067a3810 100644 --- a/config/hypr/UserConfigs/UserSettings.conf +++ b/config/hypr/UserConfigs/UserSettings.conf @@ -8,8 +8,9 @@ # NOTE: some settings are in ~/.config/hypr/UserConfigs/UserDecorAnimations.conf dwindle { - pseudotile = yes - preserve_split = yes + pseudotile = true + preserve_split = true + #smart_split = true special_scale_factor = 0.8 } @@ -48,7 +49,7 @@ input { middle_button_emulation = true tap-to-click = true drag_lock = false - } + } # below for devices with touchdevice ie. touchscreen touchdevice { @@ -104,8 +105,6 @@ xwayland { } render { - #explicit_sync = 2 - #explicit_sync_kms = 2 direct_scanout = 0 } diff --git a/config/hypr/UserConfigs/WindowRules.conf b/config/hypr/UserConfigs/WindowRules.conf index 661b3a17..e5fd11b9 100644 --- a/config/hypr/UserConfigs/WindowRules.conf +++ b/config/hypr/UserConfigs/WindowRules.conf @@ -44,9 +44,10 @@ windowrule = tag +screenshare, class:^(com.obsproject.Studio)$ windowrule = tag +im, class:^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$ windowrule = tag +im, class:^([Ff]erdium)$ windowrule = tag +im, class:^([Ww]hatsapp-for-linux)$ -windowrule = tag +im, class:^(ZapZap|com.rtosta.zapzap)$ +windowrule = tag +im, class:^(ZapZap|com.rtosta.zapzap)$ windowrule = tag +im, class:^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$ windowrule = tag +im, class:^(teams-for-linux)$ +windowrule = tag +im, class:^(im.riot.Riot|Element)$ # Element Matrix client # game tags windowrule = tag +games, class:^(gamescope)$ @@ -177,7 +178,6 @@ windowrule = opacity 0.82 0.75, tag:viewer* windowrule = opacity 0.9 0.7, tag:wallpaper* windowrule = opacity 0.8 0.7, class:^(gedit|org.gnome.TextEditor|mousepad)$ windowrule = opacity 0.9 0.8, class:^(deluge)$ -windowrule = opacity 0.9 0.8, class:^(im.riot.Riot)$ # Element matrix client windowrule = opacity 0.9 0.8, class:^(seahorse)$ # gnome-keyring gui windowrule = opacity 0.95 0.75, title:^(Picture-in-Picture)$ @@ -211,6 +211,10 @@ layerrule = blur, rofi layerrule = ignorezero, rofi layerrule = blur, notifications layerrule = ignorezero, notifications +layerrule = blur, quickshell:overview +layerrule = ignorezero, quickshell:overview +layerrule = ignorealpha 0.5, quickshell:overview + #layerrule = ignorealpha 0.5, tag:notif* #layerrule = ignorezero, class:^([Rr]ofi)$ @@ -219,4 +223,4 @@ layerrule = ignorezero, notifications #layerrule = ignorezero, <rofi> #layerrule = ignorezero, overview -#layerrule = blur, overview
\ No newline at end of file +#layerrule = blur, overview diff --git a/config/hypr/UserScripts/WallpaperEffects.sh b/config/hypr/UserScripts/WallpaperEffects.sh index 3717543c..2ba58d0c 100755 --- a/config/hypr/UserScripts/WallpaperEffects.sh +++ b/config/hypr/UserScripts/WallpaperEffects.sh @@ -131,10 +131,8 @@ if [[ -n "$choice" ]]; then exit 1 fi - # Open terminal and set the wallpaper - $terminal -e bash -c "echo 'Enter your password to set wallpaper as SDDM Background'; \ - sudo cp -r $wallpaper_output '$sddm_simple/Backgrounds/default' && \ - notify-send -i '$iDIR/ja.png' 'SDDM' 'Background SET'" + exec $SCRIPTSDIR/sddm_wallpaper.sh --effects + fi fi -fi +fi
\ No newline at end of file diff --git a/config/hypr/UserScripts/WallpaperSelect.sh b/config/hypr/UserScripts/WallpaperSelect.sh index 47a8f2cd..a6e6c4d4 100755 --- a/config/hypr/UserScripts/WallpaperSelect.sh +++ b/config/hypr/UserScripts/WallpaperSelect.sh @@ -123,11 +123,9 @@ set_sddm_wallpaper() { notify-send -i "$iDIR/error.png" "Missing $terminal" "Install $terminal to enable setting of wallpaper background" exit 1 fi - - # Open terminal to enter password - $terminal -e bash -c "echo 'Enter your password to set wallpaper as SDDM Background'; \ - sudo cp -r $wallpaper_current '$sddm_simple/Backgrounds/default' && \ - notify-send -i '$iDIR/ja.png' 'SDDM' 'Background SET'" + + exec $SCRIPTSDIR/sddm_wallpaper.sh --normal + fi fi } @@ -235,4 +233,4 @@ if pidof rofi >/dev/null; then pkill rofi fi -main +main
\ No newline at end of file diff --git a/config/hypr/hyprlock-1080p.conf b/config/hypr/hyprlock-1080p.conf index a0e105e0..14f2f35e 100644 --- a/config/hypr/hyprlock-1080p.conf +++ b/config/hypr/hyprlock-1080p.conf @@ -156,6 +156,18 @@ label { valign = bottom } +# battery information +label { + monitor = + text = cmd[update:1000] echo "<b> "$($Scripts/Battery.sh)" </b>" + color = $color13 + font_size = 16 + font_family = Victor Mono Bold Oblique + position = 0, 30 + halign = right + valign = bottom +} + # weather edit the scripts for locations # weather scripts are located in ~/.config/hypr/UserScripts Weather.sh and/or Weather.py # see https://github.com/JaKooLit/Hyprland-Dots/wiki/TIPS#%EF%B8%8F-weather-app-related-for-waybar-and-hyprlock diff --git a/config/hypr/hyprlock.conf b/config/hypr/hyprlock.conf index 48b09b85..b67bba51 100644 --- a/config/hypr/hyprlock.conf +++ b/config/hypr/hyprlock.conf @@ -157,6 +157,18 @@ label { valign = bottom } +# battery information +label { + monitor = + text = cmd[update:1000] echo "<b> "$($Scripts/Battery.sh)" </b>" + color = $color13 + font_size = 18 + font_family = Victor Mono Bold Oblique + position = 0, 30 + halign = right + valign = bottom +} + # weather edit the scripts for locations # weather scripts are located in ~/.config/hypr/UserScripts Weather.sh and/or Weather.py # see https://github.com/JaKooLit/Hyprland-Dots/wiki/TIPS#%EF%B8%8F-weather-app-related-for-waybar-and-hyprlock diff --git a/config/hypr/scripts/Battery.sh b/config/hypr/scripts/Battery.sh new file mode 100644 index 00000000..d7830058 --- /dev/null +++ b/config/hypr/scripts/Battery.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +for i in {0..3}; do + if [ -f /sys/class/power_supply/BAT$i/capacity ]; then + battery_level=$(cat /sys/class/power_supply/BAT$i/status) + battery_capacity=$(cat /sys/class/power_supply/BAT$i/capacity) + echo "Battery: $battery_capacity% ($battery_level)" + fi +done diff --git a/config/hypr/scripts/Brightness.sh b/config/hypr/scripts/Brightness.sh index 8e5d525a..63fd02f3 100755 --- a/config/hypr/scripts/Brightness.sh +++ b/config/hypr/scripts/Brightness.sh @@ -6,69 +6,64 @@ iDIR="$HOME/.config/swaync/icons" notification_timeout=1000 step=10 # INCREASE/DECREASE BY THIS VALUE -# Get brightness -get_backlight() { - brightnessctl -m | cut -d, -f4 | sed 's/%//' +# Get current brightness as an integer (without %) +get_brightness() { + brightnessctl -m | cut -d, -f4 | tr -d '%' } -# Get icons -get_icon() { - current=$(get_backlight) - if [ "$current" -le "20" ]; then - icon="$iDIR/brightness-20.png" - elif [ "$current" -le "40" ]; then - icon="$iDIR/brightness-40.png" - elif [ "$current" -le "60" ]; then - icon="$iDIR/brightness-60.png" - elif [ "$current" -le "80" ]; then - icon="$iDIR/brightness-80.png" - else - icon="$iDIR/brightness-100.png" - fi +# Determine the icon based on brightness level +get_icon_path() { + local brightness=$1 + local level=$(( (brightness + 19) / 20 * 20 )) # Round up to next 20 + if (( level > 100 )); then + level=100 + fi + echo "$iDIR/brightness-${level}.png" } -# Notify -notify_user() { - notify-send -e -h string:x-canonical-private-synchronous:brightness_notif -h int:value:$current -u low -i $icon "Screen" "Brightness:$current%" +# Send notification +send_notification() { + local brightness=$1 + local icon_path=$2 + + notify-send -e \ + -h string:x-canonical-private-synchronous:brightness_notif \ + -h int:value:"$brightness" \ + -u low \ + -i "$icon_path" \ + "Screen" "Brightness: ${brightness}%" } -# Change brightness -change_backlight() { - local current_brightness - current_brightness=$(get_backlight) +# Change brightness and notify +change_brightness() { + local delta=$1 + local current new icon + + current=$(get_brightness) + new=$((current + delta)) - # Calculate new brightness - if [[ "$1" == "+${step}%" ]]; then - new_brightness=$((current_brightness + step)) - elif [[ "$1" == "${step}%-" ]]; then - new_brightness=$((current_brightness - step)) - fi + # Clamp between 5 and 100 + (( new < 5 )) && new=5 + (( new > 100 )) && new=100 - # Ensure new brightness is within valid range - if (( new_brightness < 5 )); then - new_brightness=5 - elif (( new_brightness > 100 )); then - new_brightness=100 - fi + brightnessctl set "${new}%" - brightnessctl set "${new_brightness}%" - get_icon - current=$new_brightness - notify_user + icon=$(get_icon_path "$new") + send_notification "$new" "$icon" } -# Execute accordingly +# Main case "$1" in - "--get") - get_backlight - ;; - "--inc") - change_backlight "+${step}%" - ;; - "--dec") - change_backlight "${step}%-" - ;; - *) - get_backlight - ;; -esac + "--get") + get_brightness + ;; + "--inc") + change_brightness "$step" + ;; + "--dec") + change_brightness "-$step" + ;; + *) + get_brightness + ;; +esac
\ No newline at end of file diff --git a/config/hypr/scripts/BrightnessKbd.sh b/config/hypr/scripts/BrightnessKbd.sh index 4c56bc03..24737b73 100755 --- a/config/hypr/scripts/BrightnessKbd.sh +++ b/config/hypr/scripts/BrightnessKbd.sh @@ -26,7 +26,7 @@ get_icon() { } # Notify notify_user() { - notify-send -e -h string:x-canonical-private-synchronous:brightness_notif -h int:value:$current -u low -i "$icon" "Keyboard" "Brightness:$current%" + notify-send -e -h string:x-canonical-private-synchronous:brightness_notif -h int:value:$current -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$icon" "Keyboard" "Brightness:$current%" } # Change brightness diff --git a/config/hypr/scripts/Dropterminal.sh b/config/hypr/scripts/Dropterminal.sh new file mode 100755 index 00000000..fa5b899b --- /dev/null +++ b/config/hypr/scripts/Dropterminal.sh @@ -0,0 +1,293 @@ +#!/bin/bash +# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ ## +# Dropdown Terminal +# Usage: ./Dropdown.sh [-d] <terminal_command> +# Example: ./Dropdown.sh foot +# ./Dropdown.sh -d foot (with debug output) +# ./Dropdown.sh "kitty -e zsh" +# ./Dropdown.sh "alacritty --working-directory /home/user" + +DEBUG=false +SPECIAL_WS="special:scratchpad" +ADDR_FILE="/tmp/dropdown_terminal_addr" + +# Dropdown size and position configuration (percentages) +WIDTH_PERCENT=50 # Width as percentage of screen width +HEIGHT_PERCENT=50 # Height as percentage of screen height +X_PERCENT=25 # X position as percentage from left (25% centers a 50% width window) +Y_PERCENT=5 # Y position as percentage from top + +# Animation settings +ANIMATION_DURATION=100 # milliseconds +SLIDE_STEPS=5 +SLIDE_DELAY=5 # milliseconds between steps + +# Parse arguments +if [ "$1" = "-d" ]; then + DEBUG=true + shift +fi + +TERMINAL_CMD="$1" + +# Debug echo function +debug_echo() { + if [ "$DEBUG" = true ]; then + echo "$@" + fi +} + +# Validate input +if [ -z "$TERMINAL_CMD" ]; then + echo "Missing terminal command. Usage: $0 [-d] <terminal_command>" + echo "Examples:" + echo " $0 foot" + echo " $0 -d foot (with debug output)" + echo " $0 'kitty -e zsh'" + echo " $0 'alacritty --working-directory /home/user'" + echo "" + echo "Edit the script to modify size and position:" + echo " WIDTH_PERCENT - Width as percentage of screen (default: 50)" + echo " HEIGHT_PERCENT - Height as percentage of screen (default: 50)" + echo " X_PERCENT - X position from left as percentage (default: 25)" + echo " Y_PERCENT - Y position from top as percentage (default: 5)" + exit 1 +fi + +# Function to get window geometry +get_window_geometry() { + local addr="$1" + hyprctl clients -j | jq -r --arg ADDR "$addr" '.[] | select(.address == $ADDR) | "\(.at[0]) \(.at[1]) \(.size[0]) \(.size[1])"' +} + +# Function to animate window slide down (show) +animate_slide_down() { + local addr="$1" + local target_x="$2" + local target_y="$3" + local width="$4" + local height="$5" + + debug_echo "Animating slide down for window $addr to position $target_x,$target_y" + + # Start position (above screen) + local start_y=$((target_y - height - 50)) + + # Calculate step size + local step_y=$(((target_y - start_y) / SLIDE_STEPS)) + + # Move window to start position instantly (off-screen) + hyprctl dispatch movewindowpixel "exact $target_x $start_y,address:$addr" >/dev/null 2>&1 + sleep 0.05 + + # Animate slide down + for i in $(seq 1 $SLIDE_STEPS); do + local current_y=$((start_y + (step_y * i))) + hyprctl dispatch movewindowpixel "exact $target_x $current_y,address:$addr" >/dev/null 2>&1 + sleep 0.03 + done + + # Ensure final position is exact + hyprctl dispatch movewindowpixel "exact $target_x $target_y,address:$addr" >/dev/null 2>&1 +} + +# Function to animate window slide up (hide) +animate_slide_up() { + local addr="$1" + local start_x="$2" + local start_y="$3" + local width="$4" + local height="$5" + + debug_echo "Animating slide up for window $addr from position $start_x,$start_y" + + # End position (above screen) + local end_y=$((start_y - height - 50)) + + # Calculate step size + local step_y=$(((start_y - end_y) / SLIDE_STEPS)) + + # Animate slide up + for i in $(seq 1 $SLIDE_STEPS); do + local current_y=$((start_y - (step_y * i))) + hyprctl dispatch movewindowpixel "exact $start_x $current_y,address:$addr" >/dev/null 2>&1 + sleep 0.03 + done + + debug_echo "Slide up animation completed" +} + +# Function to get monitor info for centering +get_monitor_info() { + hyprctl monitors -j | jq -r '.[0] | "\(.x) \(.y) \(.width) \(.height)"' +} + +# Function to calculate dropdown position +calculate_dropdown_position() { + local monitor_info=$(get_monitor_info) + local mon_x=$(echo $monitor_info | cut -d' ' -f1) + local mon_y=$(echo $monitor_info | cut -d' ' -f2) + local mon_width=$(echo $monitor_info | cut -d' ' -f3) + local mon_height=$(echo $monitor_info | cut -d' ' -f4) + + # Calculate position and size based on percentages + local width=$((mon_width * WIDTH_PERCENT / 100)) + local height=$((mon_height * HEIGHT_PERCENT / 100)) + local x=$((mon_x + (mon_width * X_PERCENT / 100))) + local y=$((mon_y + (mon_height * Y_PERCENT / 100))) + + echo "$x $y $width $height" +} + +# Get the current workspace +CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id') + +# Function to get stored terminal address +get_terminal_address() { + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + cat "$ADDR_FILE" + fi +} + +# Function to check if terminal exists +terminal_exists() { + local addr=$(get_terminal_address) + if [ -n "$addr" ]; then + hyprctl clients -j | jq -e --arg ADDR "$addr" 'any(.[]; .address == $ADDR)' >/dev/null 2>&1 + else + return 1 + fi +} + +# Function to check if terminal is in special workspace +terminal_in_special() { + local addr=$(get_terminal_address) + if [ -n "$addr" ]; then + hyprctl clients -j | jq -e --arg ADDR "$addr" 'any(.[]; .address == $ADDR and .workspace.name == "special:scratchpad")' >/dev/null 2>&1 + else + return 1 + fi +} + +# Function to spawn terminal and capture its address +spawn_terminal() { + debug_echo "Creating new dropdown terminal with command: $TERMINAL_CMD" + + # Calculate dropdown position for later use + pos_info=$(calculate_dropdown_position) + target_x=$(echo $pos_info | cut -d' ' -f1) + target_y=$(echo $pos_info | cut -d' ' -f2) + width=$(echo $pos_info | cut -d' ' -f3) + height=$(echo $pos_info | cut -d' ' -f4) + + debug_echo "Target position: ${target_x}x${target_y}, size: ${width}x${height}" + + # Get window count before spawning + windows_before=$(hyprctl clients -j) + count_before=$(echo "$windows_before" | jq 'length') + + # Launch terminal directly in special workspace to avoid visible spawn + hyprctl dispatch exec "[float; size $width $height; workspace special:scratchpad silent] $TERMINAL_CMD" + + # Wait for window to appear + sleep 0.1 + + # Get windows after spawning + windows_after=$(hyprctl clients -j) + count_after=$(echo "$windows_after" | jq 'length') + + new_addr="" + + if [ "$count_after" -gt "$count_before" ]; then + # Find the new window by comparing before/after lists + new_addr=$(comm -13 \ + <(echo "$windows_before" | jq -r '.[].address' | sort) \ + <(echo "$windows_after" | jq -r '.[].address' | sort) \ + | head -1) + fi + + # Fallback: try to find by the most recently mapped window + if [ -z "$new_addr" ] || [ "$new_addr" = "null" ]; then + new_addr=$(hyprctl clients -j | jq -r 'sort_by(.focusHistoryID) | .[-1] | .address') + fi + + if [ -n "$new_addr" ] && [ "$new_addr" != "null" ]; then + # Store the address + echo "$new_addr" > "$ADDR_FILE" + debug_echo "Terminal created with address: $new_addr in special workspace" + + # Small delay to ensure it's properly in special workspace + sleep 0.2 + + # Now bring it back with the same animation as subsequent shows + # Use movetoworkspacesilent to avoid affecting workspace history + hyprctl dispatch movetoworkspacesilent "$CURRENT_WS,address:$new_addr" + hyprctl dispatch pin "address:$new_addr" + animate_slide_down "$new_addr" "$target_x" "$target_y" "$width" "$height" + + return 0 + fi + + debug_echo "Failed to get terminal address" + return 1 +} + +# Main logic +if terminal_exists; then + TERMINAL_ADDR=$(get_terminal_address) + debug_echo "Found existing terminal: $TERMINAL_ADDR" + + if terminal_in_special; then + debug_echo "Bringing terminal from scratchpad with slide down animation" + + # Calculate target position + pos_info=$(calculate_dropdown_position) + target_x=$(echo $pos_info | cut -d' ' -f1) + target_y=$(echo $pos_info | cut -d' ' -f2) + width=$(echo $pos_info | cut -d' ' -f3) + height=$(echo $pos_info | cut -d' ' -f4) + + # Use movetoworkspacesilent to avoid affecting workspace history + hyprctl dispatch movetoworkspacesilent "$CURRENT_WS,address:$TERMINAL_ADDR" + hyprctl dispatch pin "address:$TERMINAL_ADDR" + + # Set size and animate slide down + hyprctl dispatch resizewindowpixel "exact $width $height,address:$TERMINAL_ADDR" + animate_slide_down "$TERMINAL_ADDR" "$target_x" "$target_y" "$width" "$height" + + hyprctl dispatch focuswindow "address:$TERMINAL_ADDR" + else + debug_echo "Hiding terminal to scratchpad with slide up animation" + + # Get current geometry for animation + geometry=$(get_window_geometry "$TERMINAL_ADDR") + if [ -n "$geometry" ]; then + curr_x=$(echo $geometry | cut -d' ' -f1) + curr_y=$(echo $geometry | cut -d' ' -f2) + curr_width=$(echo $geometry | cut -d' ' -f3) + curr_height=$(echo $geometry | cut -d' ' -f4) + + debug_echo "Current geometry: ${curr_x},${curr_y} ${curr_width}x${curr_height}" + + # Animate slide up first + animate_slide_up "$TERMINAL_ADDR" "$curr_x" "$curr_y" "$curr_width" "$curr_height" + + # Small delay then move to special workspace and unpin + sleep 0.1 + hyprctl dispatch pin "address:$TERMINAL_ADDR" # Unpin (toggle) + hyprctl dispatch movetoworkspacesilent "$SPECIAL_WS,address:$TERMINAL_ADDR" + else + debug_echo "Could not get window geometry, moving to scratchpad without animation" + hyprctl dispatch pin "address:$TERMINAL_ADDR" + hyprctl dispatch movetoworkspacesilent "$SPECIAL_WS,address:$TERMINAL_ADDR" + fi + fi +else + debug_echo "No existing terminal found, creating new one" + if spawn_terminal; then + TERMINAL_ADDR=$(get_terminal_address) + if [ -n "$TERMINAL_ADDR" ]; then + hyprctl dispatch focuswindow "address:$TERMINAL_ADDR" + fi + fi +fi
\ No newline at end of file diff --git a/config/hypr/scripts/Refresh.sh b/config/hypr/scripts/Refresh.sh index 2d7887a5..d04570b1 100755 --- a/config/hypr/scripts/Refresh.sh +++ b/config/hypr/scripts/Refresh.sh @@ -25,8 +25,8 @@ done # added since wallust sometimes not applying killall -SIGUSR2 waybar -# quit ags & relaunch ags -#ags -q && ags & +# quit quickshell & relaunch quickshell +#pkill qs && qs & # some process to kill for pid in $(pidof waybar rofi swaync ags swaybg); do diff --git a/config/hypr/scripts/RefreshNoWaybar.sh b/config/hypr/scripts/RefreshNoWaybar.sh index e5a0835e..cdbb82db 100755 --- a/config/hypr/scripts/RefreshNoWaybar.sh +++ b/config/hypr/scripts/RefreshNoWaybar.sh @@ -26,7 +26,7 @@ for _prs in "${_ps[@]}"; do done # quit ags & relaunch ags -#ags -q && ags & +#pkill qs && qs & # Wallust refresh ${SCRIPTSDIR}/WallustSwww.sh & diff --git a/config/hypr/scripts/RofiThemeSelector.sh b/config/hypr/scripts/RofiThemeSelector.sh index 6fd8a6f8..8b2fcb71 100755 --- a/config/hypr/scripts/RofiThemeSelector.sh +++ b/config/hypr/scripts/RofiThemeSelector.sh @@ -1,75 +1,154 @@ #!/bin/bash -# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ ## -# Script for adding a selected theme to the Rofi config +# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ # +# Rofi Themes - Script to preview and apply themes by live-reloading the config. -IFS=$'\n\t' +# --- Configuration --- +ROFI_THEMES_DIR_CONFIG="$HOME/.config/rofi/themes" +ROFI_THEMES_DIR_LOCAL="$HOME/.local/share/rofi/themes" +ROFI_CONFIG_FILE="$HOME/.config/rofi/config.rasi" +ROFI_THEME_FOR_THIS_SCRIPT="$HOME/.config/rofi/config-rofi-theme.rasi" # A separate rofi theme for the picker itself +IDIR="$HOME/.config/swaync/images" # For notifications -# Define directories and variables -rofi_theme_dir="$HOME/.config/rofi/themes" -rofi_config_file="$HOME/.config/rofi/config.rasi" -SED=$(which sed) -iDIR="$HOME/.config/swaync/images" -rofi_theme="$HOME/.config/rofi/config-rofi-theme.rasi" +# --- Helper Functions --- -# Function to display menu options -menu() { - options=() - while IFS= read -r file; do - options+=("$file") - done < <(find -L "$rofi_theme_dir" -maxdepth 1 -type f -exec basename {} \; | sort -V) - - printf '%s\n' "${options[@]}" +# Function to send a notification +notify_user() { + notify-send -u low -i "$1" "$2" "$3" } -# Function to add or update theme in the config.rasi -add_theme_to_config() { - local theme_name="$1" - local theme_path="$rofi_theme_dir/$theme_name" - - # if config in $HOME to write as $HOME - if [[ "$theme_path" == $HOME/* ]]; then - theme_path_with_tilde="~${theme_path#$HOME}" - else - theme_path_with_tilde="$theme_path" - fi +# Function to apply the selected rofi theme to the main config file +apply_rofi_theme_to_config() { + local theme_name_to_apply="$1" - # If no @theme is in the file, add it - if ! grep -q '^\s*@theme' "$rofi_config_file"; then - echo -e "\n\n@theme \"$theme_path_with_tilde\"" >> "$rofi_config_file" - echo "Added @theme \"$theme_path_with_tilde\" to $rofi_config_file" - else - $SED -i "s/^\(\s*@theme.*\)/\/\/\1/" "$rofi_config_file" - echo -e "@theme \"$theme_path_with_tilde\"" >> "$rofi_config_file" - echo "Updated @theme line to $theme_path_with_tilde" - fi + # Find the full path of the theme file + local theme_path + if [[ -f "$ROFI_THEMES_DIR_CONFIG/$theme_name_to_apply" ]]; then + theme_path="$ROFI_THEMES_DIR_CONFIG/$theme_name_to_apply" + elif [[ -f "$ROFI_THEMES_DIR_LOCAL/$theme_name_to_apply" ]]; then + theme_path="$ROFI_THEMES_DIR_LOCAL/$theme_name_to_apply" + else + notify_user "$IDIR/error.png" "Error" "Theme file not found: $theme_name_to_apply" + return 1 + fi - # Ensure no more than max # of lines with //@theme lines - max_line="9" - total_lines=$(grep -c '^\s*//@theme' "$rofi_config_file") + # Use ~ for the home directory in the config path + local theme_path_with_tilde="~${theme_path#$HOME}" - if [ "$total_lines" -gt "$max_line" ]; then - excess=$((total_lines - max_line)) - # Remove the oldest or the very top //@theme lines - for i in $(seq 1 "$excess"); do - $SED -i '0,/^\s*\/\/@theme/ { /^\s*\/\/@theme/ {d; q; }}' "$rofi_config_file" - done - echo "Removed excess //@theme lines" - fi -} + # Create a temporary file to safely edit the config + local temp_rofi_config_file + temp_rofi_config_file=$(mktemp) + cp "$ROFI_CONFIG_FILE" "$temp_rofi_config_file" -# Main function -main() { - choice=$(menu | rofi rofi -dmenu -i -config $rofi_theme) + # Comment out any existing @theme entry + sed -i -E 's/^(\s*@theme)/\\/\\/\1/' "$temp_rofi_config_file" - if [[ -z "$choice" ]]; then - exit 0 - fi - add_theme_to_config "$choice" - notify-send -i "$iDIR/ja.png" -u low 'Rofi Theme applied:' "$choice" + # Add the new @theme entry at the end of the file + echo "@theme \"$theme_path_with_tilde\"" >>"$temp_rofi_config_file" + + # Overwrite the original config file + cp "$temp_rofi_config_file" "$ROFI_CONFIG_FILE" + rm "$temp_rofi_config_file" + + # Prune old commented-out theme lines to prevent clutter + local max_lines=10 + local total_lines=$(grep -c '^//\s*@theme' "$ROFI_CONFIG_FILE") + if [ "$total_lines" -gt "$max_lines" ]; then + local excess=$((total_lines - max_lines)) + for ((i = 1; i <= excess; i++)); do + sed -i '0,/^\s*\/\/@theme/s///' "$ROFI_CONFIG_FILE" + done + fi + + return 0 } -if pgrep -x "rofi" >/dev/null; then - pkill rofi +# --- Main Script Execution --- + +# Check for required directories and files +if [ ! -d "$ROFI_THEMES_DIR_CONFIG" ] && [ ! -d "$ROFI_THEMES_DIR_LOCAL" ]; then + notify_user "$IDIR/error.png" "E-R-R-O-R" "No Rofi themes directory found." + exit 1 +fi + +if [ ! -f "$ROFI_CONFIG_FILE" ]; then + notify_user "$IDIR/error.png" "E-R-R-O-R" "Rofi config file not found: $ROFI_CONFIG_FILE" + exit 1 +fi + +# Backup the original config content +original_rofi_config_content_backup=$(cat "$ROFI_CONFIG_FILE") + +# Generate a sorted list of available theme file names +mapfile -t available_theme_names < <(( + find "$ROFI_THEMES_DIR_CONFIG" -maxdepth 1 -name "*.rasi" -type f -printf "%f\n" 2>/dev/null + find "$ROFI_THEMES_DIR_LOCAL" -maxdepth 1 -name "*.rasi" -type f -printf "%f\n" 2>/dev/null +) | sort -V -u) + +if [ ${#available_theme_names[@]} -eq 0 ]; then + notify_user "$IDIR/error.png" "No Rofi Themes" "No .rasi files found in theme directories." + exit 1 fi -main +# Find the currently active theme to set as the initial selection +current_selection_index=0 +current_active_theme_path=$(grep -oP '^\s*@theme\s*"\K[^"]+' "$ROFI_CONFIG_FILE" | tail -n 1) +if [ -n "$current_active_theme_path" ]; then + current_active_theme_name=$(basename "$current_active_theme_path") + for i in "${!available_theme_names[@]}"; do + if [[ "${available_theme_names[$i]}" == "$current_active_theme_name" ]]; then + current_selection_index=$i + break + fi + done +fi + +# Main preview loop +while true; do + theme_to_preview_now="${available_theme_names[$current_selection_index]}" + + # Apply the theme for preview + if ! apply_rofi_theme_to_config "$theme_to_preview_now"; then + echo "$original_rofi_config_content_backup" >"$ROFI_CONFIG_FILE" + notify_user "$IDIR/error.png" "Preview Error" "Failed to apply $theme_to_preview_now. Reverted." + exit 1 + fi + + # Prepare theme list for Rofi + rofi_input_list="" + for theme_name_in_list in "${available_theme_names[@]}"; do + rofi_input_list+="$(basename "$theme_name_in_list" .rasi)\n" + done + rofi_input_list_trimmed="${rofi_input_list%\\n}" + + # Launch Rofi and get user's choice + chosen_index_from_rofi=$(echo -e "$rofi_input_list_trimmed" | + rofi -dmenu -i \ + -format 'i' \ + -p "Rofi Theme" \ + -mesg "‼️ **note** ‼️ Enter: Preview || Ctrl+S: Apply & Exit || Esc: Cancel" \ + -config "$ROFI_THEME_FOR_THIS_SCRIPT" \ + -selected-row "$current_selection_index" \ + -kb-custom-1 "Control+s") + + rofi_exit_code=$? + + # Handle Rofi's exit code + if [ $rofi_exit_code -eq 0 ]; then # Enter + if [[ "$chosen_index_from_rofi" =~ ^[0-9]+$ ]] && [ "$chosen_index_from_rofi" -lt "${#available_theme_names[@]}" ]; then + current_selection_index="$chosen_index_from_rofi" + fi + elif [ $rofi_exit_code -eq 1 ]; then # Escape + notify_user "$IDIR/note.png" "Rofi Theme" "Selection cancelled. Reverting to original theme." + echo "$original_rofi_config_content_backup" >"$ROFI_CONFIG_FILE" + break + elif [ $rofi_exit_code -eq 10 ]; then # Custom bind 1 (Ctrl+S) + notify_user "$IDIR/ja.png" "Rofi Theme Applied" "$(basename "$theme_to_preview_now" .rasi)" + break + else # Error or unexpected exit code + notify_user "$IDIR/error.png" "Rofi Error" "Unexpected Rofi exit ($rofi_exit_code). Reverting." + echo "$original_rofi_config_content_backup" >"$ROFI_CONFIG_FILE" + break + fi +done + +exit 0 diff --git a/config/hypr/scripts/Volume.sh b/config/hypr/scripts/Volume.sh index b205f8f9..8efdb55c 100755 --- a/config/hypr/scripts/Volume.sh +++ b/config/hypr/scripts/Volume.sh @@ -32,9 +32,9 @@ get_icon() { # Notify notify_user() { if [[ "$(get_volume)" == "Muted" ]]; then - notify-send -e -h string:x-canonical-private-synchronous:volume_notif -u low -i "$(get_icon)" " Volume:" " Muted" + notify-send -e -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume:" " Muted" else - notify-send -e -h int:value:"$(get_volume | sed 's/%//')" -h string:x-canonical-private-synchronous:volume_notif -u low -i "$(get_icon)" " Volume Level:" " $(get_volume)" && + notify-send -e -h int:value:"$(get_volume | sed 's/%//')" -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume Level:" " $(get_volume)" && "$sDIR/Sounds.sh" --volume fi } @@ -60,18 +60,18 @@ dec_volume() { # Toggle Mute toggle_mute() { if [ "$(pamixer --get-mute)" == "false" ]; then - pamixer -m && notify-send -e -u low -i "$iDIR/volume-mute.png" " Mute" + pamixer -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/volume-mute.png" " Mute" elif [ "$(pamixer --get-mute)" == "true" ]; then - pamixer -u && notify-send -e -u low -i "$(get_icon)" " Volume:" " Switched ON" + pamixer -u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$(get_icon)" " Volume:" " Switched ON" fi } # Toggle Mic toggle_mic() { if [ "$(pamixer --default-source --get-mute)" == "false" ]; then - pamixer --default-source -m && notify-send -e -u low -i "$iDIR/microphone-mute.png" " Microphone:" " Switched OFF" + pamixer --default-source -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone-mute.png" " Microphone:" " Switched OFF" elif [ "$(pamixer --default-source --get-mute)" == "true" ]; then - pamixer -u --default-source u && notify-send -e -u low -i "$iDIR/microphone.png" " Microphone:" " Switched ON" + pamixer -u --default-source u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone.png" " Microphone:" " Switched ON" fi } # Get Mic Icon @@ -98,7 +98,7 @@ get_mic_volume() { notify_mic_user() { volume=$(get_mic_volume) icon=$(get_mic_icon) - notify-send -e -h int:value:"$volume" -h "string:x-canonical-private-synchronous:volume_notif" -u low -i "$icon" " Mic Level:" " $volume" + notify-send -e -h int:value:"$volume" -h "string:x-canonical-private-synchronous:volume_notif" -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$icon" " Mic Level:" " $volume" } # Increase MIC Volume diff --git a/config/hypr/scripts/WaybarLayout.sh b/config/hypr/scripts/WaybarLayout.sh index 955432fc..b4d9c493 100755 --- a/config/hypr/scripts/WaybarLayout.sh +++ b/config/hypr/scripts/WaybarLayout.sh @@ -29,9 +29,10 @@ main() { # Mark and locate the active layout default_row=0 + MARKER="👉" for i in "${!options[@]}"; do if [[ "${options[i]}" == "$current_name" ]]; then - options[i]="👉 ${options[i]}" + options[i]="$MARKER ${options[i]}" default_row=$i break fi @@ -49,7 +50,7 @@ main() { [[ -z "$choice" ]] && { echo "No option selected. Exiting."; exit 0; } # Strip marker before applying - choice=${choice% ⮕} + choice=${choice# $MARKER} case "$choice" in "no panel") diff --git a/config/hypr/scripts/WaybarStyles.sh b/config/hypr/scripts/WaybarStyles.sh index 20f0873b..a439f8eb 100755 --- a/config/hypr/scripts/WaybarStyles.sh +++ b/config/hypr/scripts/WaybarStyles.sh @@ -31,9 +31,10 @@ main() { # mark the active style and record its index default_row=0 + MARKER="👉" for i in "${!options[@]}"; do if [[ "${options[i]}" == "$current_name" ]]; then - options[i]="👉 ${options[i]}" + options[i]="$MARKER ${options[i]}" default_row=$i break fi @@ -50,7 +51,7 @@ main() { [[ -z "$choice" ]] && { echo "No option selected. Exiting."; exit 0; } # remove annotation and apply - choice=${choice% ⮕} + choice=${choice# $MARKER} apply_style "$choice" } diff --git a/config/hypr/scripts/sddm_wallpaper-v2.sh b/config/hypr/scripts/sddm_wallpaper-v2.sh new file mode 100644 index 00000000..a781156e --- /dev/null +++ b/config/hypr/scripts/sddm_wallpaper-v2.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ +# SDDM Wallpaper and Wallust Colors Setter + +# for the upcoming changes on the simple_sddm_theme + +# variables +terminal=kitty +wallDIR="$HOME/Pictures/wallpapers" +SCRIPTSDIR="$HOME/.config/hypr/scripts" +wallpaper_current="$HOME/.config/hypr/wallpaper_effects/.wallpaper_current" +wallpaper_modified="$HOME/.config/hypr/wallpaper_effects/.wallpaper_modified" +sddm_simple="/usr/share/sddm/themes/simple_sddm_2" + +# rofi-wallust-sddm colors path +rofi_wallust="$HOME/.config/rofi/wallust/colors-rofi.rasi" +sddm_theme_conf="$sddm_simple/theme.conf" + +# Directory for swaync +iDIR="$HOME/.config/swaync/images" +iDIRi="$HOME/.config/swaync/icons" + +# Parse arguments +mode="effects" # default +if [[ "$1" == "--normal" ]]; then + mode="normal" +elif [[ "$1" == "--effects" ]]; then + mode="effects" +fi + +# Extract colors from rofi wallust config + +color0=$(grep -oP 'color1:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +color1=$(grep -oP 'color0:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +color7=$(grep -oP 'color14:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +color10=$(grep -oP 'color10:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +color12=$(grep -oP 'color12:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +color13=$(grep -oP 'color13:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +foreground=$(grep -oP 'foreground:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +#background-color=$(grep -oP 'background:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") + +# wallpaper to use +if [[ "$mode" == "normal" ]]; then + wallpaper_path="$wallpaper_current" +else + wallpaper_path="$wallpaper_modified" +fi + +# Launch terminal and apply changes +$terminal -e bash -c " +echo 'Enter your password to update SDDM wallpapers and colors'; + +# Update the colors in the SDDM config +sudo sed -i \"s/HeaderTextColor=\\\"#.*\\\"/HeaderTextColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/DateTextColor=\\\"#.*\\\"/DateTextColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/TimeTextColor=\\\"#.*\\\"/TimeTextColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/DropdownSelectedBackgroundColor=\\\"#.*\\\"/DropdownSelectedBackgroundColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/SystemButtonsIconsColor=\\\"#.*\\\"/SystemButtonsIconsColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/SessionButtonTextColor=\\\"#.*\\\"/SessionButtonTextColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/VirtualKeyboardButtonTextColor=\\\"#.*\\\"/VirtualKeyboardButtonTextColor=\\\"$color13\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/HighlightBackgroundColor=\\\"#.*\\\"/HighlightBackgroundColor=\\\"$color12\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/LoginFieldTextColor=\\\"#.*\\\"/LoginFieldTextColor=\\\"$color12\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/PasswordFieldTextColor=\\\"#.*\\\"/PasswordFieldTextColor=\\\"$color12\\\"/\" \"$sddm_theme_conf\" + +sudo sed -i \"s/DropdownBackgroundColor=\\\"#.*\\\"/DropdownBackgroundColor=\\\"$color1\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/HighlightTextColor=\\\"#.*\\\"/HighlightTextColor=\\\"$color10\\\"/\" \"$sddm_theme_conf\" + +sudo sed -i \"s/PlaceholderTextColor=\\\"#.*\\\"/PlaceholderTextColor=\\\"$color7\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/UserIconColor=\\\"#.*\\\"/UserIconColor=\\\"$color7\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/PasswordIconColor=\\\"#.*\\\"/PasswordIconColor=\\\"$color7\\\"/\" \"$sddm_theme_conf\" + +# Copy wallpaper to SDDM theme +sudo cp \"$wallpaper_path\" \"$sddm_simple/Backgrounds/default\" + +notify-send -i \"$iDIR/ja.png\" \"SDDM\" \"Background SET\" +"
\ No newline at end of file diff --git a/config/hypr/scripts/sddm_wallpaper.sh b/config/hypr/scripts/sddm_wallpaper.sh new file mode 100755 index 00000000..fd385fcd --- /dev/null +++ b/config/hypr/scripts/sddm_wallpaper.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ +# SDDM Wallpaper and Wallust Colors Setter + +# variables +terminal=kitty +wallDIR="$HOME/Pictures/wallpapers" +SCRIPTSDIR="$HOME/.config/hypr/scripts" +wallpaper_current="$HOME/.config/hypr/wallpaper_effects/.wallpaper_current" +wallpaper_modified="$HOME/.config/hypr/wallpaper_effects/.wallpaper_modified" +sddm_simple="/usr/share/sddm/themes/simple_sddm_2" + +# rofi-wallust-sddm colors path +rofi_wallust="$HOME/.config/rofi/wallust/colors-rofi.rasi" +sddm_theme_conf="$sddm_simple/theme.conf" + +# Directory for swaync +iDIR="$HOME/.config/swaync/images" +iDIRi="$HOME/.config/swaync/icons" + +# Parse arguments +mode="effects" # default +if [[ "$1" == "--normal" ]]; then + mode="normal" +elif [[ "$1" == "--effects" ]]; then + mode="effects" +fi + +# Extract colors from rofi wallust config +main_color=$(grep -oP 'color13:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +accent_color=$(grep -oP 'color12:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +bg_color=$(grep -oP 'color0:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") +placeholder_color="$accent_color" +icon_color=$(grep -oP 'foreground:\s*\K#[A-Fa-f0-9]+' "$rofi_wallust") + +# wallpaper to use +if [[ "$mode" == "normal" ]]; then + wallpaper_path="$wallpaper_current" +else + wallpaper_path="$wallpaper_modified" +fi + +# Launch terminal and apply changes +$terminal -e bash -c " +echo 'Enter your password to update SDDM wallpapers and colors'; +sudo sed -i \"s/MainColor=\\\"#.*\\\"/MainColor=\\\"$main_color\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/AccentColor=\\\"#.*\\\"/AccentColor=\\\"$accent_color\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/BackgroundColor=\\\"#.*\\\"/BackgroundColor=\\\"$bg_color\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/placeholderColor=\\\"#.*\\\"/placeholderColor=\\\"$placeholder_color\\\"/\" \"$sddm_theme_conf\" +sudo sed -i \"s/IconColor=\\\"#.*\\\"/IconColor=\\\"$icon_color\\\"/\" \"$sddm_theme_conf\" + +# Copy wallpaper +sudo cp \"$wallpaper_path\" \"$sddm_simple/Backgrounds/default\" + +notify-send -i \"$iDIR/ja.png\" \"SDDM\" \"Background SET\" +" diff --git a/config/hypr/v2.3.15 b/config/hypr/v2.3.16 index 31b3414d..31b3414d 100644 --- a/config/hypr/v2.3.15 +++ b/config/hypr/v2.3.16 diff --git a/config/quickshell/GlobalStates.qml b/config/quickshell/GlobalStates.qml new file mode 100644 index 00000000..84b53c03 --- /dev/null +++ b/config/quickshell/GlobalStates.qml @@ -0,0 +1,10 @@ +import "root:/modules/common/" +import QtQuick +import Quickshell +pragma Singleton +pragma ComponentBehavior: Bound + +Singleton { + id: root + property bool overviewOpen: false +}
\ No newline at end of file diff --git a/config/quickshell/config.json b/config/quickshell/config.json new file mode 100644 index 00000000..286985f5 --- /dev/null +++ b/config/quickshell/config.json @@ -0,0 +1,53 @@ +{ + "ui": { + "theme": "dark", + "scale": 1.25 + }, + "features": { + "animations": true + }, + "appearance": { + "fakeScreenRounding": 1 + }, + "overview": { + "scale": 0.15, + "numOfRows": 2, + "numOfCols": 5, + "showXwaylandIndicator": true, + "windowPadding": 6, + "position": 1, + "workspaceNumberSize": 220 + }, + "resources": { + "updateInterval": 3000 + }, + "hacks": { + "arbitraryRaceConditionDelay": 20 + }, + "search": { + "searchEnabled": true, + "nonAppResultDelay": 30, + "prefix": { + "action": "/", + "clipboard": ";", + "emojis": ":" + } + }, + "bar": { + "bottom": false + }, + "font": { + "family": { + "uiFont": "Open Sans", + "iconFont": "FiraConde Nerd Font", + "codeFont": "JetBrains Mono NF" + }, + "pixelSize": { + "textSmall": 13, + "textBase": 15, + "textMedium": 16, + "textLarge": 19, + "iconLarge": 22 + } + } +}
\ No newline at end of file diff --git a/config/quickshell/modules/common/Appearance.qml b/config/quickshell/modules/common/Appearance.qml new file mode 100644 index 00000000..675f1d1e --- /dev/null +++ b/config/quickshell/modules/common/Appearance.qml @@ -0,0 +1,201 @@ +import QtQuick +import Quickshell +import "root:/modules/common/functions/color_utils.js" as ColorUtils +pragma Singleton +pragma ComponentBehavior: Bound + + +Singleton { + id: root + property QtObject m3colors + property QtObject animation + property QtObject animationCurves + property QtObject colors + property QtObject rounding + property QtObject font + property QtObject sizes + + property real transparency: 0.5 + property real contentTransparency: 0.1 + property real workpaceTransparency: 0.8 + property string background_image: Directories.config + "/rofi/.current_wallpaper" + + m3colors: QtObject { + property bool darkmode: true + property bool transparent: true + + property color m3windowBackground: "#161217" + property color m3primaryText: "#EAE0E7" + property color m3layerBackground1: "#1F1A1F" + property color m3layerBackground2: "#231E23" + property color m3layerBackground3: "#2D282E" + property color m3surfaceText: "#EAE0E7" + property color m3secondaryText: "#CFC3CD" + property color m3borderPrimary: "#cba6f7" + property color m3shadowColor: "#000000" + property color m3accentPrimary: "#E5B6F2" + property color m3accentSecondary: "#D5C0D7" + property color m3selectionBackground: "#534457" + property color m3accentPrimaryText: "#452152" + property color m3selectionText: "#F2DCF3" + property color m3borderSecondary: "#4C444D" + + property color colTooltip: "#1e1e2e" + property color colOnTooltip: "#F8F9FA" + } + + colors: QtObject { + property color colSubtext: m3colors.m3borderPrimary + property color colLayer0: ColorUtils.transparentize(m3colors.m3windowBackground, root.transparency) + property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3layerBackground1, m3colors.m3windowBackground, 0.7), root.contentTransparency); + property color colOnLayer1: m3colors.m3secondaryText; + property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3layerBackground2, m3colors.m3layerBackground3, 0.55), root.contentTransparency) + property color colOnLayer2: m3colors.m3surfaceText; + property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency) + property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency); + property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency) + property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency); + property color colPrimary: m3colors.m3accentPrimary + property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) + property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7) + property color colShadow: ColorUtils.transparentize(m3colors.m3shadowColor, 0.7) + } + + rounding: QtObject { + property int unsharpen: 2 + property int verysmall: 8 + property int small: 12 + property int normal: 17 + property int large: 23 + property int verylarge: 30 + property int veryverylarge: 60 + property int full: 9999 + property int screenRounding: veryverylarge + property int windowRounding: veryverylarge + } + + font: QtObject { + property QtObject family: QtObject { + property string uiFont: "Open Sans" + property string iconFont: "FiraConde Nerd Font" + property string codeFont: "JetBrains Mono NF" + } + property QtObject pixelSize: QtObject { + property int textSmall: 13 + property int textBase: 15 + property int textMedium: 16 + property int textLarge: 19 + property int iconLarge: 22 + } + } + + animationCurves: QtObject { + readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms + readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms + readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms + readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms + readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] + readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1] + readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1] + readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1] + } + + animation: QtObject { + property QtObject elementMove: QtObject { + property int duration: 500 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial + property int velocity: 650 + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMove.duration + easing.type: root.animation.elementMove.type + easing.bezierCurve: root.animation.elementMove.bezierCurve + } + } + property Component colorAnimation: Component { + ColorAnimation { + duration: root.animation.elementMove.duration + easing.type: root.animation.elementMove.type + easing.bezierCurve: root.animation.elementMove.bezierCurve + } + } + } + property QtObject elementMoveEnter: QtObject { + property int duration: 400 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.emphasizedDecel + property int velocity: 650 + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMoveEnter.duration + easing.type: root.animation.elementMoveEnter.type + easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve + } + } + } + property QtObject elementMoveExit: QtObject { + property int duration: 200 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.emphasizedAccel + property int velocity: 650 + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMoveExit.duration + easing.type: root.animation.elementMoveExit.type + easing.bezierCurve: root.animation.elementMoveExit.bezierCurve + } + } + } + property QtObject elementMoveFast: QtObject { + property int duration: 200 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.expressiveEffects + property int velocity: 850 + property Component colorAnimation: Component { ColorAnimation { + duration: root.animation.elementMoveFast.duration + easing.type: root.animation.elementMoveFast.type + easing.bezierCurve: root.animation.elementMoveFast.bezierCurve + }} + property Component numberAnimation: Component { NumberAnimation { + duration: root.animation.elementMoveFast.duration + easing.type: root.animation.elementMoveFast.type + easing.bezierCurve: root.animation.elementMoveFast.bezierCurve + }} + } + + property QtObject clickBounce: QtObject { + property int duration: 200 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.expressiveFastSpatial + property int velocity: 850 + property Component numberAnimation: Component { NumberAnimation { + duration: root.animation.clickBounce.duration + easing.type: root.animation.clickBounce.type + easing.bezierCurve: root.animation.clickBounce.bezierCurve + }} + } + property QtObject scroll: QtObject { + property int duration: 400 + property int type: Easing.BezierSpline + property list<real> bezierCurve: animationCurves.standardDecel + } + property QtObject menuDecel: QtObject { + property int duration: 350 + property int type: Easing.OutExpo + } + } + + sizes: QtObject { + property real barHeight: 40 + property real notificationPopupWidth: 410 + property real searchWidthCollapsed: 260 + property real searchWidth: 450 + property real hyprlandGapsOut: 5 + property real elevationMargin: 10 + property real fabShadowRadius: 5 + property real fabHoveredShadowRadius: 7 + } +} diff --git a/config/quickshell/modules/common/ConfigOptions.qml b/config/quickshell/modules/common/ConfigOptions.qml new file mode 100644 index 00000000..25f0de05 --- /dev/null +++ b/config/quickshell/modules/common/ConfigOptions.qml @@ -0,0 +1,43 @@ +import QtQuick +import Quickshell +pragma Singleton +pragma ComponentBehavior: Bound + +Singleton { + + property QtObject appearance: QtObject { + property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen + } + + property QtObject overview: QtObject { + property real scale: 0.15 // Relative to screen size + property real numOfRows: 2 + property real numOfCols: 5 + property bool showXwaylandIndicator: true + property real windowPadding: 6 + property real position: 1 // 0: top | 1: middle | 2: bottom + property real workspaceNumberSize: 120 // Set 0, dynamic calculation based on monitor size + } + + property QtObject resources: QtObject { + property int updateInterval: 3000 + } + + property QtObject hacks: QtObject { + property int arbitraryRaceConditionDelay: 20 // milliseconds + } + + property QtObject search: QtObject { + property bool searchEnabled: false + property int nonAppResultDelay: 30 // This prevents lagging when typing + property QtObject prefix: QtObject { + property string action: "/" + property string clipboard: ";" + property string emojis: ":" + } + } + + property QtObject bar: QtObject { + property bool bottom: false // Instead of top + } +} diff --git a/config/quickshell/modules/common/Directories.qml b/config/quickshell/modules/common/Directories.qml new file mode 100644 index 00000000..f4a6bf83 --- /dev/null +++ b/config/quickshell/modules/common/Directories.qml @@ -0,0 +1,20 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/file_utils.js" as FileUtils +import Qt.labs.platform +import QtQuick +import Quickshell +import Quickshell.Hyprland + +Singleton { + // XDG Dirs, with "file://" + readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] + readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] + readonly property string gen_cache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0] + + // Other dirs used by the shell, without "file://" + property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/quickshell`) + property string shellConfigPath: `${Directories.shellConfig}/config.json` + property string generatedMaterialThemePath: `${Directories.shellConfig}/qml_color.json` +} diff --git a/config/quickshell/modules/common/functions/color_utils.js b/config/quickshell/modules/common/functions/color_utils.js new file mode 100644 index 00000000..c0ccfda9 --- /dev/null +++ b/config/quickshell/modules/common/functions/color_utils.js @@ -0,0 +1,85 @@ +// This module provides high level utility functions for color manipulation. + +/** + * Returns a color with the hue of color2 and the saturation, value, and alpha of color1. + * + * @param {string} color1 - The base color (any Qt.color-compatible string). + * @param {string} color2 - The color to take hue from. + * @returns {Qt.rgba} The resulting color. + */ +function colorWithHueOf(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + + // Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1 + var hue = c2.hsvHue; + var sat = c1.hsvSaturation; + var val = c1.hsvValue; + var alpha = c1.a; + + return Qt.hsva(hue, sat, val, alpha); +} + +/** + * Returns a color with the saturation of color2 and the hue/value/alpha of color1. + * + * @param {string} color1 - The base color (any Qt.color-compatible string). + * @param {string} color2 - The color to take saturation from. + * @returns {Qt.rgba} The resulting color. + */ +function colorWithSaturationOf(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + + var hue = c1.hsvHue; + var sat = c2.hsvSaturation; + var val = c1.hsvValue; + var alpha = c1.a; + + return Qt.hsva(hue, sat, val, alpha); +} + +/** + * Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1. + * + * @param {string} color1 - The base color (any Qt.color-compatible string). + * @param {string} color2 - The accent color. + * @returns {Qt.rgba} The resulting color. + */ +function adaptToAccent(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + + var hue = c2.hslHue; + var sat = c2.hslSaturation; + var light = c1.hslLightness; + var alpha = c1.a; + + return Qt.hsla(hue, sat, light, alpha); +} + +/** + * Mixes two colors by a given percentage. + * + * @param {string} color1 - The first color (any Qt.color-compatible string). + * @param {string} color2 - The second color. + * @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2. + * @returns {Qt.rgba} The resulting mixed color. + */ +function mix(color1, color2, percentage) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a); +} + +/** + * Transparentizes a color by a given percentage. + * + * @param {string} color - The color (any Qt.color-compatible string). + * @param {number} percentage - The amount to transparentize (0-1). + * @returns {Qt.rgba} The resulting color. + */ +function transparentize(color, percentage = 1) { + var c = Qt.color(color); + return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); +} diff --git a/config/quickshell/modules/common/functions/file_utils.js b/config/quickshell/modules/common/functions/file_utils.js new file mode 100644 index 00000000..758950de --- /dev/null +++ b/config/quickshell/modules/common/functions/file_utils.js @@ -0,0 +1,9 @@ +/** + * Trims the File protocol off the input string + * @param {string} str + * @returns {string} + */ +function trimFileProtocol(str) { + return str.startsWith("file://") ? str.slice(7) : str; +} + diff --git a/config/quickshell/modules/common/functions/fuzzysort.js b/config/quickshell/modules/common/functions/fuzzysort.js new file mode 100644 index 00000000..1c1d9b9d --- /dev/null +++ b/config/quickshell/modules/common/functions/fuzzysort.js @@ -0,0 +1,682 @@ +.pragma library + +// https://github.com/farzher/fuzzysort +// License: MIT | Copyright (c) 2018 Stephen Kamenar +// A copy of the license is available in the `licenses` folder of this repository + +var single = (search, target) => { + if(!search || !target) return NULL + + var preparedSearch = getPreparedSearch(search) + if(!isPrepared(target)) target = getPrepared(target) + + var searchBitflags = preparedSearch.bitflags + if((searchBitflags & target._bitflags) !== searchBitflags) return NULL + + return algorithm(preparedSearch, target) +} + +var go = (search, targets, options) => { + if(!search) return options?.all ? all(targets, options) : noResults + + var preparedSearch = getPreparedSearch(search) + var searchBitflags = preparedSearch.bitflags + var containsSpace = preparedSearch.containsSpace + + var threshold = denormalizeScore( options?.threshold || 0 ) + var limit = options?.limit || INFINITY + + var resultsLen = 0; var limitedCount = 0 + var targetsLen = targets.length + + function push_result(result) { + if(resultsLen < limit) { q.add(result); ++resultsLen } + else { + ++limitedCount + if(result._score > q.peek()._score) q.replaceTop(result) + } + } + + // This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys] + + // options.key + if(options?.key) { + var key = options.key + for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] + var target = getValue(obj, key) + if(!target) continue + if(!isPrepared(target)) target = getPrepared(target) + + if((searchBitflags & target._bitflags) !== searchBitflags) continue + var result = algorithm(preparedSearch, target) + if(result === NULL) continue + if(result._score < threshold) continue + + result.obj = obj + push_result(result) + } + + // options.keys + } else if(options?.keys) { + var keys = options.keys + var keysLen = keys.length + + outer: for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] + + { // early out based on bitflags + var keysBitflags = 0 + for (var keyI = 0; keyI < keysLen; ++keyI) { + var key = keys[keyI] + var target = getValue(obj, key) + if(!target) { tmpTargets[keyI] = noTarget; continue } + if(!isPrepared(target)) target = getPrepared(target) + tmpTargets[keyI] = target + + keysBitflags |= target._bitflags + } + + if((searchBitflags & keysBitflags) !== searchBitflags) continue + } + + if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) keysSpacesBestScores[i] = NEGATIVE_INFINITY + + for (var keyI = 0; keyI < keysLen; ++keyI) { + target = tmpTargets[keyI] + if(target === noTarget) { tmpResults[keyI] = noTarget; continue } + + tmpResults[keyI] = algorithm(preparedSearch, target, /*allowSpaces=*/false, /*allowPartialMatch=*/containsSpace) + if(tmpResults[keyI] === NULL) { tmpResults[keyI] = noTarget; continue } + + // todo: this seems weird and wrong. like what if our first match wasn't good. this should just replace it instead of averaging with it + // if our second match isn't good we ignore it instead of averaging with it + if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) { + if(allowPartialMatchScores[i] > -1000) { + if(keysSpacesBestScores[i] > NEGATIVE_INFINITY) { + var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4/*bonus score for having multiple matches*/ + if(tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp + } + } + if(allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i] + } + } + + if(containsSpace) { + for(let i=0; i<preparedSearch.spaceSearches.length; i++) { if(keysSpacesBestScores[i] === NEGATIVE_INFINITY) continue outer } + } else { + var hasAtLeast1Match = false + for(let i=0; i < keysLen; i++) { if(tmpResults[i]._score !== NEGATIVE_INFINITY) { hasAtLeast1Match = true; break } } + if(!hasAtLeast1Match) continue + } + + var objResults = new KeysResult(keysLen) + for(let i=0; i < keysLen; i++) { objResults[i] = tmpResults[i] } + + if(containsSpace) { + var score = 0 + for(let i=0; i<preparedSearch.spaceSearches.length; i++) score += keysSpacesBestScores[i] + } else { + // todo could rewrite this scoring to be more similar to when there's spaces + // if we match multiple keys give us bonus points + var score = NEGATIVE_INFINITY + for(let i=0; i<keysLen; i++) { + var result = objResults[i] + if(result._score > -1000) { + if(score > NEGATIVE_INFINITY) { + var tmp = (score + result._score) / 4/*bonus score for having multiple matches*/ + if(tmp > score) score = tmp + } + } + if(result._score > score) score = result._score + } + } + + objResults.obj = obj + objResults._score = score + if(options?.scoreFn) { + score = options.scoreFn(objResults) + if(!score) continue + score = denormalizeScore(score) + objResults._score = score + } + + if(score < threshold) continue + push_result(objResults) + } + + // no keys + } else { + for(var i = 0; i < targetsLen; ++i) { var target = targets[i] + if(!target) continue + if(!isPrepared(target)) target = getPrepared(target) + + if((searchBitflags & target._bitflags) !== searchBitflags) continue + var result = algorithm(preparedSearch, target) + if(result === NULL) continue + if(result._score < threshold) continue + + push_result(result) + } + } + + if(resultsLen === 0) return noResults + var results = new Array(resultsLen) + for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll() + results.total = resultsLen + limitedCount + return results +} + + +// this is written as 1 function instead of 2 for minification. perf seems fine ... +// except when minified. the perf is very slow +var highlight = (result, open='<b>', close='</b>') => { + var callback = typeof open === 'function' ? open : undefined + + var target = result.target + var targetLen = target.length + var indexes = result.indexes + var highlighted = '' + var matchI = 0 + var indexesI = 0 + var opened = false + var parts = [] + + for(var i = 0; i < targetLen; ++i) { var char = target[i] + if(indexes[indexesI] === i) { + ++indexesI + if(!opened) { opened = true + if(callback) { + parts.push(highlighted); highlighted = '' + } else { + highlighted += open + } + } + + if(indexesI === indexes.length) { + if(callback) { + highlighted += char + parts.push(callback(highlighted, matchI++)); highlighted = '' + parts.push(target.substr(i+1)) + } else { + highlighted += char + close + target.substr(i+1) + } + break + } + } else { + if(opened) { opened = false + if(callback) { + parts.push(callback(highlighted, matchI++)); highlighted = '' + } else { + highlighted += close + } + } + } + highlighted += char + } + + return callback ? parts : highlighted +} + + +var prepare = (target) => { + if(typeof target === 'number') target = ''+target + else if(typeof target !== 'string') target = '' + var info = prepareLowerInfo(target) + return new_result(target, {_targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _bitflags:info.bitflags}) +} + +var cleanup = () => { preparedCache.clear(); preparedSearchCache.clear() } + + +// Below this point is only internal code +// Below this point is only internal code +// Below this point is only internal code +// Below this point is only internal code + + +class Result { + get ['indexes']() { return this._indexes.slice(0, this._indexes.len).sort((a,b)=>a-b) } + set ['indexes'](indexes) { return this._indexes = indexes } + ['highlight'](open, close) { return highlight(this, open, close) } + get ['score']() { return normalizeScore(this._score) } + set ['score'](score) { this._score = denormalizeScore(score) } +} + +class KeysResult extends Array { + get ['score']() { return normalizeScore(this._score) } + set ['score'](score) { this._score = denormalizeScore(score) } +} + +var new_result = (target, options) => { + const result = new Result() + result['target'] = target + result['obj'] = options.obj ?? NULL + result._score = options._score ?? NEGATIVE_INFINITY + result._indexes = options._indexes ?? [] + result._targetLower = options._targetLower ?? '' + result._targetLowerCodes = options._targetLowerCodes ?? NULL + result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL + result._bitflags = options._bitflags ?? 0 + return result +} + + +var normalizeScore = score => { + if(score === NEGATIVE_INFINITY) return 0 + if(score > 1) return score + return Math.E ** ( ((-score + 1)**.04307 - 1) * -2) +} +var denormalizeScore = normalizedScore => { + if(normalizedScore === 0) return NEGATIVE_INFINITY + if(normalizedScore > 1) return normalizedScore + return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307) +} + + +var prepareSearch = (search) => { + if(typeof search === 'number') search = ''+search + else if(typeof search !== 'string') search = '' + search = search.trim() + var info = prepareLowerInfo(search) + + var spaceSearches = [] + if(info.containsSpace) { + var searches = search.split(/\s+/) + searches = [...new Set(searches)] // distinct + for(var i=0; i<searches.length; i++) { + if(searches[i] === '') continue + var _info = prepareLowerInfo(searches[i]) + spaceSearches.push({lowerCodes:_info.lowerCodes, _lower:searches[i].toLowerCase(), containsSpace:false}) + } + } + + return {lowerCodes: info.lowerCodes, _lower: info._lower, containsSpace: info.containsSpace, bitflags: info.bitflags, spaceSearches: spaceSearches} +} + + + +var getPrepared = (target) => { + if(target.length > 999) return prepare(target) // don't cache huge targets + var targetPrepared = preparedCache.get(target) + if(targetPrepared !== undefined) return targetPrepared + targetPrepared = prepare(target) + preparedCache.set(target, targetPrepared) + return targetPrepared +} +var getPreparedSearch = (search) => { + if(search.length > 999) return prepareSearch(search) // don't cache huge searches + var searchPrepared = preparedSearchCache.get(search) + if(searchPrepared !== undefined) return searchPrepared + searchPrepared = prepareSearch(search) + preparedSearchCache.set(search, searchPrepared) + return searchPrepared +} + + +var all = (targets, options) => { + var results = []; results.total = targets.length // this total can be wrong if some targets are skipped + + var limit = options?.limit || INFINITY + + if(options?.key) { + for(var i=0;i<targets.length;i++) { var obj = targets[i] + var target = getValue(obj, options.key) + if(target == NULL) continue + if(!isPrepared(target)) target = getPrepared(target) + var result = new_result(target.target, {_score: target._score, obj: obj}) + results.push(result); if(results.length >= limit) return results + } + } else if(options?.keys) { + for(var i=0;i<targets.length;i++) { var obj = targets[i] + var objResults = new KeysResult(options.keys.length) + for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) { + var target = getValue(obj, options.keys[keyI]) + if(!target) { objResults[keyI] = noTarget; continue } + if(!isPrepared(target)) target = getPrepared(target) + target._score = NEGATIVE_INFINITY + target._indexes.len = 0 + objResults[keyI] = target + } + objResults.obj = obj + objResults._score = NEGATIVE_INFINITY + results.push(objResults); if(results.length >= limit) return results + } + } else { + for(var i=0;i<targets.length;i++) { var target = targets[i] + if(target == NULL) continue + if(!isPrepared(target)) target = getPrepared(target) + target._score = NEGATIVE_INFINITY + target._indexes.len = 0 + results.push(target); if(results.length >= limit) return results + } + } + + return results +} + + +var algorithm = (preparedSearch, prepared, allowSpaces=false, allowPartialMatch=false) => { + if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch) + + var searchLower = preparedSearch._lower + var searchLowerCodes = preparedSearch.lowerCodes + var searchLowerCode = searchLowerCodes[0] + var targetLowerCodes = prepared._targetLowerCodes + var searchLen = searchLowerCodes.length + var targetLen = targetLowerCodes.length + var searchI = 0 // where we at + var targetI = 0 // where you at + var matchesSimpleLen = 0 + + // very basic fuzzy match; to remove non-matching targets ASAP! + // walk through target. find sequential matches. + // if all chars aren't found then exit + for(;;) { + var isMatch = searchLowerCode === targetLowerCodes[targetI] + if(isMatch) { + matchesSimple[matchesSimpleLen++] = targetI + ++searchI; if(searchI === searchLen) break + searchLowerCode = searchLowerCodes[searchI] + } + ++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI + } + + var searchI = 0 + var successStrict = false + var matchesStrictLen = 0 + + var nextBeginningIndexes = prepared._nextBeginningIndexes + if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target) + targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1] + + // Our target string successfully matched all characters in sequence! + // Let's try a more advanced and strict test to improve the score + // only count it as a match if it's consecutive or a beginning character! + var backtrackCount = 0 + if(targetI !== targetLen) for(;;) { + if(targetI >= targetLen) { + // We failed to find a good spot for this search char, go back to the previous search char and force it forward + if(searchI <= 0) break // We failed to push chars forward for a better match + + ++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match + + --searchI + var lastMatch = matchesStrict[--matchesStrictLen] + targetI = nextBeginningIndexes[lastMatch] + + } else { + var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI] + if(isMatch) { + matchesStrict[matchesStrictLen++] = targetI + ++searchI; if(searchI === searchLen) { successStrict = true; break } + ++targetI + } else { + targetI = nextBeginningIndexes[targetI] + } + } + } + + // check if it's a substring match + var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow + var isSubstring = !!~substringIndex + var isSubstringBeginning = !isSubstring ? false : substringIndex===0 || prepared._nextBeginningIndexes[substringIndex-1] === substringIndex + + // if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score + if(isSubstring && !isSubstringBeginning) { + for(var i=0; i<nextBeginningIndexes.length; i=nextBeginningIndexes[i]) { + if(i <= substringIndex) continue + + for(var s=0; s<searchLen; s++) if(searchLowerCodes[s] !== prepared._targetLowerCodes[i+s]) break + if(s === searchLen) { substringIndex = i; isSubstringBeginning = true; break } + } + } + + // tally up the score & keep track of matches for highlighting later + // if it's a simple match, we'll switch to a substring match if a substring exists + // if it's a strict match, we'll switch to a substring match only if that's a better score + + var calculateScore = matches => { + var score = 0 + + var extraMatchGroupCount = 0 + for(var i = 1; i < searchLen; ++i) { + if(matches[i] - matches[i-1] !== 1) {score -= matches[i]; ++extraMatchGroupCount} + } + var unmatchedDistance = matches[searchLen-1] - matches[0] - (searchLen-1) + + score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups + + if(matches[0] !== 0) score -= matches[0]*matches[0]*.2 // penality for not starting near the beginning + + if(!successStrict) { + score *= 1000 + } else { + // successStrict on a target with too many beginning indexes loses points for being a bad target + var uniqueBeginningIndexes = 1 + for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes + + if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ... + } + + score -= (targetLen - searchLen)/2 // penality for longer targets + + if(isSubstring) score /= 1+searchLen*searchLen*1 // bonus for being a full substring + if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex + + score -= (targetLen - searchLen)/2 // penality for longer targets + + return score + } + + if(!successStrict) { + if(isSubstring) for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches + var matchesBest = matchesSimple + var score = calculateScore(matchesBest) + } else { + if(isSubstringBeginning) { + for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches + var matchesBest = matchesSimple + var score = calculateScore(matchesSimple) + } else { + var matchesBest = matchesStrict + var score = calculateScore(matchesStrict) + } + } + + prepared._score = score + + for(var i = 0; i < searchLen; ++i) prepared._indexes[i] = matchesBest[i] + prepared._indexes.len = searchLen + + const result = new Result() + result.target = prepared.target + result._score = prepared._score + result._indexes = prepared._indexes + return result +} +var algorithmSpaces = (preparedSearch, target, allowPartialMatch) => { + var seen_indexes = new Set() + var score = 0 + var result = NULL + + var first_seen_index_last_search = 0 + var searches = preparedSearch.spaceSearches + var searchesLen = searches.length + var changeslen = 0 + + // Return _nextBeginningIndexes back to its normal state + var resetNextBeginningIndexes = () => { + for(let i=changeslen-1; i>=0; i--) target._nextBeginningIndexes[nextBeginningIndexesChanges[i*2 + 0]] = nextBeginningIndexesChanges[i*2 + 1] + } + + var hasAtLeast1Match = false + for(var i=0; i<searchesLen; ++i) { + allowPartialMatchScores[i] = NEGATIVE_INFINITY + var search = searches[i] + + result = algorithm(search, target) + if(allowPartialMatch) { + if(result === NULL) continue + hasAtLeast1Match = true + } else { + if(result === NULL) {resetNextBeginningIndexes(); return NULL} + } + + // if not the last search, we need to mutate _nextBeginningIndexes for the next search + var isTheLastSearch = i === searchesLen - 1 + if(!isTheLastSearch) { + var indexes = result._indexes + + var indexesIsConsecutiveSubstring = true + for(let i=0; i<indexes.len-1; i++) { + if(indexes[i+1] - indexes[i] !== 1) { + indexesIsConsecutiveSubstring = false; break; + } + } + + if(indexesIsConsecutiveSubstring) { + var newBeginningIndex = indexes[indexes.len-1] + 1 + var toReplace = target._nextBeginningIndexes[newBeginningIndex-1] + for(let i=newBeginningIndex-1; i>=0; i--) { + if(toReplace !== target._nextBeginningIndexes[i]) break + target._nextBeginningIndexes[i] = newBeginningIndex + nextBeginningIndexesChanges[changeslen*2 + 0] = i + nextBeginningIndexesChanges[changeslen*2 + 1] = toReplace + changeslen++ + } + } + } + + score += result._score / searchesLen + allowPartialMatchScores[i] = result._score / searchesLen + + // dock points based on order otherwise "c man" returns Manifest.cpp instead of CheatManager.h + if(result._indexes[0] < first_seen_index_last_search) { + score -= (first_seen_index_last_search - result._indexes[0]) * 2 + } + first_seen_index_last_search = result._indexes[0] + + for(var j=0; j<result._indexes.len; ++j) seen_indexes.add(result._indexes[j]) + } + + if(allowPartialMatch && !hasAtLeast1Match) return NULL + + resetNextBeginningIndexes() + + // allows a search with spaces that's an exact substring to score well + var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/true) + if(allowSpacesResult !== NULL && allowSpacesResult._score > score) { + if(allowPartialMatch) { + for(var i=0; i<searchesLen; ++i) { + allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen + } + } + return allowSpacesResult + } + + if(allowPartialMatch) result = target + result._score = score + + var i = 0 + for (let index of seen_indexes) result._indexes[i++] = index + result._indexes.len = i + + return result +} + +// we use this instead of just .normalize('NFD').replace(/[\u0300-\u036f]/g, '') because that screws with japanese characters +var remove_accents = (str) => str.replace(/\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\u0300-\u036f]/g, '') + +var prepareLowerInfo = (str) => { + str = remove_accents(str) + var strLen = str.length + var lower = str.toLowerCase() + var lowerCodes = [] // new Array(strLen) sparse array is too slow + var bitflags = 0 + var containsSpace = false // space isn't stored in bitflags because of how searching with a space works + + for(var i = 0; i < strLen; ++i) { + var lowerCode = lowerCodes[i] = lower.charCodeAt(i) + + if(lowerCode === 32) { + containsSpace = true + continue // it's important that we don't set any bitflags for space + } + + var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet + : lowerCode>=48&&lowerCode<=57 ? 26 // numbers + // 3 bits available + : lowerCode<=127 ? 30 // other ascii + : 31 // other utf8 + bitflags |= 1<<bit + } + + return {lowerCodes:lowerCodes, bitflags:bitflags, containsSpace:containsSpace, _lower:lower} +} +var prepareBeginningIndexes = (target) => { + var targetLen = target.length + var beginningIndexes = []; var beginningIndexesLen = 0 + var wasUpper = false + var wasAlphanum = false + for(var i = 0; i < targetLen; ++i) { + var targetCode = target.charCodeAt(i) + var isUpper = targetCode>=65&&targetCode<=90 + var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57 + var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum + wasUpper = isUpper + wasAlphanum = isAlphanum + if(isBeginning) beginningIndexes[beginningIndexesLen++] = i + } + return beginningIndexes +} +var prepareNextBeginningIndexes = (target) => { + target = remove_accents(target) + var targetLen = target.length + var beginningIndexes = prepareBeginningIndexes(target) + var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow + var lastIsBeginning = beginningIndexes[0] + var lastIsBeginningI = 0 + for(var i = 0; i < targetLen; ++i) { + if(lastIsBeginning > i) { + nextBeginningIndexes[i] = lastIsBeginning + } else { + lastIsBeginning = beginningIndexes[++lastIsBeginningI] + nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning + } + } + return nextBeginningIndexes +} + +var preparedCache = new Map() +var preparedSearchCache = new Map() + +// the theory behind these being globals is to reduce garbage collection by not making new arrays +var matchesSimple = []; var matchesStrict = [] +var nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search +var keysSpacesBestScores = []; var allowPartialMatchScores = [] +var tmpTargets = []; var tmpResults = [] + +// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop] +// prop = 'key1.key2' 10ms +// prop = ['key1', 'key2'] 27ms +// prop = obj => obj.tags.join() ??ms +var getValue = (obj, prop) => { + var tmp = obj[prop]; if(tmp !== undefined) return tmp + if(typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower + var segs = prop + if(!Array.isArray(prop)) segs = prop.split('.') + var len = segs.length + var i = -1 + while (obj && (++i < len)) obj = obj[segs[i]] + return obj +} + +var isPrepared = (x) => { return typeof x === 'object' && typeof x._bitflags === 'number' } +var INFINITY = Infinity; var NEGATIVE_INFINITY = -INFINITY +var noResults = []; noResults.total = 0 +var NULL = null + +var noTarget = prepare('') + +// Hacked version of https://github.com/lemire/FastPriorityQueue.js +var fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c<o;){var s=c+1;a=c,s<o&&e[s]._score<e[c]._score&&(a=s),e[a-1>>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v._score<e[f]._score;f=(a=f)-1>>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r._score<e[v]._score;v=(a=v)-1>>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a} +var q = fastpriorityqueue() // reuse this diff --git a/config/quickshell/modules/common/functions/levendist.js b/config/quickshell/modules/common/functions/levendist.js new file mode 100644 index 00000000..90180d21 --- /dev/null +++ b/config/quickshell/modules/common/functions/levendist.js @@ -0,0 +1,141 @@ +// Original code from https://github.com/koeqaife/hyprland-material-you +// Original code license: GPLv3 +// Translated to Js from Cython with an LLM and reviewed + +function min3(a, b, c) { + return a < b && a < c ? a : b < c ? b : c; +} + +function max3(a, b, c) { + return a > b && a > c ? a : b > c ? b : c; +} + +function min2(a, b) { + return a < b ? a : b; +} + +function max2(a, b) { + return a > b ? a : b; +} + +function levenshteinDistance(s1, s2) { + let len1 = s1.length; + let len2 = s2.length; + + if (len1 === 0) return len2; + if (len2 === 0) return len1; + + if (len2 > len1) { + [s1, s2] = [s2, s1]; + [len1, len2] = [len2, len1]; + } + + let prev = new Array(len2 + 1); + let curr = new Array(len2 + 1); + + for (let j = 0; j <= len2; j++) { + prev[j] = j; + } + + for (let i = 1; i <= len1; i++) { + curr[0] = i; + for (let j = 1; j <= len2; j++) { + let cost = s1[i - 1] === s2[j - 1] ? 0 : 1; + curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost); + } + [prev, curr] = [curr, prev]; + } + + return prev[len2]; +} + +function partialRatio(shortS, longS) { + let lenS = shortS.length; + let lenL = longS.length; + let best = 0.0; + + if (lenS === 0) return 1.0; + + for (let i = 0; i <= lenL - lenS; i++) { + let sub = longS.slice(i, i + lenS); + let dist = levenshteinDistance(shortS, sub); + let score = 1.0 - (dist / lenS); + if (score > best) best = score; + } + + return best; +} + +function computeScore(s1, s2) { + if (s1 === s2) return 1.0; + + let dist = levenshteinDistance(s1, s2); + let maxLen = max2(s1.length, s2.length); + if (maxLen === 0) return 1.0; + + let full = 1.0 - (dist / maxLen); + let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); + + let score = 0.85 * full + 0.15 * part; + + if (s1 && s2 && s1[0] !== s2[0]) { + score -= 0.05; + } + + let lenDiff = Math.abs(s1.length - s2.length); + if (lenDiff >= 3) { + score -= 0.05 * lenDiff / maxLen; + } + + let commonPrefixLen = 0; + let minLen = min2(s1.length, s2.length); + for (let i = 0; i < minLen; i++) { + if (s1[i] === s2[i]) { + commonPrefixLen++; + } else { + break; + } + } + score += 0.02 * commonPrefixLen; + + if (s1.includes(s2) || s2.includes(s1)) { + score += 0.06; + } + + return Math.max(0.0, Math.min(1.0, score)); +} + +function computeTextMatchScore(s1, s2) { + if (s1 === s2) return 1.0; + + let dist = levenshteinDistance(s1, s2); + let maxLen = max2(s1.length, s2.length); + if (maxLen === 0) return 1.0; + + let full = 1.0 - (dist / maxLen); + let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1); + + let score = 0.4 * full + 0.6 * part; + + let lenDiff = Math.abs(s1.length - s2.length); + if (lenDiff >= 10) { + score -= 0.02 * lenDiff / maxLen; + } + + let commonPrefixLen = 0; + let minLen = min2(s1.length, s2.length); + for (let i = 0; i < minLen; i++) { + if (s1[i] === s2[i]) { + commonPrefixLen++; + } else { + break; + } + } + score += 0.01 * commonPrefixLen; + + if (s1.includes(s2) || s2.includes(s1)) { + score += 0.2; + } + + return Math.max(0.0, Math.min(1.0, score)); +} diff --git a/config/quickshell/modules/common/functions/object_utils.js b/config/quickshell/modules/common/functions/object_utils.js new file mode 100644 index 00000000..96c632fd --- /dev/null +++ b/config/quickshell/modules/common/functions/object_utils.js @@ -0,0 +1,45 @@ +function toPlainObject(qtObj) { + if (qtObj === null || typeof qtObj !== "object") return qtObj; + + // Handle arrays + if (Array.isArray(qtObj)) { + return qtObj.map(toPlainObject); + } + + const result = ({}); + for (let key in qtObj) { + if ( + typeof qtObj[key] !== "function" && + !key.startsWith("objectName") && + !key.startsWith("children") && + !key.startsWith("object") && + !key.startsWith("parent") && + !key.startsWith("metaObject") && + !key.startsWith("destroyed") && + !key.startsWith("reloadableId") + ) { + result[key] = toPlainObject(qtObj[key]); + } + } + return result; +} + +function applyToQtObject(qtObj, jsonObj) { + if (!qtObj || typeof jsonObj !== "object" || jsonObj === null) return; + + for (let key in jsonObj) { + if (!qtObj.hasOwnProperty(key)) continue; + + // Check if the property is a QtObject (not a value) + const value = qtObj[key]; + const jsonValue = jsonObj[key]; + + // If it's an object and not an array, recurse + if (value && typeof value === "object" && !Array.isArray(value)) { + applyToQtObject(value, jsonValue); + } else { + // Otherwise, assign the value + qtObj[key] = jsonValue; + } + } +} diff --git a/config/quickshell/modules/common/functions/string_utils.js b/config/quickshell/modules/common/functions/string_utils.js new file mode 100644 index 00000000..c31edf49 --- /dev/null +++ b/config/quickshell/modules/common/functions/string_utils.js @@ -0,0 +1,53 @@ +/** + * Formats a string according to the args that are passed in + * @param { string } str + * @param {...any} args + * @returns + */ +function format(str, ...args) { + return str.replace(/{(\d+)}/g, (match, index) => + typeof args[index] !== 'undefined' ? args[index] : match + ); +} + +/** + * Returns the domain of the passed in url or null + * @param { string } url + * @returns { string| null } + */ +function getDomain(url) { + const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/); + return match ? match[1] : null; +} + +/** + * Returns the base url of the passed in url or null + * @param { string } url + * @returns { string | null } + */ +function getBaseUrl(url) { + const match = url.match(/^(https?:\/\/[^\/]+)(\/.*)?$/); + return match ? match[1] : null; +} + +/** + * Escapes single quotes in shell commands + * @param { string } str + * @returns { string } + */ +function shellSingleQuoteEscape(str) { + // escape single quotes + return String(str) + // .replace(/\\/g, '\\\\') + .replace(/'/g, "'\\''"); +} + +function escapeHtml(str) { + if (typeof str !== 'string') return str; + return str + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} diff --git a/config/quickshell/modules/common/widgets/CliphistImage.qml b/config/quickshell/modules/common/widgets/CliphistImage.qml new file mode 100644 index 00000000..9de34450 --- /dev/null +++ b/config/quickshell/modules/common/widgets/CliphistImage.qml @@ -0,0 +1,101 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils +import Qt5Compat.GraphicalEffects +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +Rectangle { + id: root + property string entry + property real maxWidth + property real maxHeight + + property string imageDecodePath: Directories.cliphistDecode + property string imageDecodeFileName: `${entryNumber}` + property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}` + property string source + + property int entryNumber: { + if (!root.entry) return 0 + const match = root.entry.match(/^(\d+)\t/) + return match ? parseInt(match[1]) : 0 + } + property int imageWidth: { + if (!root.entry) return 0 + const match = root.entry.match(/(\d+)x(\d+)/) + return match ? parseInt(match[1]) : 0 + } + property int imageHeight: { + if (!root.entry) return 0 + const match = root.entry.match(/(\d+)x(\d+)/) + return match ? parseInt(match[2]) : 0 + } + property real scale: { + return Math.min( + root.maxWidth / imageWidth, + root.maxHeight / imageHeight, + 1 + ) + } + + color: Appearance.colors.colLayer1 + radius: Appearance.rounding.small + implicitHeight: imageHeight * scale + implicitWidth: imageWidth * scale + + Component.onCompleted: { + decodeImageProcess.running = true + } + + Process { + id: decodeImageProcess + command: ["bash", "-c", + `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | cliphist decode > '${imageDecodeFilePath}'` + ] + onExited: (exitCode, exitStatus) => { + if (exitCode === 0) { + root.source = imageDecodeFilePath + } else { + console.error("[CliphistImage] Failed to decode image for entry:", root.entry) + root.source = "" + } + } + } + + Component.onDestruction: { + Hyprland.dispatch(`exec bash -c "[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'"`) + } + + Image { + id: image + anchors.fill: parent + + source: Qt.resolvedUrl(root.source) + fillMode: Image.PreserveAspectFit + antialiasing: true + asynchronous: true + + width: root.imageWidth * root.scale + height: root.imageHeight * root.scale + sourceSize.width: width + sourceSize.height: height + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: image.width + height: image.height + radius: root.radius + } + } + } +} + diff --git a/config/quickshell/modules/common/widgets/DialogButton.qml b/config/quickshell/modules/common/widgets/DialogButton.qml new file mode 100644 index 00000000..b799336a --- /dev/null +++ b/config/quickshell/modules/common/widgets/DialogButton.qml @@ -0,0 +1,38 @@ +import "root:/modules/common" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +/** + * Material 3 dialog button. See https://m3.material.io/components/dialogs/overview + */ +RippleButton { + id: button + + property string buttonText + implicitHeight: 30 + implicitWidth: buttonTextWidget.implicitWidth + 15 * 2 + buttonRadius: Appearance?.rounding.full ?? 9999 + + property color colEnabled: Appearance?.colors.colPrimary + property color colDisabled: Appearance?.m3colors.m3borderPrimary + + contentItem: StyledText { + id: buttonTextWidget + anchors.fill: parent + anchors.leftMargin: 15 + anchors.rightMargin: 15 + text: buttonText + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance?.font.pixelSize.textBase ?? 12 + color: button.enabled ? button.colEnabled : button.colDisabled + + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + } + +} diff --git a/config/quickshell/modules/common/widgets/MaterialSymbol.qml b/config/quickshell/modules/common/widgets/MaterialSymbol.qml new file mode 100644 index 00000000..214f838e --- /dev/null +++ b/config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -0,0 +1,27 @@ +import "root:/modules/common/" +import QtQuick + +Text { + id: root + property real iconSize: Appearance?.font.pixelSize.textBase ?? 16 + property real fill: 0 + renderType: Text.NativeRendering + font.hintingPreference: Font.PreferFullHinting + verticalAlignment: Text.AlignVCenter + font.family: Appearance?.font.family.iconFont ?? "Material Symbols Rounded" + font.pixelSize: iconSize + color: Appearance.m3colors.m3primaryText + + Behavior on fill { + NumberAnimation { + duration: Appearance?.animation.elementMoveFast.duration ?? 200 + easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline + easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] + } + } + + font.variableAxes: { + "FILL": fill, + "opsz": iconSize, + } +} diff --git a/config/quickshell/modules/common/widgets/PointingHandInteraction.qml b/config/quickshell/modules/common/widgets/PointingHandInteraction.qml new file mode 100644 index 00000000..cf8b065f --- /dev/null +++ b/config/quickshell/modules/common/widgets/PointingHandInteraction.qml @@ -0,0 +1,7 @@ +import QtQuick + +MouseArea { + anchors.fill: parent + onPressed: (mouse) => mouse.accepted = false + cursorShape: Qt.PointingHandCursor +}
\ No newline at end of file diff --git a/config/quickshell/modules/common/widgets/RippleButton.qml b/config/quickshell/modules/common/widgets/RippleButton.qml new file mode 100644 index 00000000..cd7762b9 --- /dev/null +++ b/config/quickshell/modules/common/widgets/RippleButton.qml @@ -0,0 +1,185 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Widgets + +/** + * A button with ripple effect similar to in Material Design. + */ +Button { + id: root + property bool toggled + property string buttonText + property real buttonRadius: Appearance?.rounding?.small ?? 4 + property real buttonRadiusPressed: buttonRadius + property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius + property int rippleDuration: 1200 + property bool rippleEnabled: true + property var downAction // When left clicking (down) + property var releaseAction // When left clicking (release) + property var altAction // When right clicking + property var middleClickAction // When middle clicking + + property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" + property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" + property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" + property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" + property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" + property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" + + property color buttonColor: root.enabled ? (root.toggled ? + (root.hovered ? colBackgroundToggledHover : + colBackgroundToggled) : + (root.hovered ? colBackgroundHover : + colBackground)) : colBackground + property color rippleColor: root.toggled ? colRippleToggled : colRipple + + function startRipple(x, y) { + const stateY = buttonBackground.y; + rippleAnim.x = x; + rippleAnim.y = y - stateY; + + const dist = (ox,oy) => ox*ox + oy*oy + const stateEndY = stateY + buttonBackground.height + rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY))) + + rippleFadeAnim.complete(); + rippleAnim.restart(); + } + + component RippleAnim: NumberAnimation { + duration: rippleDuration + easing.type: Appearance?.animation.elementMoveEnter.type + easing.bezierCurve: Appearance?.animationCurves.standardDecel + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onPressed: (event) => { + if(event.button === Qt.RightButton) { + if (root.altAction) root.altAction(); + return; + } + if(event.button === Qt.MiddleButton) { + if (root.middleClickAction) root.middleClickAction(); + return; + } + root.down = true + if (root.downAction) root.downAction(); + if (!root.rippleEnabled) return; + const {x,y} = event + startRipple(x, y) + } + onReleased: (event) => { + root.down = false + if (event.button != Qt.LeftButton) return; + if (root.releaseAction) root.releaseAction(); + root.click() // Because the MouseArea already consumed the event + if (!root.rippleEnabled) return; + rippleFadeAnim.restart(); + } + onCanceled: (event) => { + root.down = false + if (!root.rippleEnabled) return; + rippleFadeAnim.restart(); + } + } + + RippleAnim { + id: rippleFadeAnim + target: ripple + property: "opacity" + to: 0 + } + + SequentialAnimation { + id: rippleAnim + + property real x + property real y + property real radius + + PropertyAction { + target: ripple + property: "x" + value: rippleAnim.x + } + PropertyAction { + target: ripple + property: "y" + value: rippleAnim.y + } + PropertyAction { + target: ripple + property: "opacity" + value: 1 + } + ParallelAnimation { + RippleAnim { + target: ripple + properties: "implicitWidth,implicitHeight" + from: 0 + to: rippleAnim.radius * 2 + } + } + } + + background: Rectangle { + id: buttonBackground + radius: root.buttonEffectiveRadius + implicitHeight: 50 + + color: root.buttonColor + Behavior on color { + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: buttonBackground.width + height: buttonBackground.height + radius: root.buttonEffectiveRadius + } + } + + Item { + id: ripple + width: ripple.implicitWidth + height: ripple.implicitHeight + opacity: 0 + + property real implicitWidth: 0 + property real implicitHeight: 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) + } + + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: root.rippleColor } + GradientStop { position: 0.3; color: root.rippleColor } + GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) } + } + } + + transform: Translate { + x: -ripple.width / 2 + y: -ripple.height / 2 + } + } + } + + contentItem: StyledText { + text: root.buttonText + } +} diff --git a/config/quickshell/modules/common/widgets/RoundCorner.qml b/config/quickshell/modules/common/widgets/RoundCorner.qml new file mode 100644 index 00000000..c9a2827a --- /dev/null +++ b/config/quickshell/modules/common/widgets/RoundCorner.qml @@ -0,0 +1,64 @@ +import QtQuick 2.9 + +Item { + id: root + + property int size: 25 + property color color: "#000000" + + onColorChanged: { + canvas.requestPaint(); + } + + property QtObject cornerEnum: QtObject { + property int topLeft: 0 + property int topRight: 1 + property int bottomLeft: 2 + property int bottomRight: 3 + } + + property int corner: cornerEnum.topLeft // Default to TopLeft + + width: size + height: size + + Canvas { + id: canvas + + anchors.fill: parent + antialiasing: true + + onPaint: { + var ctx = getContext("2d"); + var r = root.size; + + ctx.beginPath(); + switch (root.corner) { + case cornerEnum.topLeft: + ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2); + ctx.lineTo(0, 0); + break; + case cornerEnum.topRight: + ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI); + ctx.lineTo(r, 0); + break; + case cornerEnum.bottomLeft: + ctx.arc(r, 0, r, Math.PI / 2, Math.PI); + ctx.lineTo(0, r); + break; + case cornerEnum.bottomRight: + ctx.arc(0, 0, r, 0, Math.PI / 2); + ctx.lineTo(r, r); + break; + } + ctx.closePath(); + ctx.fillStyle = root.color; + ctx.fill(); + } + } + + Behavior on size { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + +} diff --git a/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml b/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml new file mode 100644 index 00000000..6e1f2e16 --- /dev/null +++ b/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml @@ -0,0 +1,13 @@ +import QtQuick +import QtQuick.Effects +import "root:/modules/common" + +RectangularShadow { + required property var target + anchors.fill: target + radius: target.radius + blur: 1.2 * Appearance.sizes.elevationMargin + spread: 1 + color: Appearance.colors.colShadow + cached: true +} diff --git a/config/quickshell/modules/common/widgets/StyledText.qml b/config/quickshell/modules/common/widgets/StyledText.qml new file mode 100644 index 00000000..988c136d --- /dev/null +++ b/config/quickshell/modules/common/widgets/StyledText.qml @@ -0,0 +1,13 @@ +import "root:/modules/common" +import QtQuick + +Text { + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font { + hintingPreference: Font.PreferFullHinting + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 + } + color: Appearance?.m3colors.m3primaryText ?? "black" +} diff --git a/config/quickshell/modules/common/widgets/StyledTextArea.qml b/config/quickshell/modules/common/widgets/StyledTextArea.qml new file mode 100644 index 00000000..af3cf34d --- /dev/null +++ b/config/quickshell/modules/common/widgets/StyledTextArea.qml @@ -0,0 +1,15 @@ +import "root:/modules/common" +import QtQuick +import QtQuick.Controls + +TextArea { + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3selectionText + selectionColor: Appearance.m3colors.m3selectionBackground + placeholderTextColor: Appearance.m3colors.m3borderPrimary + font { + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 + hintingPreference: Font.PreferFullHinting + } +} diff --git a/config/quickshell/modules/common/widgets/StyledToolTip.qml b/config/quickshell/modules/common/widgets/StyledToolTip.qml new file mode 100644 index 00000000..aaaad813 --- /dev/null +++ b/config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -0,0 +1,60 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ToolTip { + id: root + property string content + property bool extraVisibleCondition: true + property bool alternativeVisibleCondition: false + property bool internalVisibleCondition: { + const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition + return ans + } + verticalPadding: 5 + horizontalPadding: 10 + opacity: internalVisibleCondition ? 1 : 0 + visible: opacity > 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + + background: null + + contentItem: Item { + id: contentItemBackground + implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding + implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding + + Rectangle { + id: backgroundRectangle + anchors.bottom: contentItemBackground.bottom + anchors.horizontalCenter: contentItemBackground.horizontalCenter + color: Appearance?.m3colors.colTooltip ?? "#3C4043" + radius: Appearance?.rounding.verysmall ?? 7 + width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0 + height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0 + clip: true + + Behavior on width { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + + StyledText { + id: tooltipTextObject + anchors.centerIn: parent + text: content + font.pixelSize: Appearance?.font.pixelSize.textSmall ?? 14 + font.hintingPreference: Font.PreferNoHinting // Prevent shaky text + color: Appearance?.m3colors.colOnTooltip ?? "#FFFFFF" + wrapMode: Text.Wrap + } + } + } +}
\ No newline at end of file diff --git a/config/quickshell/modules/overview/Overview.qml b/config/quickshell/modules/overview/Overview.qml new file mode 100644 index 00000000..ef5a49c3 --- /dev/null +++ b/config/quickshell/modules/overview/Overview.qml @@ -0,0 +1,279 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: overviewScope + property bool dontAutoCancelSearch: false + property bool searchEnabled: ConfigOptions.search.searchEnabled + + Variants { + id: overviewVariants + model: Quickshell.screens + PanelWindow { + id: root + required property var modelData + property string searchingText: "" + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + screen: modelData + visible: GlobalStates.overviewOpen + + WlrLayershell.namespace: "quickshell:overview" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + mask: Region { + item: GlobalStates.overviewOpen ? columnLayout : null + } + HyprlandWindow.visibleMask: Region { + item: GlobalStates.overviewOpen ? columnLayout : null + } + + anchors { + top: true + left: true + right: true + bottom: true + } + + HyprlandFocusGrab { + id: grab + windows: [ root ] + property bool canBeActive: root.monitorIsFocused + active: false + onCleared: () => { + if (!active) GlobalStates.overviewOpen = false + } + } + + Connections { + target: GlobalStates + function onOverviewOpenChanged() { + if (!GlobalStates.overviewOpen) { + if (overviewScope.searchEnabled && searchWidget) { + searchWidget.disableExpandAnimation() + } + overviewScope.dontAutoCancelSearch = false; + } else { + if (!overviewScope.dontAutoCancelSearch && overviewScope.searchEnabled && searchWidget) { + searchWidget.cancelSearch() + } + delayedGrabTimer.start() + } + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + if (!grab.canBeActive) return + grab.active = GlobalStates.overviewOpen + } + } + + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight + + function setSearchingText(text) { + if (overviewScope.searchEnabled && searchWidget) { + searchWidget.setSearchingText(text); + } + } + + ColumnLayout { + id: columnLayout + visible: GlobalStates.overviewOpen + anchors { + horizontalCenter: parent.horizontalCenter + top: ConfigOptions.overview.position === 0 ? parent.top : undefined + verticalCenter: ConfigOptions.overview.position === 1 ? parent.verticalCenter : undefined + bottom: ConfigOptions.overview.position === 2 ? parent.bottom : undefined + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + GlobalStates.overviewOpen = false; + } + } + + Item { + height: 1 + width: 1 + } + + // Conditionally render SearchWidget - only exists when searchEnabled is true + SearchWidget { + id: searchWidget + Layout.alignment: Qt.AlignHCenter + visible: overviewScope.searchEnabled + height: overviewScope.searchEnabled ? implicitHeight : 0 + Layout.preferredHeight: overviewScope.searchEnabled ? implicitHeight : 0 + onSearchingTextChanged: (text) => { + root.searchingText = searchingText + } + } + + Item { + Layout.preferredHeight: overviewScope.searchEnabled ? 0 : 20 + Layout.fillWidth: true + visible: !overviewScope.searchEnabled + } + + Loader { + id: overviewLoader + active: GlobalStates.overviewOpen + sourceComponent: OverviewWidget { + panelWindow: root + // Show OverviewWidget when search is disabled OR when search text is empty + visible: !overviewScope.searchEnabled || (root.searchingText == "") + } + } + } + } + } + + IpcHandler { + target: "overview" + + function toggle() { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + function close() { + GlobalStates.overviewOpen = false + } + function open() { + GlobalStates.overviewOpen = true + } + function toggleReleaseInterrupt() { + GlobalStates.superReleaseMightTrigger = false + } + // Add function to control search + function toggleSearch() { + overviewScope.searchEnabled = !overviewScope.searchEnabled + } + function enableSearch() { + overviewScope.searchEnabled = true + } + function disableSearch() { + overviewScope.searchEnabled = false + } + } + + GlobalShortcut { + name: "overviewToggle" + description: qsTr("Toggles overview on press") + + onPressed: { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + + GlobalShortcut { + name: "overviewClose" + description: qsTr("Closes overview") + + onPressed: { + GlobalStates.overviewOpen = false + } + } + + GlobalShortcut { + name: "overviewToggleRelease" + description: qsTr("Toggles overview on release") + + onPressed: { + GlobalStates.superReleaseMightTrigger = true + } + + onReleased: { + if (!GlobalStates.superReleaseMightTrigger) { + GlobalStates.superReleaseMightTrigger = true + return + } + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + + GlobalShortcut { + name: "overviewToggleReleaseInterrupt" + description: qsTr("Interrupts possibility of overview being toggled on release. ") + + qsTr("This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ") + + qsTr("To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.") + + onPressed: { + GlobalStates.superReleaseMightTrigger = false + } + } + + // Only enable clipboard/emoji shortcuts when search is enabled + GlobalShortcut { + name: "overviewClipboardToggle" + description: qsTr("Toggle clipboard query on overview widget") + + onPressed: { + if (!overviewScope.searchEnabled) return; // Skip if search disabled + + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.clipboard + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + + GlobalShortcut { + name: "overviewEmojiToggle" + description: qsTr("Toggle emoji query on overview widget") + + onPressed: { + if (!overviewScope.searchEnabled) return; // Skip if search disabled + + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.emojis + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + + // Optional: Add shortcut to toggle search functionality + GlobalShortcut { + name: "overviewToggleSearch" + description: qsTr("Toggle search functionality in overview") + + onPressed: { + overviewScope.searchEnabled = !overviewScope.searchEnabled + } + } +}
\ No newline at end of file diff --git a/config/quickshell/modules/overview/OverviewWidget.qml b/config/quickshell/modules/overview/OverviewWidget.qml new file mode 100644 index 00000000..2ea8d58a --- /dev/null +++ b/config/quickshell/modules/overview/OverviewWidget.qml @@ -0,0 +1,343 @@ +import "root:/" +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +Item { + id: root + required property var panelWindow + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) + readonly property var toplevels: ToplevelManager.toplevels + readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + property var windows: HyprlandData.windowList + property var windowByAddress: HyprlandData.windowByAddress + property var windowAddresses: HyprlandData.addresses + property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) + property real scale: ConfigOptions.overview.scale + property color activeBorderColor: Appearance.m3colors.m3accentSecondary + + property real workspaceImplicitWidth: Math.max(100, (monitorData?.transform % 2 === 1) ? + ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : + ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale)) + property real workspaceImplicitHeight: Math.max(60, (monitorData?.transform % 2 === 1) ? + ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : + ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)) + + property real workspaceNumberMargin: 80 + property real workspaceNumberSize: (ConfigOptions.overview.workspaceNumberSize > 0) + ? ConfigOptions.overview.workspaceNumberSize + : Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale + property int workspaceZ: 0 + property int windowZ: 1 + property int windowDraggingZ: 99999 + property real workspaceSpacing: 5 + + property int draggingFromWorkspace: -1 + property int draggingTargetWorkspace: -1 + + implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property Component windowComponent: OverviewWindow {} + property list<OverviewWindow> windowWidgets: [] + + // Shared wallpaper image - loaded once and reused + Image { + id: sharedWallpaper + source: Appearance.background_image || "" + visible: false // Hidden as it's only used as a source + cache: true + asynchronous: true + smooth: true + opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) + } + + StyledRectangularShadow { + target: overviewBackground + } + Rectangle { // Background + id: overviewBackground + property real padding: 10 + anchors.fill: parent + anchors.margins: Appearance.sizes.elevationMargin + border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.2) + border.width : 2 + + implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 + implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 + radius: Appearance.rounding.screenRounding * root.scale + padding + color: Appearance.colors.colLayer0 + + + ColumnLayout { // Workspaces + id: workspaceColumnLayout + + z: root.workspaceZ + anchors.centerIn: parent + spacing: workspaceSpacing + Repeater { + model: ConfigOptions.overview.numOfRows + delegate: RowLayout { + id: row + property int rowIndex: index + spacing: workspaceSpacing + + Repeater { // Workspace repeater + model: ConfigOptions.overview.numOfCols + Rectangle { // Workspace + id: workspace + property int colIndex: index + property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 + property color defaultWorkspaceColor: Appearance.colors.colLayer1 + property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) + property color hoveredBorderColor: Appearance.colors.colLayer2Hover + property bool hoveredWhileDragging: false + readonly property int padding: ConfigOptions.overview.windowPadding + + Layout.preferredWidth: root.workspaceImplicitWidth + Layout.preferredHeight: root.workspaceImplicitHeight + Layout.minimumWidth: 100 + Layout.minimumHeight: 60 + + width: root.workspaceImplicitWidth + height: root.workspaceImplicitHeight + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + clip: true + opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) + + + // Efficient wallpaper using ShaderEffectSource + Rectangle { + id: wallpaperContainer + anchors.fill: parent + anchors.margins: 2 // Leave space for border + radius: workspace.radius - 2 + color: workspace.defaultWorkspaceColor // Fallback color + clip: true + + ShaderEffectSource { + id: wallpaperSource + anchors.fill: parent + sourceItem: sharedWallpaper + visible: sharedWallpaper.status === Image.Ready + smooth: true + + // Scale to fill while preserving aspect ratio + transform: Scale { + property real aspectRatio: sharedWallpaper.implicitWidth / Math.max(1, sharedWallpaper.implicitHeight) + property real containerRatio: wallpaperContainer.width / Math.max(1, wallpaperContainer.height) + + xScale: aspectRatio > containerRatio ? + wallpaperContainer.height * aspectRatio / wallpaperContainer.width : 1 + yScale: aspectRatio > containerRatio ? + 1 : wallpaperContainer.width / (wallpaperContainer.height * aspectRatio) + + origin.x: wallpaperContainer.width / 2 + origin.y: wallpaperContainer.height / 2 + } + } + + // Fallback when image fails to load or isn't ready + Rectangle { + anchors.fill: parent + color: workspace.defaultWorkspaceColor + visible: sharedWallpaper.status !== Image.Ready + } + + // Optional: Add overlay for better text readability and hover effects + Rectangle { + anchors.fill: parent + color: hoveredWhileDragging ? hoveredWorkspaceColor : "black" + opacity: hoveredWhileDragging ? 0.3 : 0.1 + } + } + + // Border overlay - on top of wallpaper + Rectangle { + anchors.fill: parent + color: "transparent" + radius: parent.radius + border.width: 1 + border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.6) + z: 10 // Ensure it's on top + } + + StyledText { + // Position in top-left corner with padding + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: 12 // Padding from top edge + anchors.leftMargin: 12 // Padding from left edge + + text: workspaceValue + font.pixelSize: root.workspaceNumberSize * root.scale + font.weight: Font.DemiBold + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + z: 15 // Above border + } + + MouseArea { + id: workspaceArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + z: 20 // Above all visual elements + onClicked: { + if (root.draggingTargetWorkspace === -1) { + GlobalStates.overviewOpen = false + Hyprland.dispatch(`workspace ${workspaceValue}`) + } + } + } + + DropArea { + anchors.fill: parent + z: 20 // Same level as MouseArea + onEntered: { + root.draggingTargetWorkspace = workspaceValue + if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; + hoveredWhileDragging = true + } + onExited: { + hoveredWhileDragging = false + if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 + } + } + + } + } + } + } + } + + Item { // Windows & focused workspace indicator + id: windowSpace + anchors.centerIn: parent + implicitWidth: workspaceColumnLayout.implicitWidth + implicitHeight: workspaceColumnLayout.implicitHeight + + Repeater { // Window repeater + model: ScriptModel { + values: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) + }) + } + delegate: OverviewWindow { + id: window + windowData: windowByAddress[modelData] + monitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: root.workspaceImplicitWidth + availableWorkspaceHeight: root.workspaceImplicitHeight + + property bool atInitPosition: (initX == x && initY == y) + restrictToWorkspace: Drag.active || atInitPosition + + property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols + property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex + + Timer { + id: updateWindowPosition + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + } + } + + z: atInitPosition ? root.windowZ : root.windowDraggingZ + Drag.hotSpot.x: targetWindowWidth / 2 + Drag.hotSpot.y: targetWindowHeight / 2 + MouseArea { + id: dragArea + anchors.fill: parent + hoverEnabled: true + onEntered: hovered = true + onExited: hovered = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + drag.target: parent + onPressed: { + root.draggingFromWorkspace = windowData?.workspace.id + window.pressed = true + window.Drag.active = true + window.Drag.source = window + } + onReleased: { + const targetWorkspace = root.draggingTargetWorkspace + window.pressed = false + window.Drag.active = false + root.draggingFromWorkspace = -1 + if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { + Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) + updateWindowPosition.restart() + } + else { + window.x = window.initX + window.y = window.initY + } + } + onClicked: (event) => { + if (!windowData) return; + + if (event.button === Qt.LeftButton) { + GlobalStates.overviewOpen = false + Hyprland.dispatch(`focuswindow address:${windowData.address}`) + event.accepted = true + } else if (event.button === Qt.MiddleButton) { + Hyprland.dispatch(`closewindow address:${windowData.address}`) + event.accepted = true + } + } + + StyledToolTip { + extraVisibleCondition: false + alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active + content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` + } + } + } + } + + Rectangle { // Focused workspace indicator + id: focusedWorkspaceIndicator + property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) + property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.numOfCols) + property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.numOfCols + x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex + y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex + z: root.windowZ + width: Math.max(100, root.workspaceImplicitWidth) + height: Math.max(60, root.workspaceImplicitHeight) + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: root.activeBorderColor + visible: width > 0 && height > 0 && activeWorkspaceInGroup > 0 + Behavior on x { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + } + } +}
\ No newline at end of file diff --git a/config/quickshell/modules/overview/OverviewWindow.qml b/config/quickshell/modules/overview/OverviewWindow.qml new file mode 100644 index 00000000..449a98c4 --- /dev/null +++ b/config/quickshell/modules/overview/OverviewWindow.qml @@ -0,0 +1,94 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import Quickshell.Hyprland + +Rectangle { // Window + id: root + property var windowData + property var monitorData + property var scale + property var availableWorkspaceWidth + property var availableWorkspaceHeight + property bool restrictToWorkspace: true + property real initX: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + property real initY: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + property real xOffset: 0 + property real yOffset: 0 + + property var targetWindowWidth: windowData?.size[0] * scale + property var targetWindowHeight: windowData?.size[1] * scale + property bool hovered: false + property bool pressed: false + + property var iconToWindowRatio: 0.35 + property var xwaylandIndicatorToIconRatio: 0.35 + property var iconToWindowRatioCompact: 0.6 + property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") + property bool compactMode: Appearance.font.pixelSize.textSmall * 4 > targetWindowHeight || Appearance.font.pixelSize.textSmall * 4 > targetWindowWidth + + property bool indicateXWayland: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false + + x: initX + y: initY + width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) + height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)) + + radius: Appearance.rounding.windowRounding * root.scale + color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 + // border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) + border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.4) + border.pixelAligned : false + border.width : 2 + + Behavior on x { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on width { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.font.pixelSize.textSmall * 0.5 + + IconImage { + id: windowIcon + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + + Behavior on implicitSize { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + } + + StyledText { + Layout.leftMargin: 10 + Layout.rightMargin: 10 + visible: !compactMode + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.textSmall + font.italic: indicateXWayland ? true : false + elide: Text.ElideRight + text: windowData?.title ?? "" + } + } +}
\ No newline at end of file diff --git a/config/quickshell/modules/overview/SearchItem.qml b/config/quickshell/modules/overview/SearchItem.qml new file mode 100644 index 00000000..1357d03c --- /dev/null +++ b/config/quickshell/modules/overview/SearchItem.qml @@ -0,0 +1,220 @@ +// pragma NativeMethodBehavior: AcceptThisObject +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +RippleButton { + id: root + property var entry + property string query + property bool entryShown: entry?.shown ?? true + property string itemType: entry?.type + property string itemName: entry?.name + property string itemIcon: entry?.icon ?? "" + property var itemExecute: entry?.execute + property string fontType: entry?.fontType ?? "uiFont" + property string itemClickActionName: entry?.clickActionName + property string bigText: entry?.bigText ?? "" + property string materialSymbol: entry?.materialSymbol ?? "" + property string cliphistRawString: entry?.cliphistRawString ?? "" + + property string highlightPrefix: `<u><font color="${Appearance.colors.colPrimary}">` + property string highlightSuffix: `</font></u>` + function highlightContent(content, query) { + if (!query || query.length === 0 || content == query || fontType === "codeFont") + return StringUtils.escapeHtml(content); + + let contentLower = content.toLowerCase(); + let queryLower = query.toLowerCase(); + + let result = ""; + let lastIndex = 0; + let qIndex = 0; + + for (let i = 0; i < content.length && qIndex < query.length; i++) { + if (contentLower[i] === queryLower[qIndex]) { + // Add non-highlighted part (escaped) + if (i > lastIndex) + result += StringUtils.escapeHtml(content.slice(lastIndex, i)); + // Add highlighted character (escaped) + result += root.highlightPrefix + StringUtils.escapeHtml(content[i]) + root.highlightSuffix; + lastIndex = i + 1; + qIndex++; + } + } + // Add the rest of the string (escaped) + if (lastIndex < content.length) + result += StringUtils.escapeHtml(content.slice(lastIndex)); + + return result; + } + property string displayContent: highlightContent(root.itemName, root.query) + + property list<string> urls: { + if (!root.itemName) return []; + // Regular expression to match URLs + const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi; + const matches = root.itemName?.match(urlRegex) + ?.filter(url => !url.includes("…")) // Elided = invalid + return matches ? matches : []; + } + + visible: root.entryShown + property int horizontalMargin: 10 + property int buttonHorizontalPadding: 10 + property int buttonVerticalPadding: 5 + property bool keyboardDown: false + + implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2 + implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2 + buttonRadius: Appearance.rounding.normal + colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : + ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : + ColorUtils.transparentize(Appearance.m3colors.m3layerBackground3, 1)) + colBackgroundHover: Appearance.colors.colLayer1Hover + colRipple: Appearance.colors.colLayer1Active + + background { + anchors.fill: root + anchors.leftMargin: root.horizontalMargin + anchors.rightMargin: root.horizontalMargin + } + + PointingHandInteraction {} + onClicked: { + root.itemExecute() + Hyprland.dispatch("global quickshell:overviewClose") + } + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = true + root.clicked() + event.accepted = true; + } + } + Keys.onReleased: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = false + event.accepted = true; + } + } + + RowLayout { + id: rowLayout + spacing: iconLoader.sourceComponent === null ? 0 : 10 + anchors.fill: parent + anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding + anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding + + // Icon + Loader { + id: iconLoader + active: true + sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : + root.bigText ? bigTextComponent : + root.itemIcon !== "" ? iconImageComponent : + null + } + + Component { + id: iconImageComponent + IconImage { + source: Quickshell.iconPath(root.itemIcon, "image-missing") + width: 35 + height: 35 + } + } + + Component { + id: materialSymbolComponent + MaterialSymbol { + text: root.materialSymbol + iconSize: 30 + color: Appearance.m3colors.m3surfaceText + } + } + + Component { + id: bigTextComponent + StyledText { + text: root.bigText + font.pixelSize: Appearance.font.pixelSize.textLarge + color: Appearance.m3colors.m3surfaceText + } + } + + // Main text + ColumnLayout { + id: contentColumn + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 0 + StyledText { + font.pixelSize: Appearance.font.pixelSize.textSmall + color: Appearance.colors.colSubtext + visible: root.itemType && root.itemType != qsTr("App") + text: root.itemType + } + RowLayout { + Loader { // Checkmark for copied clipboard entry + visible: itemName == Quickshell.clipboardText && root.cliphistRawString + active: itemName == Quickshell.clipboardText && root.cliphistRawString + sourceComponent: Rectangle { + implicitWidth: activeText.implicitHeight + implicitHeight: activeText.implicitHeight + radius: Appearance.rounding.full + color: Appearance.colors.colPrimary + MaterialSymbol { + id: activeText + anchors.centerIn: parent + text: "check" + font.pixelSize: Appearance.font.pixelSize.textMedium + color: Appearance.m3colors.m3accentPrimaryText + } + } + } + StyledText { // Item name/content + Layout.fillWidth: true + id: nameText + textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work + font.pixelSize: Appearance.font.pixelSize.textBase + font.family: Appearance.font.family[root.fontType] + color: Appearance.m3colors.m3surfaceText + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + text: `${root.displayContent}` + } + } + Loader { // Clipboard image preview + active: root.cliphistRawString && /^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(root.cliphistRawString) + sourceComponent: CliphistImage { + Layout.fillWidth: true + entry: root.cliphistRawString + maxWidth: contentColumn.width + maxHeight: 140 + } + } + } + + // Action text + StyledText { + Layout.fillWidth: false + visible: (root.hovered || root.focus) + id: clickAction + font.pixelSize: Appearance.font.pixelSize.textMedium + color: Appearance.colors.colSubtext + horizontalAlignment: Text.AlignRight + text: root.itemClickActionName + } + } +} diff --git a/config/quickshell/modules/overview/SearchWidget.qml b/config/quickshell/modules/overview/SearchWidget.qml new file mode 100644 index 00000000..f84aa558 --- /dev/null +++ b/config/quickshell/modules/overview/SearchWidget.qml @@ -0,0 +1,425 @@ +import "root:/" +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import Qt5Compat.GraphicalEffects +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Item { // Wrapper + id: root + readonly property string xdgConfigHome: Directories.config + property string searchingText: "" + property bool showResults: searchingText != "" + property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2 + implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property string mathResult: "" + + function disableExpandAnimation() { + searchWidthBehavior.enabled = false; + } + + function cancelSearch() { + searchInput.selectAll() + root.searchingText = "" + searchWidthBehavior.enabled = true; + } + + function setSearchingText(text) { + searchInput.text = text; + root.searchingText = text; + } + + property var searchActions: [ + { + action: "img", + execute: () => { + executor.executeCommand(Directories.wallpaperSwitchScriptPath) + } + }, + { + action: "dark", + execute: () => { + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`) + } + }, + { + action: "light", + execute: () => { + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`) + } + }, + { + action: "accentcolor", + execute: (args) => { + executor.executeCommand( + `${Directories.wallpaperSwitchScriptPath} --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` + ) + } + }, + { + action: "todo", + execute: (args) => { + Todo.addTask(args) + } + }, + ] + + function focusFirstItemIfNeeded() { + if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item + } + + Timer { + id: nonAppResultsTimer + interval: ConfigOptions.search.nonAppResultDelay + onTriggered: { + mathProcess.calculateExpression(root.searchingText); + } + } + + Process { + id: mathProcess + property list<string> baseCommand: ["qalc", "-t"] + function calculateExpression(expression) { + // mathProcess.running = false + mathProcess.command = baseCommand.concat(expression) + mathProcess.running = true + } + stdout: SplitParser { + onRead: data => { + root.mathResult = data + root.focusFirstItemIfNeeded() + } + } + } + + Process { + id: executor + property list<string> baseCommand: ["bash", "-c"] + function executeCommand(command) { + executor.command = baseCommand.concat( + `${command} || ${ConfigOptions.apps.terminal} fish -C 'echo "${qsTr("Searching for package with that command")}..." && pacman -F ${command}'` + ) + executor.startDetached() + } + } + + Keys.onPressed: (event) => { + // Prevent Esc and Backspace from registering + if (event.key === Qt.Key_Escape) return; + + // Handle Backspace: focus and delete character if not focused + if (event.key === Qt.Key_Backspace) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + if (event.modifiers & Qt.ControlModifier) { + // Delete word before cursor + let text = searchInput.text; + let pos = searchInput.cursorPosition; + if (pos > 0) { + // Find the start of the previous word + let left = text.slice(0, pos); + let match = left.match(/(\s*\S+)\s*$/); + let deleteLen = match ? match[0].length : 1; + searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); + searchInput.cursorPosition = pos - deleteLen; + } + } else { + // Delete character before cursor if any + if (searchInput.cursorPosition > 0) { + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition - 1) + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition -= 1; + } + } + // Always move cursor to end after programmatic edit + searchInput.cursorPosition = searchInput.text.length; + event.accepted = true; + } + // If already focused, let TextField handle it + return; + } + + // Only handle visible printable characters (ignore control chars, arrows, etc.) + if ( + event.text && + event.text.length === 1 && + event.key !== Qt.Key_Enter && + event.key !== Qt.Key_Return && + event.text.charCodeAt(0) >= 0x20 // ignore control chars like Backspace, Tab, etc. + ) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + // Insert the character at the cursor position + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) + + event.text + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition += 1; + event.accepted = true; + } + } + } + + StyledRectangularShadow { + target: searchWidgetContent + } + Rectangle { // Background + id: searchWidgetContent + anchors.centerIn: parent + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight + radius: Appearance.rounding.large + color: Appearance.colors.colLayer0 + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 0 + + // clip: true + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: searchWidgetContent.width + height: searchWidgetContent.width + radius: searchWidgetContent.radius + } + } + + RowLayout { + id: searchBar + spacing: 5 + MaterialSymbol { + id: searchIcon + Layout.leftMargin: 15 + iconSize: Appearance.font.pixelSize.iconLarge + color: Appearance.m3colors.m3surfaceText + text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : '' + } + TextField { // Search box + id: searchInput + + focus: GlobalStates.overviewOpen + Layout.rightMargin: 15 + padding: 15 + renderType: Text.NativeRendering + font { + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 + hintingPreference: Font.PreferFullHinting + } + color: activeFocus ? Appearance.m3colors.m3surfaceText : Appearance.m3colors.m3secondaryText + selectedTextColor: Appearance.m3colors.m3selectionText + selectionColor: Appearance.m3colors.m3selectionBackground + placeholderText: qsTr("Search, calculate or run") + placeholderTextColor: Appearance.m3colors.m3borderPrimary + implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth + + Behavior on implicitWidth { + id: searchWidthBehavior + enabled: false + NumberAnimation { + duration: 300 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + onTextChanged: root.searchingText = text + + onAccepted: { + if (appResults.count > 0) { + // Get the first visible delegate and trigger its click + let firstItem = appResults.itemAtIndex(0); + if (firstItem && firstItem.clicked) { + firstItem.clicked(); + } + } + } + + background: null + + cursorDelegate: Rectangle { + width: 1 + color: searchInput.activeFocus ? Appearance.colors.colPrimary : "transparent" + radius: 1 + } + } + } + + Rectangle { // Separator + visible: root.showResults + Layout.fillWidth: true + height: 1 + color: Appearance.m3colors.m3borderSecondary + } + + ListView { // App results + id: appResults + visible: root.showResults + Layout.fillWidth: true + implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) + clip: true + topMargin: 10 + bottomMargin: 10 + spacing: 2 + KeyNavigation.up: searchBar + highlightMoveDuration : 100 + + onFocusChanged: { + if(focus) appResults.currentIndex = 1; + } + + Connections { + target: root + function onSearchingTextChanged() { + if (appResults.count > 0) + appResults.currentIndex = 0; + } + } + + model: ScriptModel { + id: model + values: { // Search results are handled here + ////////////////// Skip? ////////////////// + if(root.searchingText == "") return []; + + ///////////// Special cases /////////////// + if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); + return Cliphist.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, + execute: () => { + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); + } + }; + }).filter(Boolean); + } + if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); + return Emojis.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + bigText: entry.match(/^\s*(\S+)/)?.[1] || "", + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: "Emoji", + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(entry.match(/^\s*(\S+)/)?.[1])}'`); + } + }; + }).filter(Boolean); + } + + + ////////////////// Init /////////////////// + nonAppResultsTimer.restart(); + const mathResultObject = { + name: root.mathResult, + clickActionName: qsTr("Copy"), + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.mathResult)}'`) + } + } + const commandResultObject = { + name: searchingText.replace("file://", ""), + clickActionName: qsTr("Run"), + type: qsTr("Run command"), + fontType: "monospace", + materialSymbol: 'terminal', + execute: () => { + executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText.replace("file://", "")}'` : root.searchingText.replace("file://", "")); + } + } + const launcherActionObjects = root.searchActions + .map(action => { + const actionString = `${ConfigOptions.search.prefix.action}${action.action}`; + if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { + return { + name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, + clickActionName: qsTr("Run"), + type: qsTr("Action"), + materialSymbol: 'settings_suggest', + execute: () => { + action.execute(root.searchingText.split(" ").slice(1).join(" ")) + }, + }; + } + return null; + }) + .filter(Boolean); + + let result = []; + + //////////////// Apps ////////////////// + result = result.concat( + AppSearch.fuzzyQuery(root.searchingText) + .map((entry) => { + entry.clickActionName = qsTr("Launch"); + entry.type = qsTr("App"); + return entry; + }) + ); + + ////////// Launcher actions //////////// + result = result.concat(launcherActionObjects); + + /////////// Math result & command ////////// + const startsWithNumber = /^\d/.test(root.searchingText); + if (startsWithNumber) { + result.push(mathResultObject); + result.push(commandResultObject); + } else { + result.push(commandResultObject); + result.push(mathResultObject); + } + + ///////////////// Web search //////////////// + result.push({ + name: root.searchingText, + clickActionName: qsTr("Search"), + type: qsTr("Search the web"), + materialSymbol: 'travel_explore', + execute: () => { + let url = ConfigOptions.search.engineBaseUrl + root.searchingText + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; + } + Qt.openUrlExternally(url); + } + }); + + return result; + } + } + + delegate: SearchItem { // The selectable item for each search result + required property var modelData + anchors.left: parent?.left + anchors.right: parent?.right + entry: modelData + query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? + root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) : + root.searchingText; + } + } + + } + } +}
\ No newline at end of file diff --git a/config/quickshell/qml_color.json b/config/quickshell/qml_color.json new file mode 100644 index 00000000..888ba3e6 --- /dev/null +++ b/config/quickshell/qml_color.json @@ -0,0 +1,17 @@ +{ + "windowBackground": "#0f0f15", + "primaryText": "#bac2de", + "layerBackground1": "#1F1A1F", + "layerBackground2": "#231E23", + "layerBackground3": "#2D282E", + "surfaceText": "#EAE0E7", + "secondaryText": "#CFC3CD", + "borderPrimary": "#cba6f7", + "shadowColor": "#000000", + "accentPrimary": "#6750A4", + "accentSecondary": "#D5C0D7", + "selectionBackground": "#534457", + "accentPrimaryText": "#FFFFFF", + "selectionText": "#F2DCF3", + "borderSecondary": "#4C444D" +}
\ No newline at end of file diff --git a/config/quickshell/services/AppSearch.qml b/config/quickshell/services/AppSearch.qml new file mode 100644 index 00000000..876df183 --- /dev/null +++ b/config/quickshell/services/AppSearch.qml @@ -0,0 +1,116 @@ +pragma Singleton + +import "root:/modules/common" +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/levendist.js" as Levendist +import Quickshell +import Quickshell.Io + +/** + * - Eases fuzzy searching for applications by name + * - Guesses icon name for window class name + */ +Singleton { + id: root + property bool sloppySearch: ConfigOptions?.search.sloppy ?? false + property real scoreThreshold: 0.2 + property var substitutions: ({ + "code-url-handler": "visual-studio-code", + "Code": "visual-studio-code", + "gnome-tweaks": "org.gnome.tweaks", + "pavucontrol-qt": "pavucontrol", + "wps": "wps-office2019-kprometheus", + "wpsoffice": "wps-office2019-kprometheus", + "footclient": "foot", + "zen": "zen-browser", + }) + property var regexSubstitutions: [ + { + "regex": /^steam_app_(\\d+)$/, + "replace": "steam_icon_$1" + }, + { + "regex": /Minecraft.*/, + "replace": "minecraft" + }, + { + "regex": /.*polkit.*/, + "replace": "system-lock-screen" + }, + { + "regex": /gcr.prompter/, + "replace": "system-lock-screen" + } + ] + + readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values) + .sort((a, b) => a.name.localeCompare(b.name)) + + readonly property var preppedNames: list.map(a => ({ + name: Fuzzy.prepare(`${a.name} `), + entry: a + })) + + function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work + if (root.sloppySearch) { + const results = list.map(obj => ({ + entry: obj, + score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold) + .sort((a, b) => b.score - a.score) + return results + .map(item => item.entry) + } + + return Fuzzy.go(search, preppedNames, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry + }); + } + + function iconExists(iconName) { + return (Quickshell.iconPath(iconName, true).length > 0) + && !iconName.includes("image-missing"); + } + + function guessIcon(str) { + if (!str || str.length == 0) return "image-missing"; + + // Normal substitutions + if (substitutions[str]) + return substitutions[str]; + + // Regex substitutions + for (let i = 0; i < regexSubstitutions.length; i++) { + const substitution = regexSubstitutions[i]; + const replacedName = str.replace( + substitution.regex, + substitution.replace, + ); + if (replacedName != str) return replacedName; + } + + // If it gets detected normally, no need to guess + if (iconExists(str)) return str; + + let guessStr = str; + // Guess: Take only app name of reverse domain name notation + guessStr = str.split('.').slice(-1)[0].toLowerCase(); + if (iconExists(guessStr)) return guessStr; + // Guess: normalize to kebab case + guessStr = str.toLowerCase().replace(/\s+/g, "-"); + if (iconExists(guessStr)) return guessStr; + // Guess: First fuzze desktop entry match + const searchResults = root.fuzzyQuery(str); + if (searchResults.length > 0) { + const firstEntry = searchResults[0]; + guessStr = firstEntry.icon + if (iconExists(guessStr)) return guessStr; + } + + // Give up + return str; + } +} diff --git a/config/quickshell/services/ConfigLoader.qml b/config/quickshell/services/ConfigLoader.qml new file mode 100644 index 00000000..d3fb4e26 --- /dev/null +++ b/config/quickshell/services/ConfigLoader.qml @@ -0,0 +1,146 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import "root:/modules/common/functions/file_utils.js" as FileUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/object_utils.js" as ObjectUtils +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import Qt.labs.platform + +/** + * Loads and manages the shell configuration file. + * The config file is by default at XDG_CONFIG_HOME/quickshell/config.json. + * Automatically reloaded when the file changes, but does not provide a way to save changes. + */ +Singleton { + id: root + property string filePath: Directories.shellConfigPath + property bool firstLoad: true + + function loadConfig() { + configFileView.reload() + } + + function applyConfig(fileContent) { + try { + const json = JSON.parse(fileContent); + + // Extract font configuration if it exists + let fontConfig = null; + let configForOptions = {}; + + // Copy all properties except font to configForOptions + for (let key in json) { + if (key !== "font") { + configForOptions[key] = json[key]; + } else { + fontConfig = json[key]; + } + } + + // Apply the non-font configuration to ConfigOptions + ObjectUtils.applyToQtObject(ConfigOptions, configForOptions); + + // Apply font configuration to Appearance if it exists + if (fontConfig && typeof Appearance !== 'undefined') { + if (fontConfig.family && Appearance.font && Appearance.font.family) { + ObjectUtils.applyToQtObject(Appearance.font.family, fontConfig.family); + } + if (fontConfig.pixelSize && Appearance.font && Appearance.font.pixelSize) { + ObjectUtils.applyToQtObject(Appearance.font.pixelSize, fontConfig.pixelSize); + } + } + + if (root.firstLoad) { + root.firstLoad = false; + } else { + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) + } + } catch (e) { + console.error("[ConfigLoader] Error reading file:", e); + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`) + return; + } + } + + function setLiveConfigValue(nestedKey, value) { + let keys = nestedKey.split("."); + let targetObject = ConfigOptions; + + // Check if this is a font-related configuration + if (keys[0] === "font") { + targetObject = Appearance; + } + + let obj = targetObject; + let parents = [obj]; + + // Traverse and collect parent objects + for (let i = 0; i < keys.length - 1; ++i) { + if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { + obj[keys[i]] = {}; + } + obj = obj[keys[i]]; + parents.push(obj); + } + + // Convert value to correct type using JSON.parse when safe + let convertedValue = value; + if (typeof value === "string") { + let trimmed = value.trim(); + if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) { + try { + convertedValue = JSON.parse(trimmed); + } catch (e) { + convertedValue = value; + } + } + } + + console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`); + obj[keys[keys.length - 1]] = convertedValue; + } + + function saveConfig() { + const plainConfig = ObjectUtils.toPlainObject(ConfigOptions); + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`) + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + root.applyConfig(configFileView.text()) + } + } + + FileView { + id: configFileView + path: Qt.resolvedUrl(root.filePath) + watchChanges: true + onFileChanged: { + console.log("[ConfigLoader] File changed, reloading...") + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = configFileView.text() + root.applyConfig(fileContent) + } + onLoadFailed: (error) => { + if(error == FileViewError.FileNotFound) { + console.log("[ConfigLoader] File not found, creating new file.") + root.saveConfig() + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`) + } else { + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`) + } + } + } +}
\ No newline at end of file diff --git a/config/quickshell/services/HyprlandData.qml b/config/quickshell/services/HyprlandData.qml new file mode 100644 index 00000000..2b88ad9c --- /dev/null +++ b/config/quickshell/services/HyprlandData.qml @@ -0,0 +1,69 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +/** + * Provides access to some Hyprland data not available in Quickshell.Hyprland. + */ +Singleton { + id: root + property var windowList: [] + property var addresses: [] + property var windowByAddress: ({}) + property var monitors: [] + + function updateWindowList() { + getClients.running = true + getMonitors.running = true + } + + Component.onCompleted: { + updateWindowList() + } + + Connections { + target: Hyprland + + function onRawEvent(event) { + // Filter out redundant old v1 events for the same thing + if(event.name in [ + "activewindow", "focusedmon", "monitoradded", + "createworkspace", "destroyworkspace", "moveworkspace", + "activespecial", "movewindow", "windowtitle" + ]) return ; + updateWindowList() + } + } + + Process { + id: getClients + command: ["bash", "-c", "hyprctl clients -j | jq -c"] + stdout: SplitParser { + onRead: (data) => { + root.windowList = JSON.parse(data) + let tempWinByAddress = {} + for (var i = 0; i < root.windowList.length; ++i) { + var win = root.windowList[i] + tempWinByAddress[win.address] = win + } + root.windowByAddress = tempWinByAddress + root.addresses = root.windowList.map((win) => win.address) + } + } + } + Process { + id: getMonitors + command: ["bash", "-c", "hyprctl monitors -j | jq -c"] + stdout: SplitParser { + onRead: (data) => { + root.monitors = JSON.parse(data) + } + } + } +} + diff --git a/config/quickshell/services/HyprlandKeybinds.qml b/config/quickshell/services/HyprlandKeybinds.qml new file mode 100644 index 00000000..189ba76d --- /dev/null +++ b/config/quickshell/services/HyprlandKeybinds.qml @@ -0,0 +1,73 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import "root:/modules/common/functions/file_utils.js" as FileUtils +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +/** + * A service that provides access to Hyprland keybinds. + * Uses the `get_keybinds.py` script to parse comments in config files in a certain format and convert to JSON. + */ +Singleton { + id: root + property string keybindParserPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/hyprland/get_keybinds.py`) + property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/keybinds.conf`) + property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/custom/keybinds.conf`) + property var defaultKeybinds: {"children": []} + property var userKeybinds: {"children": []} + property var keybinds: ({ + children: [ + ...(defaultKeybinds.children ?? []), + ...(userKeybinds.children ?? []), + ] + }) + + Connections { + target: Hyprland + + function onRawEvent(event) { + if (event.name == "configreloaded") { + getDefaultKeybinds.running = true + getUserKeybinds.running = true + } + } + } + + Process { + id: getDefaultKeybinds + running: true + command: [root.keybindParserPath, "--path", root.defaultKeybindConfigPath,] + + stdout: SplitParser { + onRead: data => { + try { + root.defaultKeybinds = JSON.parse(data) + } catch (e) { + console.error("[CheatsheetKeybinds] Error parsing keybinds:", e) + } + } + } + } + + Process { + id: getUserKeybinds + running: true + command: [root.keybindParserPath, "--path", root.userKeybindConfigPath] + + stdout: SplitParser { + onRead: data => { + try { + root.userKeybinds = JSON.parse(data) + } catch (e) { + console.error("[CheatsheetKeybinds] Error parsing keybinds:", e) + } + } + } + } +} + diff --git a/config/quickshell/services/MaterialThemeLoader.qml b/config/quickshell/services/MaterialThemeLoader.qml new file mode 100644 index 00000000..2d67ad5b --- /dev/null +++ b/config/quickshell/services/MaterialThemeLoader.qml @@ -0,0 +1,58 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io + +/** + * Automatically reloads generated material colors. + * It is necessary to run reapplyTheme() on startup because Singletons are lazily loaded. + */ +Singleton { + id: root + property string filePath: Directories.generatedMaterialThemePath + + function reapplyTheme() { + themeFileView.reload() + } + + function applyColors(fileContent) { + const json = JSON.parse(fileContent) + for (const key in json) { + if (json.hasOwnProperty(key)) { + // Convert snake_case to CamelCase + const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) + const m3Key = `m3${camelCaseKey}` + Appearance.m3colors[m3Key] = json[key] + } + } + + Appearance.m3colors.darkmode = (Appearance.m3colors.m3windowBackground.hslLightness < 0.5) + } + + Timer { + id: delayedFileRead + interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 + repeat: false + running: false + onTriggered: { + root.applyColors(themeFileView.text()) + } + } + + FileView { + id: themeFileView + path: Qt.resolvedUrl(root.filePath) + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = themeFileView.text() + root.applyColors(fileContent) + } + } +} diff --git a/config/quickshell/shell.qml b/config/quickshell/shell.qml new file mode 100644 index 00000000..b14c8773 --- /dev/null +++ b/config/quickshell/shell.qml @@ -0,0 +1,26 @@ +//@ pragma UseQApplication +//@ pragma Env QS_NO_RELOAD_POPUP=1 +//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic + +import "./modules/common/" +import "./modules/overview/" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "./services/" + +ShellRoot { + // Enable/disable modules here. False = not loaded at all, so rest assured + // no unnecessary stuff will take up memory if you decide to only use, say, the overview. + property bool enableOverview: true + + // Force initialization of some singletons + Component.onCompleted: { + MaterialThemeLoader.reapplyTheme() + ConfigLoader.loadConfig() + } + + Loader { active: enableOverview; sourceComponent: Overview {} } + +}
\ No newline at end of file diff --git a/config/rofi/config-rofi-theme.rasi b/config/rofi/config-rofi-theme.rasi index 652cd94c..0d77f857 100644 --- a/config/rofi/config-rofi-theme.rasi +++ b/config/rofi/config-rofi-theme.rasi @@ -11,8 +11,17 @@ window { /* ---- Mainbox ---- */ mainbox { - children: - [ "inputbar", "listview"]; + children: [ "inputbar", "message", "listview" ]; +} + +/* ---- Custombox ---- */ +/* Override custombox to ensure listview is visible for themes that use it */ +custombox { + spacing: 0px; + background-color: inherit; + text-color: inherit; + orientation: vertical; + children: [ "listview" ]; } /* ---- Entry input ---- */ @@ -42,3 +51,15 @@ element-text { margin: 0px; padding: 0px; } + +/* ---- Message ---- */ +message { + padding: 10px; + border-radius: 10px; + background-color: inherit; +} + +textbox { + text-color: inherit; + background-color: inherit; +} diff --git a/config/wallust/templates/qml_color.json b/config/wallust/templates/qml_color.json new file mode 100644 index 00000000..03565181 --- /dev/null +++ b/config/wallust/templates/qml_color.json @@ -0,0 +1,17 @@ +{ + "windowBackground": "#0f0f15", + "primaryText": "#bac2de", + "layerBackground1": "{{color7}}", + "layerBackground2": "{{color6}}", + "layerBackground3": "#2D282E", + "surfaceText": "#EAE0E7", + "secondaryText": "#CFC3CD", + "borderPrimary": "{{color7}}", + "shadowColor": "#000000", + "accentPrimary": "{{color7}}", + "accentSecondary": "#{{color7}}", + "selectionBackground": "{{color7}}", + "accentPrimaryText": "#FFFFFF", + "selectionText": "#F2DCF3", + "borderSecondary": "#{{color5}}" +}
\ No newline at end of file diff --git a/config/wallust/wallust.toml b/config/wallust/wallust.toml index a7f66721..d1f40ab2 100644 --- a/config/wallust/wallust.toml +++ b/config/wallust/wallust.toml @@ -49,6 +49,9 @@ waybar.target = '~/.config/waybar/wallust/colors-waybar.css' kitty.template = 'colors-kitty.conf' kitty.target = '~/.config/kitty/kitty-themes/01-Wallust.conf' +quickshell.template = 'qml_color.json' +quickshell.target = '~/.config/quickshell/qml_color.json' + #swaync.template = 'colors-swaync.css' #swaync.target = '~/.config/swaync/wallust/colors-wallust.css' diff --git a/config/waybar/ModulesCustom b/config/waybar/ModulesCustom index 088cea1c..dddc5ccc 100644 --- a/config/waybar/ModulesCustom +++ b/config/waybar/ModulesCustom @@ -17,6 +17,13 @@ "tooltip": true, }, +"custom/hyprpicker": { + "format": "", + "on-click": "hyprpicker | wl-copy", + "tooltip": true, + "tooltip-format": "Hyprpicker", +}, + "custom/file_manager": { "format": " ", "on-click": "$HOME/.config/hypr/scripts/WaybarScripts.sh --files", diff --git a/config/waybar/configs/[BOT & Left] SouthWest b/config/waybar/configs/[BOT & Left] SouthWest index 9bbe10cd..a039f040 100644 --- a/config/waybar/configs/[BOT & Left] SouthWest +++ b/config/waybar/configs/[BOT & Left] SouthWest @@ -42,7 +42,8 @@ "battery", "backlight", "pulseaudio", - //"wireplumber", + //"wireplumber", + "power-profiles-daemon", "pulseaudio#microphone", "keyboard-state", "custom/power", diff --git a/config/waybar/configs/[BOT & Right] SouthEast b/config/waybar/configs/[BOT & Right] SouthEast index f08fb507..9a58e952 100644 --- a/config/waybar/configs/[BOT & Right] SouthEast +++ b/config/waybar/configs/[BOT & Right] SouthEast @@ -42,7 +42,8 @@ "battery", "backlight", "pulseaudio", - //"wireplumber", + //"wireplumber", + "power-profiles-daemon", "pulseaudio#microphone", "keyboard-state", "custom/power", diff --git a/config/waybar/configs/[BOT] Camellia b/config/waybar/configs/[BOT] Camellia index 30eba9c6..f0a52329 100644 --- a/config/waybar/configs/[BOT] Camellia +++ b/config/waybar/configs/[BOT] Camellia @@ -41,6 +41,7 @@ "backlight/slider", "custom/speaker", "pulseaudio/slider", + "power-profiles-daemon", "battery", "clock#3", "network"], diff --git a/config/waybar/configs/[BOT] Chrysanthemum b/config/waybar/configs/[BOT] Chrysanthemum index ca10079b..3bc401c9 100644 --- a/config/waybar/configs/[BOT] Chrysanthemum +++ b/config/waybar/configs/[BOT] Chrysanthemum @@ -36,6 +36,7 @@ "modules-right": [ "pulseaudio#1", "backlight#2", + "power-profiles-daemon", "battery"], }
\ No newline at end of file diff --git a/config/waybar/configs/[BOT] Gardenia b/config/waybar/configs/[BOT] Gardenia index 48c59e34..42355cac 100644 --- a/config/waybar/configs/[BOT] Gardenia +++ b/config/waybar/configs/[BOT] Gardenia @@ -36,7 +36,8 @@ "modules-right": [ "pulseaudio#1", - "backlight#2", + "backlight#2", + "power-profiles-daemon", "battery" ], diff --git a/config/waybar/configs/[BOT] Sleek b/config/waybar/configs/[BOT] Sleek index c5177d86..0dda9b35 100644 --- a/config/waybar/configs/[BOT] Sleek +++ b/config/waybar/configs/[BOT] Sleek @@ -39,6 +39,7 @@ "custom/separator#blank_2", "pulseaudio", "custom/separator#blank", + "power-profiles-daemon", "custom/power", ], diff --git a/config/waybar/configs/[TOP & BOT] SummitSplit b/config/waybar/configs/[TOP & BOT] SummitSplit index 35298c39..03c8e81b 100644 --- a/config/waybar/configs/[TOP & BOT] SummitSplit +++ b/config/waybar/configs/[TOP & BOT] SummitSplit @@ -23,7 +23,8 @@ "margin-right": 8, "modules-left": [ - "cpu", + "cpu", + "power-profiles-daemon", "temperature", "memory", "disk", diff --git a/config/waybar/configs/[TOP] Camellia b/config/waybar/configs/[TOP] Camellia index 5157c052..c93e9079 100644 --- a/config/waybar/configs/[TOP] Camellia +++ b/config/waybar/configs/[TOP] Camellia @@ -41,6 +41,7 @@ "backlight/slider", "custom/speaker", "pulseaudio/slider", + "power-profiles-daemon", "battery", "clock#3", "network"], diff --git a/config/waybar/configs/[TOP] Peony b/config/waybar/configs/[TOP] Peony index cf890324..2fd1dfe3 100644 --- a/config/waybar/configs/[TOP] Peony +++ b/config/waybar/configs/[TOP] Peony @@ -42,7 +42,8 @@ "pulseaudio", "custom/separator#blank", "temperature", - "custom/separator#blank", + "custom/separator#blank", + "group/mobo_drawer", "network"], }
\ No newline at end of file diff --git a/config/waybar/configs/[TOP] Sleek b/config/waybar/configs/[TOP] Sleek index 0f46198a..fe0f41ba 100644 --- a/config/waybar/configs/[TOP] Sleek +++ b/config/waybar/configs/[TOP] Sleek @@ -38,6 +38,7 @@ "custom/separator#blank_2", "pulseaudio", "custom/separator#blank", + "group/mobo_drawer", "custom/power", ], } diff --git a/config/waybar/style/[0 VERTICAL] Golden Noir.css b/config/waybar/style/[0 VERTICAL] Golden Noir.css index 8aa3ad74..c64fedf9 100644 --- a/config/waybar/style/[0 VERTICAL] Golden Noir.css +++ b/config/waybar/style/[0 VERTICAL] Golden Noir.css @@ -140,6 +140,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[0 VERTICAL] Oglo Chicklets.css b/config/waybar/style/[0 VERTICAL] Oglo Chicklets.css index 81ce148b..3a6084cb 100644 --- a/config/waybar/style/[0 VERTICAL] Oglo Chicklets.css +++ b/config/waybar/style/[0 VERTICAL] Oglo Chicklets.css @@ -102,6 +102,7 @@ button.active { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[0 VERTICAL] [Catpuccin] Mocha.css b/config/waybar/style/[0 VERTICAL] [Catpuccin] Mocha.css index 37b67a43..971dc40f 100644 --- a/config/waybar/style/[0 VERTICAL] [Catpuccin] Mocha.css +++ b/config/waybar/style/[0 VERTICAL] [Catpuccin] Mocha.css @@ -112,6 +112,7 @@ tooltip label { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Black & White] Monochrome.css b/config/waybar/style/[Black & White] Monochrome.css index 6106422a..700bebfc 100644 --- a/config/waybar/style/[Black & White] Monochrome.css +++ b/config/waybar/style/[Black & White] Monochrome.css @@ -149,6 +149,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Catppuccin] Frappe.css b/config/waybar/style/[Catppuccin] Frappe.css index c9a02d0f..82f79678 100644 --- a/config/waybar/style/[Catppuccin] Frappe.css +++ b/config/waybar/style/[Catppuccin] Frappe.css @@ -113,6 +113,7 @@ window#waybar.hidden { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Catppuccin] Latte.css b/config/waybar/style/[Catppuccin] Latte.css index b87c543f..80608e53 100644 --- a/config/waybar/style/[Catppuccin] Latte.css +++ b/config/waybar/style/[Catppuccin] Latte.css @@ -112,6 +112,7 @@ window#waybar.hidden { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Catppuccin] Mocha.css b/config/waybar/style/[Catppuccin] Mocha.css index 74892b69..67f4efa5 100644 --- a/config/waybar/style/[Catppuccin] Mocha.css +++ b/config/waybar/style/[Catppuccin] Mocha.css @@ -135,6 +135,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colored] Chroma Glow.css b/config/waybar/style/[Colored] Chroma Glow.css index cc1b0de8..2497d69f 100644 --- a/config/waybar/style/[Colored] Chroma Glow.css +++ b/config/waybar/style/[Colored] Chroma Glow.css @@ -136,6 +136,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colored] Translucent.css b/config/waybar/style/[Colored] Translucent.css index 0a69da90..50803ef7 100644 --- a/config/waybar/style/[Colored] Translucent.css +++ b/config/waybar/style/[Colored] Translucent.css @@ -137,6 +137,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colorful] Aurora Blossom.css b/config/waybar/style/[Colorful] Aurora Blossom.css index 003dabbf..dbd405d2 100644 --- a/config/waybar/style/[Colorful] Aurora Blossom.css +++ b/config/waybar/style/[Colorful] Aurora Blossom.css @@ -128,6 +128,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colorful] Aurora.css b/config/waybar/style/[Colorful] Aurora.css index debbc873..92c058c6 100644 --- a/config/waybar/style/[Colorful] Aurora.css +++ b/config/waybar/style/[Colorful] Aurora.css @@ -117,6 +117,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colorful] Oglo Chicklets.css b/config/waybar/style/[Colorful] Oglo Chicklets.css index 98efcc2b..b5ac3584 100644 --- a/config/waybar/style/[Colorful] Oglo Chicklets.css +++ b/config/waybar/style/[Colorful] Oglo Chicklets.css @@ -102,6 +102,7 @@ button.active { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colorful] Rainbow Spectrum.css b/config/waybar/style/[Colorful] Rainbow Spectrum.css index fd4e5cb5..6ca5906f 100644 --- a/config/waybar/style/[Colorful] Rainbow Spectrum.css +++ b/config/waybar/style/[Colorful] Rainbow Spectrum.css @@ -120,6 +120,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Colorful] stolen-style.css b/config/waybar/style/[Colorful] stolen-style.css new file mode 100644 index 00000000..c6b3345f --- /dev/null +++ b/config/waybar/style/[Colorful] stolen-style.css @@ -0,0 +1,348 @@ +/* ----------- 💫 https://github.com/JaKooLit 💫 -------- */ +/* ....[Colorful] Stolen ......... */ +* { + font-family: "JetBrainsMono Nerd Font"; + font-weight: bold; + min-height: 0; + /* set font-size to 100% if font scaling is set to 1.00 using nwg-look */ + font-size: 97%; + font-feature-settings: '"zero", "ss01", "ss02", "ss03", "ss04", "ss05", "cv31"'; +} + +window#waybar { + transition-property: background-color; + transition-duration: 0.5s; + background: rgba(0, 0, 0, 0.8); + border-radius: 6px; +} + +window#waybar.hidden { + opacity: 0.2; +} + +window#waybar.empty, +window#waybar.empty #window { + background-color: rgba(0, 0, 0, 0.8); + padding: 0px; + border: 0px; +} + +#backlight, +#backlight-slider, +#battery, +#bluetooth, +#clock, +#cpu, +#disk, +#idle_inhibitor, +#keyboard-state, +#memory, +#mode, +#mpris, +#network, +#power-profiles-daemon, +#pulseaudio, +#pulseaudio-slider, +#taskbar button, +#taskbar, +#temperature, +#tray, +#window, +#wireplumber, +#workspaces, +#custom-backlight, +#custom-cycle_wall, +#custom-github, +#custom-hint, +#custom-hyprWindowMode, +#custom-keyboard, +#custom-light_dark, +#custom-lock, +#custom-menu, +#custom-power, +#custom-power_vertical, +#custom-speaker, +#custom-swaync, +#custom-updater, +#custom-weather, +#custom-weather.clearNight, +#custom-weather.cloudyFoggyDay, +#custom-weather.cloudyFoggyNight, +#custom-weather.default, +#custom-weather.rainyDay, +#custom-weather.rainyNight, +#custom-weather.severe, +#custom-weather.showyIcyDay, +#custom-weather.snowyIcyNight, +#custom-weather.sunnyDay { + padding-top: 2px; + padding-bottom: 2px; + padding-right: 6px; + padding-left: 6px; +} + +#workspaces button { + padding-left: 10px; + padding-right: 10px; + animation: gradient_f 20s ease-in infinite; + transition: all 0.1s linear; +} + +#workspaces button.persistent { + color: #faba4a; +} + +#workspaces button.empty { + color: #ffffed; +} + +#workspaces button.active { + color: #00ffff; + border: 2px solid #455a64; + border-radius: 16px; +} + +#workspaces button.urgent { + color: #f7768e; + border: 2px solid #f7768e; + border-radius: 16px; +} + +#workspaces button:hover { + border: 2px solid #455a64; + border-radius: 16px; +} + +#idle_inhibitor { + color: #7aa2f7; +} + +#backlight { + color: #7aa2f7; +} + +#bluetooth { + color: #7aa2f7; +} + +#clock { + color: #9fe044; + padding-right: 7px; +} + +#custom-swaync { + color: #9fe044; + border: 2px solid #455a64; + border-radius: 20px; + padding-left: 13px; + padding-right: 12px; +} + +#custom-keyboard { + color: #e0af68; + border: 2px solid #455a64; + border-radius: 20px; + padding-left: 14px; + padding-right: 14px; +} + +#custom-cycle_wall { + color: #c0caf5; + padding-left: 7px; + padding-right: 4px; +} + +#custom-hint { + color: #c0caf5; + padding-left: 0px; + padding-right: 4px; +} + +#custom-github { + color: #c0caf5; + padding-left: 0px; + padding-right: 4px; +} + +#custom-hyprWindowMode { + color: #c0caf5; + padding-left: 6px; +} + +#custom-niflveil { + color: #7dcfff; + padding-left: 0px; + padding-right: 10px; +} + +#custom-niflveil.empty { + color: #c0caf5; + +} + +#cpu { + padding-left: 10px; + padding-right: 3px; + color: #ffffed; + border-top: 2px solid #455a64; + border-bottom: 2px solid #455a64; + border-left: 2px solid #455a64; + border-radius: 20px 0px 0px 20px; +} + +#memory { + color: #ffffed; + padding-right: 8px; + border-bottom: 2px solid #455a64; + border-top: 2px solid #455a64; + border-radius: 0px; +} + +#temperature { + color: #ffffed; + border-bottom: 2px solid #455a64; + border-top: 2px solid #455a64; + border-radius: 0px; +} + +#disk { + padding-right: 16px; + color: #ffffed; + border-top: 2px solid #455a64; + border-bottom: 2px solid #455a64; + border-right: 2px solid #455a64; + border-radius: 0px 20px 20px 0px; +} + +#temperature.critical { + background-color: #f7768e; +} + +#tray > .passive { + -gtk-icon-effect: dim; +} + +#tray > .needs-attention { + -gtk-icon-effect: highlight; +} + +#keyboard-state { + color: #c7a9ff; +} + +#custom-cava_mviz { + color: #c7a9ff; +} + +#custom-menu { + + color: #7dcfff; + padding-left: 14px; + padding-right: 12px; +} + +#custom-power { + + color: #f7768e; + padding-left: 8px; + padding-right: 17px; +} + +#custom-updater { + color: #f7768e; + border: 2px solid #455a64; + border-radius: 20px; + padding-left: 10pt; + padding-right: 12px; + +} + +#custom-light_dark { + color: #7aa2f7; +} + +#custom-weather { + color: #9fe044; + +} + +#custom-speaker { + color: #faba4a; +} + +#pulseaudio { + color: #faba4a; + +} + +#pulseaudio.bluetooth { + color: #c7a9ff; +} + +#pulseaudio.muted { + color: #f7768e; +} + +#window { + font-size: 100%; + color: #ffffed; +} + +#mpris { + color: #bb9af7; +} + +#network { + color: #7dcfff; +} + +#network.disconnected, +#network.disabled { + background-color: #c0caf5; + color: #000000; +} + +#pulseaudio-slider slider { + min-width: 0px; + min-height: 0px; + opacity: 0; + background-image: none; + border: none; + box-shadow: none; +} + +#pulseaudio-slider trough { + min-width: 80px; + min-height: 5px; + border-radius: 5px; +} + +#pulseaudio-slider highlight { + min-height: 10px; + border-radius: 5px; +} + +#backlight-slider slider { + min-width: 0px; + min-height: 0px; + opacity: 0; + background-image: none; + border: none; + box-shadow: none; +} + +#backlight-slider trough { + min-width: 80px; + min-height: 10px; + border-radius: 5px; +} + +#backlight-slider highlight { + min-width: 10px; + border-radius: 5px; +} + +#custom-separator { + padding-left: 6px; + color: #f7768e; +} diff --git a/config/waybar/style/[Dark] Golden Eclipse.css b/config/waybar/style/[Dark] Golden Eclipse.css index b2d1079f..af3160a6 100644 --- a/config/waybar/style/[Dark] Golden Eclipse.css +++ b/config/waybar/style/[Dark] Golden Eclipse.css @@ -75,6 +75,7 @@ window#waybar.hidden { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Dark] Golden Noir.css b/config/waybar/style/[Dark] Golden Noir.css index 9fe7b9c8..17025266 100644 --- a/config/waybar/style/[Dark] Golden Noir.css +++ b/config/waybar/style/[Dark] Golden Noir.css @@ -140,6 +140,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Dark] Latte-Wallust combined v2.css b/config/waybar/style/[Dark] Latte-Wallust combined v2.css index a937724c..46d55346 100644 --- a/config/waybar/style/[Dark] Latte-Wallust combined v2.css +++ b/config/waybar/style/[Dark] Latte-Wallust combined v2.css @@ -154,6 +154,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Dark] Latte-Wallust combined.css b/config/waybar/style/[Dark] Latte-Wallust combined.css index 51533e8e..ea5c08a5 100644 --- a/config/waybar/style/[Dark] Latte-Wallust combined.css +++ b/config/waybar/style/[Dark] Latte-Wallust combined.css @@ -159,6 +159,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Dark] Purpl.css b/config/waybar/style/[Dark] Purpl.css index c2ded5ff..5e2ff8aa 100644 --- a/config/waybar/style/[Dark] Purpl.css +++ b/config/waybar/style/[Dark] Purpl.css @@ -144,6 +144,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Dark] Wallust Obsidian Edge.css b/config/waybar/style/[Dark] Wallust Obsidian Edge.css index 22998299..94ad7cac 100644 --- a/config/waybar/style/[Dark] Wallust Obsidian Edge.css +++ b/config/waybar/style/[Dark] Wallust Obsidian Edge.css @@ -130,6 +130,7 @@ tooltip label { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Arrow.css b/config/waybar/style/[Extra] Arrow.css index 16aab612..9435d226 100644 --- a/config/waybar/style/[Extra] Arrow.css +++ b/config/waybar/style/[Extra] Arrow.css @@ -106,6 +106,7 @@ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Crimson.css b/config/waybar/style/[Extra] Crimson.css index a71f89e6..cf9e7db2 100644 --- a/config/waybar/style/[Extra] Crimson.css +++ b/config/waybar/style/[Extra] Crimson.css @@ -128,6 +128,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] EverForest.css b/config/waybar/style/[Extra] EverForest.css index ad2b51bc..f0014762 100644 --- a/config/waybar/style/[Extra] EverForest.css +++ b/config/waybar/style/[Extra] EverForest.css @@ -308,6 +308,7 @@ window#waybar.hidden { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] ML4W starter.css b/config/waybar/style/[Extra] ML4W starter.css index 79456a84..8d651da6 100644 --- a/config/waybar/style/[Extra] ML4W starter.css +++ b/config/waybar/style/[Extra] ML4W starter.css @@ -177,6 +177,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Mauve.css b/config/waybar/style/[Extra] Mauve.css index 816ed777..ec24bec7 100644 --- a/config/waybar/style/[Extra] Mauve.css +++ b/config/waybar/style/[Extra] Mauve.css @@ -147,6 +147,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Modern-Combined - Transparent.css b/config/waybar/style/[Extra] Modern-Combined - Transparent.css index f4184817..c4ff7197 100644 --- a/config/waybar/style/[Extra] Modern-Combined - Transparent.css +++ b/config/waybar/style/[Extra] Modern-Combined - Transparent.css @@ -161,6 +161,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Neon Circuit.css b/config/waybar/style/[Extra] Neon Circuit.css index 2415841a..7fbb39a5 100644 --- a/config/waybar/style/[Extra] Neon Circuit.css +++ b/config/waybar/style/[Extra] Neon Circuit.css @@ -125,6 +125,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Prismatic Glow.css b/config/waybar/style/[Extra] Prismatic Glow.css index cbcedfc6..8179352d 100644 --- a/config/waybar/style/[Extra] Prismatic Glow.css +++ b/config/waybar/style/[Extra] Prismatic Glow.css @@ -172,6 +172,7 @@ window#waybar.empty { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Rose Pine.css b/config/waybar/style/[Extra] Rose Pine.css index 1503355a..023b03a2 100644 --- a/config/waybar/style/[Extra] Rose Pine.css +++ b/config/waybar/style/[Extra] Rose Pine.css @@ -148,6 +148,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Extra] Simple Pink.css b/config/waybar/style/[Extra] Simple Pink.css index 84b2491f..db63a02f 100644 --- a/config/waybar/style/[Extra] Simple Pink.css +++ b/config/waybar/style/[Extra] Simple Pink.css @@ -140,6 +140,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Light] Monochrome Contrast.css b/config/waybar/style/[Light] Monochrome Contrast.css index 6e8a2e41..95b34fcc 100644 --- a/config/waybar/style/[Light] Monochrome Contrast.css +++ b/config/waybar/style/[Light] Monochrome Contrast.css @@ -129,6 +129,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Light] Obsidian Glow.css b/config/waybar/style/[Light] Obsidian Glow.css index da8bb502..692ce5d5 100644 --- a/config/waybar/style/[Light] Obsidian Glow.css +++ b/config/waybar/style/[Light] Obsidian Glow.css @@ -116,6 +116,7 @@ tooltip label { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Rainbow] RGB Bordered.css b/config/waybar/style/[Rainbow] RGB Bordered.css index e2df8c39..b38ae40d 100644 --- a/config/waybar/style/[Rainbow] RGB Bordered.css +++ b/config/waybar/style/[Rainbow] RGB Bordered.css @@ -136,6 +136,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Retro] Simple Style.css b/config/waybar/style/[Retro] Simple Style.css index fc50013c..8a601487 100644 --- a/config/waybar/style/[Retro] Simple Style.css +++ b/config/waybar/style/[Retro] Simple Style.css @@ -90,6 +90,7 @@ window#waybar { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Transparent] Crystal Clear.css b/config/waybar/style/[Transparent] Crystal Clear.css index b0cbaaf1..b355c176 100644 --- a/config/waybar/style/[Transparent] Crystal Clear.css +++ b/config/waybar/style/[Transparent] Crystal Clear.css @@ -111,6 +111,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[VERTICAL] [Catpuccin] Mocha.css b/config/waybar/style/[VERTICAL] [Catpuccin] Mocha.css index cd2d9165..d265506c 100644 --- a/config/waybar/style/[VERTICAL] [Catpuccin] Mocha.css +++ b/config/waybar/style/[VERTICAL] [Catpuccin] Mocha.css @@ -111,6 +111,7 @@ tooltip label { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[WALLUST] ML4W-modern-mixed.css b/config/waybar/style/[WALLUST] ML4W-modern-mixed.css index 5df79cb6..f1b64745 100644 --- a/config/waybar/style/[WALLUST] ML4W-modern-mixed.css +++ b/config/waybar/style/[WALLUST] ML4W-modern-mixed.css @@ -161,6 +161,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[WALLUST] ML4W-modern.css b/config/waybar/style/[WALLUST] ML4W-modern.css index b2a97488..048f972b 100644 --- a/config/waybar/style/[WALLUST] ML4W-modern.css +++ b/config/waybar/style/[WALLUST] ML4W-modern.css @@ -168,6 +168,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust Bordered] Chroma Fusion Edge.css b/config/waybar/style/[Wallust Bordered] Chroma Fusion Edge.css index e3224529..812dbf6f 100644 --- a/config/waybar/style/[Wallust Bordered] Chroma Fusion Edge.css +++ b/config/waybar/style/[Wallust Bordered] Chroma Fusion Edge.css @@ -100,6 +100,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust Bordered] Chroma Simple.css b/config/waybar/style/[Wallust Bordered] Chroma Simple.css index 63d66b26..3bf56789 100644 --- a/config/waybar/style/[Wallust Bordered] Chroma Simple.css +++ b/config/waybar/style/[Wallust Bordered] Chroma Simple.css @@ -125,6 +125,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust Transparent] Crystal Clear.css b/config/waybar/style/[Wallust Transparent] Crystal Clear.css index 54c7f2f3..cddee7e7 100644 --- a/config/waybar/style/[Wallust Transparent] Crystal Clear.css +++ b/config/waybar/style/[Wallust Transparent] Crystal Clear.css @@ -144,6 +144,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Box type.css b/config/waybar/style/[Wallust] Box type.css index 7c53ef92..84c85cf6 100644 --- a/config/waybar/style/[Wallust] Box type.css +++ b/config/waybar/style/[Wallust] Box type.css @@ -125,6 +125,7 @@ window#waybar.empty #window { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Chroma Edge.css b/config/waybar/style/[Wallust] Chroma Edge.css index 0cbaa6c0..3530c3af 100644 --- a/config/waybar/style/[Wallust] Chroma Edge.css +++ b/config/waybar/style/[Wallust] Chroma Edge.css @@ -128,6 +128,7 @@ tooltip label{ #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Chroma Fusion.css b/config/waybar/style/[Wallust] Chroma Fusion.css index 079c266a..d740c78f 100644 --- a/config/waybar/style/[Wallust] Chroma Fusion.css +++ b/config/waybar/style/[Wallust] Chroma Fusion.css @@ -101,6 +101,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Chroma Tally V2.css b/config/waybar/style/[Wallust] Chroma Tally V2.css index 8251d950..8082331b 100644 --- a/config/waybar/style/[Wallust] Chroma Tally V2.css +++ b/config/waybar/style/[Wallust] Chroma Tally V2.css @@ -113,6 +113,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Chroma Tally.css b/config/waybar/style/[Wallust] Chroma Tally.css index c9938eb3..a6fd1ee9 100644 --- a/config/waybar/style/[Wallust] Chroma Tally.css +++ b/config/waybar/style/[Wallust] Chroma Tally.css @@ -112,6 +112,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Colored.css b/config/waybar/style/[Wallust] Colored.css index 381417dd..435f3651 100644 --- a/config/waybar/style/[Wallust] Colored.css +++ b/config/waybar/style/[Wallust] Colored.css @@ -153,6 +153,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/waybar/style/[Wallust] Simple.css b/config/waybar/style/[Wallust] Simple.css index 0990a80c..c2206c10 100644 --- a/config/waybar/style/[Wallust] Simple.css +++ b/config/waybar/style/[Wallust] Simple.css @@ -132,6 +132,7 @@ tooltip { #custom-swaync, #custom-tty, #custom-updater, +#custom-hyprpicker, #custom-weather, #custom-weather.clearNight, #custom-weather.cloudyFoggyDay, diff --git a/config/wlogout/layout b/config/wlogout/layout index ea468745..bcebbf44 100644 --- a/config/wlogout/layout +++ b/config/wlogout/layout @@ -18,7 +18,7 @@ } { "label" : "logout", - "action" : "loginctl kill-session $XDG_SESSION_ID", + "action" : "hyprctl dispatch exit 0", "text" : "Logout", "keybind" : "e" } @@ -33,4 +33,4 @@ "action" : "systemctl hibernate", "text" : "Hibernate", "keybind" : "h" -}
\ No newline at end of file +} @@ -33,7 +33,7 @@ fi # Function to print colorful text print_color() { - printf "%b%s%b\n" "$1" "$2" "$CLEAR" + printf "%b%s%b\n" "$1" "$2" "$RESET" } # Check /etc/os-release to see if this is an Ubuntu or Debian based distro @@ -75,11 +75,6 @@ if [ ! -d Copy-Logs ]; then mkdir Copy-Logs fi -# Function to print colorful text -print_color() { - printf "%b%s%b\n" "$1" "$2" "$CLEAR" -} - # Set the name of the log file to include the current date and time LOG="Copy-Logs/install-$(date +%d-%H%M%S)_dotfiles.log" @@ -92,6 +87,8 @@ if lspci -k | grep -A 2 -E "(VGA|3D)" | grep -iq nvidia; then sed -i '/env = LIBVA_DRIVER_NAME,nvidia/s/^#//' config/hypr/UserConfigs/ENVariables.conf sed -i '/env = __GLX_VENDOR_LIBRARY_NAME,nvidia/s/^#//' config/hypr/UserConfigs/ENVariables.conf sed -i '/env = NVD_BACKEND,direct/s/^#//' config/hypr/UserConfigs/ENVariables.conf + sed -i '/env = GSK_RENDERER,ngl/s/^#//' config/hypr/UserConfigs/ENVariables.conf + # no hardware cursors if nvidia detected sed -i 's/^\([[:space:]]*no_hardware_cursors[[:space:]]*=[[:space:]]*\)2/\1 1/' config/hypr/UserConfigs/UserSettings.conf #sed -i 's/^\([[:space:]]*explicit_sync[[:space:]]*=[[:space:]]*\)2/\1 0/' config/hypr/UserConfigs/UserSettings.conf @@ -245,6 +242,25 @@ if command -v ags >/dev/null 2>&1; then sed -i '/^\s*#exec-once = ags/s/^#//' config/hypr/UserConfigs/Startup_Apps.conf sed -i '/#ags -q && ags &/s/^#//' config/hypr/scripts/RefreshNoWaybar.sh sed -i '/#ags -q && ags &/s/^#//' config/hypr/scripts/Refresh.sh + + # Uncomment the ags overview keybind + sed -i '/^#bind = \$mainMod, A, exec, pkill rofi || true && ags -t '\''overview'\''/s/^#//' config/hypr/UserConfigs/UserKeybinds.conf + + # Comment the quickshell line if not already commented + sed -i '/^\s*bind\s*=\s*\$mainMod,\s*A,\s*global,\s*quickshell:overviewToggle/{s/^\s*/#/}' config/hypr/UserConfigs/UserKeybinds.conf +fi + +# Check if quickshell is installed; edit quickshell behaviour on configs +if command -v qs >/dev/null 2>&1; then + sed -i '/^\s*#exec-once = qs/s/^#//' config/hypr/UserConfigs/Startup_Apps.conf + sed -i '/#pkill qs && qs &/s/^#//' config/hypr/scripts/RefreshNoWaybar.sh + sed -i '/#pkill qs && qs &/s/^#//' config/hypr/scripts/Refresh.sh + + # Uncomment the quickshell keybind line + sed -i "/^#bind = \$mainMod, A, global, quickshell:overviewToggle/s/^#//" config/hypr/UserConfigs/UserKeybinds.conf + + # Ensure the ags overview keybind is commented + sed -i "/^\s*bind\s*=\s*\\\$mainMod,\s*A,\s*exec,\s*pkill rofi\s*||\s*true\s*&&\s*ags\s*-t\s*'overview'/{s/^\s*/#/}" config/hypr/UserConfigs/UserKeybinds.conf fi printf "\n%.0s" {1..1} @@ -392,7 +408,7 @@ while true; do # Applying to different SDDM themes apply_sddm_12h_format "/usr/share/sddm/themes/simple-sddm" - apply_sddm_12h_format "/usr/share/sddm/themes/simple-sddm-2" + apply_sddm_12h_format "/usr/share/sddm/themes/simple_sddm_2" # For SDDM (sequoia_2) sddm_directory_3="/usr/share/sddm/themes/sequoia_2" @@ -461,7 +477,7 @@ fi printf "${INFO} - copying dotfiles ${SKY_BLUE}first${RESET} part\n" # Config directories which will ask the user whether to replace or not -DIRS="ags fastfetch kitty rofi swaync" +DIRS="fastfetch kitty rofi swaync" for DIR2 in $DIRS; do DIRPATH="$HOME/.config/$DIR2" @@ -670,6 +686,79 @@ done printf "\n%.0s" {1..1} +# ags config +# Check if ags is installed +if command -v ags >/dev/null 2>&1; then + echo -e "${NOTE} - ${YELLOW}ags${RESET} is detected as installed" + + DIRPATH_AGS="$HOME/.config/ags" + + if [ ! -d "$DIRPATH_AGS" ]; then + echo "${INFO} - ags config not found, copying new config." + if [ -d "config/ags" ]; then + cp -r "config/ags/" "$DIRPATH_AGS" 2>&1 | tee -a "$LOG" + fi + else + read -p "${CAT} Do you want to overwrite your existing ${YELLOW}ags${RESET} config? [y/N] " answer_ags + case "$answer_ags" in + [Yy]* ) + BACKUP_DIR=$(get_backup_dirname) + mv "$DIRPATH_AGS" "$DIRPATH_AGS-backup-$BACKUP_DIR" 2>&1 | tee -a "$LOG" + echo -e "${NOTE} - Backed up ags config to $DIRPATH_AGS-backup-$BACKUP_DIR" + + if cp -r "config/ags/" "$DIRPATH_AGS" 2>&1 | tee -a "$LOG"; then + echo "${OK} - ${YELLOW}ags configs${RESET} overwritten successfully." + else + echo "${ERROR} - Failed to copy ${YELLOW}ags${RESET} config." + exit 1 + fi + ;; + * ) + echo "${NOTE} - Skipping overwrite of ags config." + ;; + esac + fi +fi + +printf "\n%.0s" {1..1} + +# quickshell (ags alternative) +# Check if quickshell is installed +if command -v qs >/dev/null 2>&1; then + echo -e "${NOTE} - ${YELLOW}quickshell${RESET} is detected as installed" + + DIRPATH_QS="$HOME/.config/quickshell" + + if [ ! -d "$DIRPATH_QS" ]; then + echo "${INFO} - quickshell config not found, copying new config." + if [ -d "config/quickshell" ]; then + cp -r "config/quickshell/" "$DIRPATH_QS" 2>&1 | tee -a "$LOG" + fi + else + read -p "${CAT} Do you want to overwrite your existing ${YELLOW}quickshell${RESET} config? [y/N] " answer_qs + case "$answer_qs" in + [Yy]* ) + BACKUP_DIR=$(get_backup_dirname) + mv "$DIRPATH_QS" "$DIRPATH_QS-backup-$BACKUP_DIR" 2>&1 | tee -a "$LOG" + echo -e "${NOTE} - Backed up quickshell to $DIRPATH_QS-backup-$BACKUP_DIR" + + cp -r "config/quickshell/" "$DIRPATH_QS" 2>&1 | tee -a "$LOG" + if [ $? -eq 0 ]; then + echo "${OK} - ${YELLOW}quickshell${RESET} overwritten successfully." + else + echo "${ERROR} - Failed to copy ${YELLOW}quickshell${RESET} config." + exit 1 + fi + ;; + * ) + echo "${NOTE} - Skipping overwrite of quickshell config." + ;; + esac + fi +fi +printf "\n%.0s" {1..1} + + # Restore automatically Animations and Monitor-Profiles # including monitors.conf and workspaces.conf HYPR_DIR="$HOME/.config/hypr" @@ -897,11 +986,11 @@ rm -rf "$HOME/.config/waybar/configs/[TOP] Default$config_remove" \ printf "\n%.0s" {1..1} -# for SDDM (sequoia_2) -sddm_sequioa="/usr/share/sddm/themes/sequoia_2" -if [ -d "$sddm_sequioa" ]; then +# for SDDM (simple_sddm_2) +sddm_simple_sddm_2="/usr/share/sddm/themes/simple_sddm_2" +if [ -d "$sddm_simple_sddm_2" ]; then while true; do - echo -n "${CAT} SDDM sequoia_2 theme detected! Apply current wallpaper as SDDM background? (y/n): " + echo -n "${CAT} SDDM simple_sddm_2 theme detected! Apply current wallpaper as SDDM background? (y/n): " read SDDM_WALL # Remove any leading/trailing whitespace or newlines from input @@ -910,7 +999,7 @@ if [ -d "$sddm_sequioa" ]; then case $SDDM_WALL in [Yy]) # Copy the wallpaper, ignore errors if the file exists or fails - sudo cp -r "config/hypr/wallpaper_effects/.wallpaper_current" "/usr/share/sddm/themes/sequoia_2/backgrounds/default" || true + sudo cp -r "config/hypr/wallpaper_effects/.wallpaper_current" "/usr/share/sddm/themes/simple_sddm_2/Backgrounds/default" || true echo "${NOTE} Current wallpaper applied as default SDDM background" 2>&1 | tee -a "$LOG" break ;; @@ -1037,4 +1126,4 @@ printf "\n%.0s" {1..1} printf "${INFO} However, it is ${MAGENTA}HIGHLY SUGGESTED${RESET} to logout and re-login or better reboot to avoid any issues" printf "\n%.0s" {1..1} printf "${SKY_BLUE}Thank you${RESET} for using ${MAGENTA}KooL's Hyprland Configuration${RESET}... ${YELLOW}ENJOY!!!${RESET}" -printf "\n%.0s" {1..3} +printf "\n%.0s" {1..3}
\ No newline at end of file diff --git a/i18n/README.ru.markdown b/i18n/README.ru.markdown new file mode 100644 index 00000000..d80a2f53 --- /dev/null +++ b/i18n/README.ru.markdown @@ -0,0 +1,194 @@ +<h3 align="center"> +<img align="center" width="80%" src=https://github.com/user-attachments/assets/bc18bd4d-944b-4d5f-a119-7578fa38f9b4 /> +</h3> + +<p align="center"> + <img src="https://raw.githubusercontent.com/JaKooLit/Hyprland-Dots/main/assets/latte.png" width="400" /> +</p> + +<div align="center"> +<br> + <a href="#установка"><kbd> <br> Установка <br> </kbd></a> + <a href="https://www.youtube.com/playlist?list=PLDtGd5Fw5_GjXCznR0BzCJJDIQSZJRbxx"><kbd> <br> YouTube <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki"><kbd> <br> Вики <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/discussions"><kbd> <br> Обсуждения <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki/Keybinds"><kbd> <br> Горячие клавиши <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki/FAQ"><kbd> <br> Часто задаваемые вопросы <br> </kbd></a> + <a href="https://discord.gg/kool-tech-world"><kbd> <br> Discord <br> </kbd></a> +</div><br> + +<div align="center"> + +   <a href="https://discord.gg/kool-tech-world"> <img src="https://img.shields.io/discord/1151869464405606400?style=for-the-badge&logo=discord&color=cba6f7&link=https%3A%2F%2Fdiscord.gg%kool-tech-world"> </a> + +<br/> +</div> + +<h3 align="center"> + <img src="https://github.com/JaKooLit/Telegram-Animated-Emojis/blob/main/Activity/Sparkles.webp" alt="Sparkles" width="38" height="38" /> + Демонстрация Dotfiles Hyprland от KooL + <img src="https://github.com/JaKooLit/Telegram-Animated-Emojis/blob/main/Activity/Sparkles.webp" alt="Sparkles" width="38" height="38" /> +</h3> + +<div align="center"> + +https://github.com/user-attachments/assets/49bc12b2-abaf-45de-a21c-67aacd9bb872 + +</div> + +### 📹 Видеообзоры +- в конце страницы + +### 🎞️ Демо AGS Overview +- если интересно, вот короткое демо AGS overview [Ссылка на YouTube](https://youtu.be/zY5SLNPBJTs) + +</details> + +--- +[](https://git.io/typing-svg) +### 🚩 🏁 Автоматические скрипты установки Hyprland для дистрибутивов, клонирование и запуск 🇵🇭 +> [!ВНИМАНИЕ] +> Если вы используете FISH SHELL, НЕ используйте эту функцию. Вместо этого клонируйте Distro-Hyprland и запустите install.sh. + +- ПРИМЕЧАНИЕ: для работы требуется пакет `curl` + +```bash +sh <(curl -L https://raw.githubusercontent.com/JaKooLit/Hyprland-Dots/main/Distro-Hyprland.sh) +``` + +- теперь вы можете использовать приведённую выше команду для автоматического клонирования скриптов установки Distro-Hyprland, указанных ниже +- она клонирует скрипты установки и запускает `install.sh` 😎 + +### 👁️🗨️ Мои скрипты установки Hyprland 👁️🗨️ +- Автоматические скрипты Hyprland для выбранного дистрибутива, которые загрузят эти dotfiles, если вы выберете установку этих конфигураций + +- [Arch-Linux](https://github.com/JaKooLit/Arch-Hyprland) +- [OpenSUSE(Tumbleweed)](https://github.com/JaKooLit/OpenSuse-Hyprland) +- [Fedora-Linux](https://github.com/JaKooLit/Fedora-Hyprland) +- [Debian-Linux (Trixie & SID)](https://github.com/JaKooLit/Debian-Hyprland) +- [NixOS](https://github.com/JaKooLit/NixOS-Hyprland) +- [Ubuntu 24.04 LTS](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/24.04) +- [Ubuntu 24.10](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/24.10) +- [Ubuntu 25.04 - (АЛЬФА-СТАДИЯ)](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/25.04) + +--- + +### 🪧 Внимание 🪧 +- Этот репозиторий НЕ содержит и НЕ устанавливает пакеты. Это только предварительно настроенные конфигурации Hyprland или dotfiles +- обратитесь к скриптам установки, чтобы узнать, какие пакеты нужно установить... но, как минимум, пакеты Hyprland необходимы 😏😏😏 очевидно!! +- Этот репозиторий будет загружен скриптами установки Distro-Hyprland, указанными выше, если вы выберете загрузку предварительно настроенных dotfiles + +### 👀 Скриншоты 👀 +- Все скриншоты собраны здесь [Скриншоты](https://github.com/JaKooLit/screenshots/tree/main/Hyprland-ScreenShots) + +### 📦 Что нового? +- Чтобы легко отслеживать изменения, я буду обновлять [Журнал изменений](https://github.com/JaKooLit/Hyprland-Dots/wiki/Changelogs). Скриншоты будут включены, если изменения заслуживают упоминания! + +> [!ПРИМЕЧАНИЕ] +> Обратите внимание, что по умолчанию dotfiles от KooL настроены для дисплеев 2k (1440p) без масштабирования. + +### 💥 Инструкции по копированию / установке / обновлению 💥 +- [`БОЛЬШЕ ИНФОРМАЦИИ ЗДЕСЬ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Install_&_Update) +> [!Примечание] +> Автоматический скрипт копирования „copy.sh“ создаёт резервные копии директорий, которые будут скопированы. Тем не менее, рекомендуется сделать резервную копию вручную на случай, если скрипт не сможет этого сделать! + +- клонируйте этот репозиторий с помощью git. Перейдите в директорию, сделайте скрипт исполняемым и запустите его + +> для загрузки из ветки Master +```bash +git clone --depth=1 https://github.com/JaKooLit/Hyprland-Dots.git +cd Hyprland-Dots +``` + +> для загрузки из ветки Development (разработка и тестирование) +```bash +git clone --depth=1 https://github.com/JaKooLit/Hyprland-Dots.git -b development +cd Hyprland-Dots +``` + +- автоматическое копирование/установка предварительно настроенных dotfiles (рекомендуется для обновлений) +```bash +chmod +x copy.sh +./copy.sh +``` + +- для копирования/установки из релизов (стабильные) (примечание: это на одну версию старше, чем в основной ветке) +```bash +chmod +x release.sh +./release.sh +``` + +- Функция UPGRADE.sh +> [!ВАЖНО] +> Для работы требуется rsync +> у вас уже должен быть настроен и запущен Hyprland от KooL перед использованием этой функции +```bash +chmod +x upgrade.sh +./upgrade.sh +``` + +## ❗❗❗ ВНИМАНИЕ ДЛЯ ПОЛЬЗОВАТЕЛЕЙ DEBIAN И UBUNTU! +- Я получаю огромное количество сообщений об обновлении dotfiles Hyprland от KooL. Я сделал большую заметку в [`ВИКИ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Install_&_Update) + +#### ⚠️⚠️⚠️ ВНИМАНИЕ - РЕЗЕРВНЫЕ КОПИИ, СОЗДАННЫЕ СКРИПТОМ +> [!ВНИМАНИЕ] +> copy.sh, release.sh и даже upgrade.sh создают резервную копию! +> Проверьте содержимое в $HOME/.config вручную +> Удалите вручную все ненужные резервные копии + +#### 🛎️ Небольшое замечание об обоях +- по умолчанию копируется только несколько обоев (по 1 для тёмного и светлого режима, плюс ещё 3). Вам будет предложено загрузить дополнительные обои. Вы можете просмотреть/проверить дополнительные обои по [`ЭТОЙ`](https://github.com/JaKooLit/Wallpaper-Bank/tree/main/wallpapers) ссылке + +#### ⚠️⚠️⚠️ ОБЯЗАТЕЛЬНО! после копирования / установки этих dotfiles ++ Нажмите SUPER W и установите обои. Это также необходимо для инициализации wallust для тем waybar, kitty (tty) и rofi. Однако, если вы используете copy.sh или release.sh, начальные обои уже будут установлены, и этого делать не придётся + ++ Владельцы Nvidia. Обязательно отредактируйте `~/.config/hypr/UserConfigs/ENVariables.conf` (настоятельно рекомендуется). +- Пользователи / владельцы Nvidia, после установки проверьте [`ЭТО`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Notes_to_remember#--for-nvidia-gpu-users) + ++ Если вы уже настроили свои горячие клавиши, мониторы и т.д., просто скопируйте их из созданной резервной копии перед выходом из системы или перезагрузкой. (рекомендуется) + +#### 📖 Известные проблемы и возможные решения +- ознакомьтесь с этой страницей [Часто задаваемые вопросы](https://github.com/JaKooLit/Hyprland-Dots/wiki/FAQ) и [НЕРЕШЁННЫЕ ПРОБЛЕМЫ](https://github.com/JaKooLit/Hyprland-Dots/wiki/Known_Issues) + +#### 🙋 ВОПРОСЫ ?!?! ⁉️ +- Часто задаваемые вопросы! Да, вы можете использовать эти dotfiles на других дистрибутивах! Просто убедитесь, что сначала установлены соответствующие пакеты! Если вам от этого легче, я использую ту же конфигурацию на моём Gentoo :) +- БЫСТРЫЙ СОВЕТ! Нажмите на модуль HINT! в Waybar (примечание: доступно только в стандартном и Simple-L [ВЕРХНЕМ] макете Waybar). Можно запустить с помощью горячей клавиши `SUPER H` +- Ещё вопросы? щёлкните здесь, чтобы просмотреть эту [ВИКИ](https://github.com/JaKooLit/Hyprland-Dots/wiki/) +- Если вам нужны старые конфигурации, они собраны в моём репозитории „Archive“. Смотрите [ЗДЕСЬ](https://github.com/JaKooLit/Hyprland-Dots-releases-Archive) + +#### ⌨ Горячие клавиши +- Горячие клавиши [`ЩЁЛКНИТЕ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Keybinds) + +#### 🙏 Особая просьба +- Если у вас есть улучшения для dotfiles или конфигураций, не стесняйтесь отправить PR для улучшений. Я всегда приветствую улучшения, так как тоже учусь, как и вы! + +#### ✍️ Вклад +- Хотите внести вклад? Щёлкните [`ЗДЕСЬ`](https://github.com/JaKooLit/Hyprland-Dots/blob/main/CONTRIBUTING.md) для руководства по внесению вклада + +#### 🤷♂️ ЧТО ДЕЛАТЬ! +- [ ] Настройка dotfiles - 🚧 в постоянном прогрессе +- ~~[ ] Возможно, переход на starship? Хотя у starship ограниченные темы по сравнению с oh-my-zsh.~~ пока планов нет + +#### 🔮 Сервер Discord +- приглашаю присоединиться к моему [Discord](https://discord.com/invite/kool-tech-world) + +#### 💖 Поддержка +- звезда на моих репозиториях GitHub была бы замечательной 🌟 + +- Подпишитесь на мой канал YouTube [YouTube](https://www.youtube.com/@Ja.KooLit) + +- также вы можете поддержать через кофе или btc 😊 + +[](https://ko-fi.com/jakoolit) + +или + +[](https://www.buymeacoffee.com/JaKooLit) + +Или вы можете пожертвовать криптовалюту на мой btc-кошелёк :) +> 1N3MeV2dsX6gQB42HXU6MF2hAix1mqjo8i + + + +## 🫰 Спасибо за звёзды 🩷 +[](https://starchart.cc/JaKooLit/Hyprland-Dots)
\ No newline at end of file diff --git a/i18n/README.uk.markdown b/i18n/README.uk.markdown new file mode 100644 index 00000000..04d6ad27 --- /dev/null +++ b/i18n/README.uk.markdown @@ -0,0 +1,194 @@ +<h3 align="center"> +<img align="center" width="80%" src=https://github.com/user-attachments/assets/bc18bd4d-944b-4d5f-a119-7578fa38f9b4 /> +</h3> + +<p align="center"> + <img src="https://raw.githubusercontent.com/JaKooLit/Hyprland-Dots/main/assets/latte.png" width="400" /> +</p> + +<div align="center"> +<br> + <a href="#встановлення"><kbd> <br> Встановлення <br> </kbd></a> + <a href="https://www.youtube.com/playlist?list=PLDtGd5Fw5_GjXCznR0BzCJJDIQSZJRbxx"><kbd> <br> YouTube <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki"><kbd> <br> Вікі <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/discussions"><kbd> <br> Обговорення <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki/Keybinds"><kbd> <br> Гарячі клавіші <br> </kbd></a> + <a href="https://github.com/JaKooLit/Hyprland-Dots/wiki/FAQ"><kbd> <br> Поширені запитання <br> </kbd></a> + <a href="https://discord.gg/kool-tech-world"><kbd> <br> Discord <br> </kbd></a> +</div><br> + +<div align="center"> + +   <a href="https://discord.gg/kool-tech-world"> <img src="https://img.shields.io/discord/1151869464405606400?style=for-the-badge&logo=discord&color=cba6f7&link=https%3A%2F%2Fdiscord.gg%kool-tech-world"> </a> + +<br/> +</div> + +<h3 align="center"> + <img src="https://github.com/JaKooLit/Telegram-Animated-Emojis/blob/main/Activity/Sparkles.webp" alt="Sparkles" width="38" height="38" /> + Презентація Dotfiles Hyprland від KooL + <img src="https://github.com/JaKooLit/Telegram-Animated-Emojis/blob/main/Activity/Sparkles.webp" alt="Sparkles" width="38" height="38" /> +</h3> + +<div align="center"> + +https://github.com/user-attachments/assets/49bc12b2-abaf-45de-a21c-67aacd9bb872 + +</div> + +### 📹 Відеоогляди +- у кінці сторінки + +### 🎞️ Демо AGS Overview +- якщо вам цікаво, ось коротке демо AGS overview [Посилання на YouTube](https://youtu.be/zY5SLNPBJTs) + +</details> + +--- +[](https://git.io/typing-svg) +### 🚩 🏁 Автоматичні скрипти встановлення Hyprland для дистрибутивів, клонування та запуск 🇵🇭 +> [!УВАГА] +> Якщо ви використовуєте FISH SHELL, НЕ використовуйте цю функцію. Натомість клонуйте Distro-Hyprland і запустіть install.sh. + +- ПРИМІТКА: для роботи потрібен пакет `curl` + +```bash +sh <(curl -L https://raw.githubusercontent.com/JaKooLit/Hyprland-Dots/main/Distro-Hyprland.sh) +``` + +- тепер ви можете використовувати наведену вище команду для автоматичного клонування скриптів встановлення Distro-Hyprland, зазначених нижче +- вона клонує скрипти встановлення та запускає `install.sh` 😎 + +### 👁️🗨️ Мої скрипти встановлення Hyprland 👁️🗨️ +- Автоматичні скрипти Hyprland для обраного дистрибутива, які завантажать ці dotfiles, якщо ви оберете встановлення цих конфігурацій + +- [Arch-Linux](https://github.com/JaKooLit/Arch-Hyprland) +- [OpenSUSE(Tumbleweed)](https://github.com/JaKooLit/OpenSuse-Hyprland) +- [Fedora-Linux](https://github.com/JaKooLit/Fedora-Hyprland) +- [Debian-Linux (Trixie & SID)](https://github.com/JaKooLit/Debian-Hyprland) +- [NixOS](https://github.com/JaKooLit/NixOS-Hyprland) +- [Ubuntu 24.04 LTS](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/24.04) +- [Ubuntu 24.10](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/24.10) +- [Ubuntu 25.04 - (АЛЬФА-ЕТАП)](https://github.com/JaKooLit/Ubuntu-Hyprland/tree/25.04) + +--- + +### 🪧 Увага 🪧 +- Цей репозиторій НЕ містить і НЕ встановлює жодних пакетів. Це лише попередньо налаштовані конфігурації Hyprland або dotfiles +- зверніться до скриптів встановлення, щоб дізнатися, які пакети потрібно встановити... але принаймні пакети Hyprland потрібні 😏😏😏 очевидно!! +- Цей репозиторій буде завантажено скриптами встановлення Distro-Hyprland, зазначеними вище, якщо ви оберете завантаження попередньо налаштованих dotfiles + +### 👀 Скріншоти 👀 +- Усі скріншоти зібрано тут [Скріншоти](https://github.com/JaKooLit/screenshots/tree/main/Hyprland-ScreenShots) + +### 📦 Що нового? +- Щоб легко відстежувати зміни, я оновлюватиму [Журнал змін](https://github.com/JaKooLit/Hyprland-Dots/wiki/Changelogs). Скріншоти будуть додані, якщо зміни варті згадки! + +> [!ПРИМІТКА] +> Зверніть увагу, що за замовчуванням dotfiles від KooL налаштовані для дисплеїв 2k (1440p) без масштабування. + +### 💥 Інструкції з копіювання / встановлення / оновлення 💥 +- [`БІЛЬШЕ ІНФОРМАЦІЇ ТУТ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Install_&_Update) +> [!Примітака] +> Автоматичний скрипт копіювання „copy.sh“ створює резервні копії директорій, які будуть скопійовані. Проте рекомендується зробити резервну копію вручну на випадок, якщо скрипт не зможе цього зробити! + +- клонуйте цей репозиторій за допомогою git. Перейдіть до директорії, зробіть скрипт виконуваним і запустіть його + +> для завантаження з гілки Master +```bash +git clone --depth=1 https://github.com/JaKooLit/Hyprland-Dots.git +cd Hyprland-Dots +``` + +> для завантаження з гілки Development (розробка та тестування) +```bash +git clone --depth=1 https://github.com/JaKooLit/Hyprland-Dots.git -b development +cd Hyprland-Dots +``` + +- автоматичне копіювання/встановлення попередньо налаштованих dotfiles (рекомендується для оновлень) +```bash +chmod +x copy.sh +./copy.sh +``` + +- для копіювання/встановлення з релізів (стабільні) (примітка: це на одну версію старше, ніж у основній гілці) +```bash +chmod +x release.sh +./release.sh +``` + +- Функція UPGRADE.sh +> [!ВАЖЛИВО] +> Для роботи потрібен rsync +> у вас уже має бути налаштований і запущений Hyprland від KooL перед використанням цієї функції +```bash +chmod +x upgrade.sh +./upgrade.sh +``` + +## ❗❗❗ УВАГА ДЛЯ КОРИСТУВАЧІВ DEBIAN ТА UBUNTU! +- Я отримую величезну кількість повідомлень щодо оновлення dotfiles Hyprland від KooL. Я зробив велику примітку у [`ВІКІ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Install_&_Update) + +#### ⚠️⚠️⚠️ УВАГА - РЕЗЕРВНІ КОПІЇ, СТВОРЕНІ СКРИПТОМ +> [!УВАГА] +> copy.sh, release.sh і навіть upgrade.sh створюють резервну копію! +> Перевірте вміст у $HOME/.config вручну +> Видаліть вручну всі непотрібні резервні копії + +#### 🛎️ Невелика примітка про шпалери +- за замовчуванням копіюється лише кілька шпалер (по 1 для темного та світлого режиму, плюс ще 3). Вам буде запропоновано завантажити додаткові шпалери. Ви можете переглянути/перевірити додаткові шпалери за [`ЦИМ`](https://github.com/JaKooLit/Wallpaper-Bank/tree/main/wallpapers) посиланням + +#### ⚠️⚠️⚠️ ОБОВ’ЯЗКОВО! після копіювання / встановлення цих dotfiles ++ Натисніть SUPER W і встановіть шпалери. Це також необхідно для ініціалізації wallust для тем waybar, kitty (tty) і rofi. Однак, якщо ви використовуєте copy.sh або release.sh, початкові шпалери вже будуть встановлені, і цього робити не доведеться + ++ Власники Nvidia. Обов’язково відредагуйте `~/.config/hypr/UserConfigs/ENVariables.conf` (настійно рекомендується). +- Користувачі / власники Nvidia, після встановлення перевірте [`ЦЕ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Notes_to_remember#--for-nvidia-gpu-users) + ++ Якщо ви вже налаштували власні гарячі клавіші, монітори тощо, просто скопіюйте їх із створеної резервної копії перед виходом із системи або перезавантаженням. (рекомендується) + +#### 📖 Відомі проблеми та можливі рішення +- перегляньте цю сторінку [Поширені запитання](https://github.com/JaKooLit/Hyprland-Dots/wiki/FAQ) та [НЕРОЗВ’ЯЗАНІ ПРОБЛЕМИ](https://github.com/JaKooLit/Hyprland-Dots/wiki/Known_Issues) + +#### 🙋 ЗАПИТАННЯ ?!?! ⁉️ +- Поширені запитання! Так, ви можете використовувати ці dotfiles на інших дистрибутивах! Просто переконайтеся, що спочатку встановлено відповідні пакети! Якщо вам від цього легше, я використовую ту саму конфігурацію на моєму Gentoo :) +- ШВИДКА ПОРАДА! Натисніть на модуль HINT! у Waybar (примітка: доступно лише у стандартному та Simple-L [ВЕРХНЬОМУ] макеті Waybar). Можна запустити за допомогою гарячої клавіші `SUPER H` +- Ще запитання? натисніть тут, щоб переглянути цю [ВІКІ](https://github.com/JaKooLit/Hyprland-Dots/wiki/) +- Якщо вам потрібні старі конфігурації, вони зібрані в моєму репозиторії „Archive“. Дивіться [ТУТ](https://github.com/JaKooLit/Hyprland-Dots-releases-Archive) + +#### ⌨ Гарячі клавіші +- Гарячі клавіші [`КЛІКНІТЬ`](https://github.com/JaKooLit/Hyprland-Dots/wiki/Keybinds) + +#### 🙏 Особливе прохання +- Якщо у вас є покращення для dotfiles або конфігурацій, не соромтеся надіслати PR для покращень. Я завжди вітаю покращення, адже я також вчуся, як і ви! + +#### ✍️ Внесок +- Хочете зробити внесок? Клікніть [`ТУТ`](https://github.com/JaKooLit/Hyprland-Dots/blob/main/CONTRIBUTING.md) для посібника зі внесення внеску + +#### 🤷♂️ ЩО РОБИТИ! +- [ ] Налаштування dotfiles - 🚧 у постійному прогресі +- ~~[ ] Можливо, перехід на starship? Хоча у starship обмежені теми порівняно з oh-my-zsh.~~ поки що планів немає + +#### 🔮 Сервер Discord +- запрошую приєднатися до мого [Discord](https://discord.com/invite/kool-tech-world) + +#### 💖 Підтримка +- зірка на моїх репозиторіях GitHub була б чудовою 🌟 + +- Підпишіться на мій канал YouTube [YouTube](https://www.youtube.com/@Ja.KooLit) + +- також ви можете підтримати через каву або btc 😊 + +[](https://ko-fi.com/jakoolit) + +або + +[](https://www.buymeacoffee.com/JaKooLit) + +Або ви можете пожертвувати криптовалюту на мій btc-гаманець :) +> 1N3MeV2dsX6gQB42HXU6MF2hAix1mqjo8i + + + +## 🫰 Дякую за зірки 🩷 +[](https://starchart.cc/JaKooLit/Hyprland-Dots)
\ No newline at end of file |
