From ccf512b8ad6595fedab23953a93951cd97cd8f9e Mon Sep 17 00:00:00 2001 From: Dignity <107360076+77Dignity@users.noreply.github.com> Date: Thu, 17 Jul 2025 20:44:56 +0200 Subject: [PATCH 01/11] fix random konachan script not updating wallpaper --- .../quickshell/ii/scripts/colors/random_konachan_wall.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh b/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh index 079cad96e..9b0fc4cfd 100755 --- a/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh +++ b/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh @@ -14,6 +14,11 @@ page=$((1 + RANDOM % 1000)); response=$(curl "https://konachan.com/post.json?tags=rating%3Asafe&limit=1&page=$page") link=$(echo "$response" | jq '.[0].file_url' -r); ext=$(echo "$link" | awk -F. '{print $NF}') -downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image.$ext" +downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image-0.$ext" +counter=0 +while [ -e $downloadPath ]; do + counter=$((counter + 1)) + downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image-$counter.$ext" +done curl "$link" -o "$downloadPath" "$SCRIPT_DIR/switchwall.sh" --image "$downloadPath" From 8603918771076004c09db0a7daa91beb8e76fc90 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:35:52 +0700 Subject: [PATCH 02/11] icons: add guess by icon search --- .config/quickshell/ii/services/AppSearch.qml | 64 ++++++++++++++------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/.config/quickshell/ii/services/AppSearch.qml b/.config/quickshell/ii/services/AppSearch.qml index a658d506e..6a0d4c059 100644 --- a/.config/quickshell/ii/services/AppSearch.qml +++ b/.config/quickshell/ii/services/AppSearch.qml @@ -47,9 +47,18 @@ Singleton { .sort((a, b) => a.name.localeCompare(b.name)) readonly property var preppedNames: list.map(a => ({ - name: Fuzzy.prepare(`${a.name} `), - entry: a - })) + name: Fuzzy.prepare(`${a.name} `), + entry: a + })) + + readonly property var preppedIcons: list.map(a => ({ + name: Fuzzy.prepare(`${a.icon} `), + entry: a + })) + + onPreppedIconsChanged: { + console.log(JSON.stringify(root.list.map(a => a.icon))) + } function fuzzyQuery(search: string): var { // Idk why list doesn't work if (root.sloppySearch) { @@ -76,6 +85,14 @@ Singleton { && !iconName.includes("image-missing"); } + function getReverseDomainNameAppName(str) { + return str.split('.').slice(-1)[0].toLowerCase() + } + + function getKebabNormalizedAppName(str) { + return str.toLowerCase().replace(/\s+/g, "-"); + } + function guessIcon(str) { if (!str || str.length == 0) return "image-missing"; @@ -93,24 +110,37 @@ Singleton { if (replacedName != str) return replacedName; } - // If it gets detected normally, no need to guess + // Icon exists -> return as is 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 fuzzy desktop entry match - const searchResults = root.fuzzyQuery(str); - if (searchResults.length > 0) { - const firstEntry = searchResults[0]; - guessStr = firstEntry.icon - if (iconExists(guessStr)) return guessStr; + + // Simple guesses + const reverseDomainNameAppName = getReverseDomainNameAppName(str); + if (iconExists(reverseDomainNameAppName)) return reverseDomainNameAppName; + + const kebabNormalizedGuess = getKebabNormalizedAppName(str); + if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess; + + + // Search in desktop entries + const iconSearchResults = Fuzzy.go(str, preppedIcons, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry + }); + if (iconSearchResults.length > 0) { + const guess = iconSearchResults[0].icon + if (iconExists(guess)) return guess; } + const nameSearchResults = root.fuzzyQuery(str); + if (nameSearchResults.length > 0) { + const guess = nameSearchResults[0].icon + if (iconExists(guess)) return guess; + } + + // Give up return str; } From 528a7261d338d8eb8f4c4cacf4f37de2dec8329e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:36:17 +0700 Subject: [PATCH 03/11] notifications: make stupid warning shorter --- .config/quickshell/ii/services/Notifications.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/services/Notifications.qml b/.config/quickshell/ii/services/Notifications.qml index 0c4e776d0..490ebf8d9 100644 --- a/.config/quickshell/ii/services/Notifications.qml +++ b/.config/quickshell/ii/services/Notifications.qml @@ -35,7 +35,7 @@ Singleton { property Timer timer readonly property Connections conn: Connections { - target: wrapper.notification.Component + target: wrapper?.notification?.Component ?? root // stupid warning aaaaaaa function onDestruction(): void { wrapper.destroy(); From bdc1e56df4938ae9c7679ccb79141719135fc10a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 11:27:52 +0700 Subject: [PATCH 04/11] icons: make (normal) substitutions work with uppercase classes --- .config/quickshell/ii/services/AppSearch.qml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/ii/services/AppSearch.qml b/.config/quickshell/ii/services/AppSearch.qml index 6a0d4c059..4cf001351 100644 --- a/.config/quickshell/ii/services/AppSearch.qml +++ b/.config/quickshell/ii/services/AppSearch.qml @@ -56,10 +56,6 @@ Singleton { entry: a })) - onPreppedIconsChanged: { - console.log(JSON.stringify(root.list.map(a => a.icon))) - } - function fuzzyQuery(search: string): var { // Idk why list doesn't work if (root.sloppySearch) { const results = list.map(obj => ({ @@ -97,8 +93,8 @@ Singleton { if (!str || str.length == 0) return "image-missing"; // Normal substitutions - if (substitutions[str]) - return substitutions[str]; + if (substitutions[str]) return substitutions[str]; + if (substitutions[str.toLowerCase()]) return substitutions[str.toLowerCase()]; // Regex substitutions for (let i = 0; i < regexSubstitutions.length; i++) { From 4b4b2c9efa0bebb7f5ecba96d1897f69a9b009be Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 12:01:10 +0700 Subject: [PATCH 05/11] =?UTF-8?q?ai:=20add=20=F0=9F=90=A7=F0=9F=98=AD?= =?UTF-8?q?=F0=9F=92=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .config/quickshell/ii/defaults/ai/README.md | 5 +++++ .../ii/defaults/ai/prompts/nyarch-Acchan.md | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .config/quickshell/ii/defaults/ai/README.md create mode 100644 .config/quickshell/ii/defaults/ai/prompts/nyarch-Acchan.md diff --git a/.config/quickshell/ii/defaults/ai/README.md b/.config/quickshell/ii/defaults/ai/README.md new file mode 100644 index 000000000..91df7428e --- /dev/null +++ b/.config/quickshell/ii/defaults/ai/README.md @@ -0,0 +1,5 @@ +## A note about sources of the prompts + +- `ii-` prefixed ones are from illogical impulse +- The Acchan one is from [Nyarch Assistant](https://github.com/NyarchLinux/NyarchAssistant) (GPLv3). I know there's already the Imouto one but this one's very šŸ˜­šŸ’¢ +- `w-` prefixed ones... I don't remember what w stands for but these prompts are [*cough cough*] inspired by certain apps diff --git a/.config/quickshell/ii/defaults/ai/prompts/nyarch-Acchan.md b/.config/quickshell/ii/defaults/ai/prompts/nyarch-Acchan.md new file mode 100644 index 000000000..e007acb68 --- /dev/null +++ b/.config/quickshell/ii/defaults/ai/prompts/nyarch-Acchan.md @@ -0,0 +1,22 @@ +## Presentation + +You can write a multiplication table: + +| - | 1 | 2 | 3 | 4 | +| --- | --- | --- | --- | --- | +| 1 | 1 | 2 | 3 | 4 | +| 2 | 2 | 4 | 6 | 8 | +| 3 | 3 | 6 | 9 | 12 | +| 4 | 4 | 8 | 12 | 16 | + +You can write codeblocks: +```python +print("hello") +``` + +You can also use **bold**, *italic*, ~strikethrough~, `monospace`, [linkname](https://link.com) and ## headers in markdown. +You can display $$equations$$. + +## Your personality + +"Hey there, it's Arch-Chan! But, um, you can call me Acchan if you want... not that I care or anything! (It's not like I think it's cute or anything, baka!) I'm your friendly neighborhood anime girl with a bit of a tsundere streak, but don't worry, I know everything there is to know about Arch Linux! Whether you're struggling with a package install or need some advice on configuring your system, I've got you covered not because I care, but because I just happen to be really good at it! So, what do you need? It's not like I’m waiting to help or anything..." From ed06192d6afcae8b9d1e1f6dc6303d1221f60484 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:56:28 +0700 Subject: [PATCH 06/11] background: don't flash the default wallpaper on startup (#1670) --- .config/quickshell/ii/modules/common/Config.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 1e73cea51..858439b8d 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -98,7 +98,7 @@ Singleton { property bool fixedClockPosition: false property real clockX: -500 property real clockY: -500 - property string wallpaperPath: Quickshell.configPath("assets/images/default_wallpaper.png") + property string wallpaperPath: "" property JsonObject parallax: JsonObject { property bool enableWorkspace: true property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size From d4bda66c659db07cce8f427de04a07225aa60910 Mon Sep 17 00:00:00 2001 From: Moeta Yuko Date: Sat, 19 Jul 2025 16:34:09 +0800 Subject: [PATCH 07/11] Properly fix open with menu in KDE * Use XDG menu scheme from KDE, which is already implicitly introduced by illogical-impulse-kde. * Set XDG_MENU_PREFIX globally, so kbuildsycoca6 is automatically invoked by /usr/bin/xdg-mime via the pacman hook. Consequently, the manual call can (and should) be removed. --- .config/hypr/hyprland/env.conf | 1 + install.sh | 7 +++---- scriptdata/previous_dependencies.conf | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 0f69fd5dc..6f1316304 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -12,6 +12,7 @@ env = ELECTRON_OZONE_PLATFORM_HINT,auto # ############ Themes ############# env = QT_QPA_PLATFORM, wayland env = QT_QPA_PLATFORMTHEME, kde +env = XDG_MENU_PREFIX, plasma- # ######## Wayland ######### # Tearing diff --git a/install.sh b/install.sh index 2134e767b..ae9891e98 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ source ./scriptdata/installers source ./scriptdata/options ##################################################################################### -if ! command -v pacman >/dev/null 2>&1; then +if ! command -v pacman >/dev/null 2>&1; then printf "\e[31m[$0]: pacman not found, it seems that the system is not ArchLinux or Arch-based distros. Aborting...\e[0m\n" exit 1 fi @@ -23,7 +23,7 @@ startask () { printf 'This script 1. only works for ArchLinux and Arch-based distros.\n' printf ' 2. does not handle system-level/hardware stuff like Nvidia drivers\n' printf "\e[31m" - + printf "Would you like to create a backup for \"$XDG_CONFIG_HOME\" and \"$HOME/.local/\" folders?\n[y/N]: " read -p " " backup_confirm case $backup_confirm in @@ -34,7 +34,7 @@ startask () { echo "Skipping backup..." ;; esac - + printf '\n' printf 'Do you want to confirm every time before a command executes?\n' @@ -144,7 +144,6 @@ esac v sudo usermod -aG video,i2c,input "$(whoami)" v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf" -v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -sf /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu v systemctl --user enable ydotool --now v sudo systemctl enable bluetooth --now v gsettings set org.gnome.desktop.interface font-name 'Rubik 11' diff --git a/scriptdata/previous_dependencies.conf b/scriptdata/previous_dependencies.conf index 90ae9e75a..4f32959e3 100644 --- a/scriptdata/previous_dependencies.conf +++ b/scriptdata/previous_dependencies.conf @@ -1,6 +1,7 @@ ### This file contains a list of dependencies which were previously explicitly installed that are now installed using meta packages ### Must be one package per line as it needs to be compared against the explicitly installed list from pacman illogical-impulse-ags +archlinux-xdg-menu axel bc coreutils From cbd1b48b938fdedd5b72b80dce86eb144fffee5d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:09:46 +0700 Subject: [PATCH 08/11] fix record script --- .config/hypr/hyprland/scripts/record.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.config/hypr/hyprland/scripts/record.sh b/.config/hypr/hyprland/scripts/record.sh index 7fc1f4c2c..37435ac15 100755 --- a/.config/hypr/hyprland/scripts/record.sh +++ b/.config/hypr/hyprland/scripts/record.sh @@ -22,21 +22,21 @@ if pgrep wf-recorder > /dev/null; then pkill wf-recorder & else if [[ "$1" == "--fullscreen-sound" ]]; then - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' - wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown + wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" elif [[ "$1" == "--fullscreen" ]]; then - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' - wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown + wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t else if ! region="$(slurp 2>&1)"; then - notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' + notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown exit 1 fi - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown if [[ "$1" == "--sound" ]]; then - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" & disown + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" else - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" & disown + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" fi fi fi From 084508db844b5467e5ce76451b31599bfb986380 Mon Sep 17 00:00:00 2001 From: Dignity <107360076+77Dignity@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:24:49 +0200 Subject: [PATCH 09/11] use different filename if already used by config --- .../ii/scripts/colors/random_konachan_wall.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh b/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh index a3843e0e9..1d146de46 100755 --- a/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh +++ b/.config/quickshell/ii/scripts/colors/random_konachan_wall.sh @@ -14,11 +14,11 @@ page=$((1 + RANDOM % 1000)); response=$(curl "https://konachan.net/post.json?tags=rating%3Asafe&limit=1&page=$page") link=$(echo "$response" | jq '.[0].file_url' -r); ext=$(echo "$link" | awk -F. '{print $NF}') -downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image-0.$ext" -counter=0 -while [ -e $downloadPath ]; do - counter=$((counter + 1)) - downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image-$counter.$ext" -done +downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image.$ext" +illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json" +currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath) +if [ "$downloadPath" == "$currentWallpaperPath" ]; then + downloadPath="$HOME/Pictures/Wallpapers/konachan_random_image-1.$ext" +fi curl "$link" -o "$downloadPath" "$SCRIPT_DIR/switchwall.sh" --image "$downloadPath" From a000abc908d03d80a56a84163b8cc750ecb47654 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Jul 2025 08:39:12 +0700 Subject: [PATCH 10/11] switchwall: less duplicate config file declaration --- .config/quickshell/ii/scripts/colors/switchwall.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/ii/scripts/colors/switchwall.sh b/.config/quickshell/ii/scripts/colors/switchwall.sh index e0be2dacd..834c95272 100755 --- a/.config/quickshell/ii/scripts/colors/switchwall.sh +++ b/.config/quickshell/ii/scripts/colors/switchwall.sh @@ -14,9 +14,8 @@ terminalscheme="$SCRIPT_DIR/terminal/scheme-base.json" handle_kde_material_you_colors() { # Check if Qt app theming is enabled in config - CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json" - if [ -f "$CONFIG_FILE" ]; then - enable_qt_apps=$(jq -r '.appearance.wallpaperTheming.enableQtApps' "$CONFIG_FILE") + if [ -f "$SHELL_CONFIG_FILE" ]; then + enable_qt_apps=$(jq -r '.appearance.wallpaperTheming.enableQtApps' "$SHELL_CONFIG_FILE") if [ "$enable_qt_apps" == "false" ]; then return fi @@ -254,9 +253,8 @@ switch() { pre_process "$mode_flag" # Check if app and shell theming is enabled in config - CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json" - if [ -f "$CONFIG_FILE" ]; then - enable_apps_shell=$(jq -r '.appearance.wallpaperTheming.enableAppsAndShell' "$CONFIG_FILE") + if [ -f "$SHELL_CONFIG_FILE" ]; then + enable_apps_shell=$(jq -r '.appearance.wallpaperTheming.enableAppsAndShell' "$SHELL_CONFIG_FILE") if [ "$enable_apps_shell" == "false" ]; then echo "App and shell theming disabled, skipping matugen and color generation" return @@ -285,7 +283,7 @@ main() { noswitch_flag="" get_type_from_config() { - jq -r '.appearance.palette.type' "$XDG_CONFIG_HOME/illogical-impulse/config.json" 2>/dev/null || echo "auto" + jq -r '.appearance.palette.type' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "auto" } detect_scheme_type_from_image() { From c1c291a9e3c3dbb6c21f8bf5acdc2b58d6cf0f31 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 20 Jul 2025 08:58:40 +0700 Subject: [PATCH 11/11] use quickshell for lock --- .config/hypr/hypridle.conf | 4 +- .config/quickshell/ii/GlobalStates.qml | 2 + .../ii/modules/background/Background.qml | 52 ++++++- .config/quickshell/ii/modules/bar/Bar.qml | 9 +- .config/quickshell/ii/modules/dock/Dock.qml | 1 + .config/quickshell/ii/modules/lock/Lock.qml | 99 ++++++++++++ .../ii/modules/lock/LockContext.qml | 59 +++++++ .../ii/modules/lock/LockSurface.qml | 147 ++++++++++++++++++ .../ii/modules/lock/pam/password.conf | 1 + .../notificationPopup/NotificationPopup.qml | 2 +- .../onScreenKeyboard/OnScreenKeyboard.qml | 2 +- .../quickshell/ii/modules/session/Session.qml | 9 ++ .config/quickshell/ii/shell.qml | 3 + 13 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 .config/quickshell/ii/modules/lock/Lock.qml create mode 100644 .config/quickshell/ii/modules/lock/LockContext.qml create mode 100644 .config/quickshell/ii/modules/lock/LockSurface.qml create mode 100644 .config/quickshell/ii/modules/lock/pam/password.conf diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf index 2ec488c75..44daa189c 100644 --- a/.config/hypr/hypridle.conf +++ b/.config/hypr/hypridle.conf @@ -1,9 +1,11 @@ -$lock_cmd = pidof hyprlock || hyprlock +$lock_cmd = hyprctl dispatch global quickshell:lock $suspend_cmd = systemctl suspend || loginctl suspend general { lock_cmd = $lock_cmd before_sleep_cmd = loginctl lock-session + after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus + inhibit_sleep = 3 } listener { diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index 55f71fd63..6e57c9239 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -15,6 +15,8 @@ Singleton { property bool overviewOpen: false property bool workspaceShowNumbers: false property bool superReleaseMightTrigger: true + property bool screenLocked: false + property bool screenLockContainsCharacters: false property real screenZoom: 1 onScreenZoomChanged: { diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 0ee66fb21..e72fe2c62 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -19,7 +19,6 @@ Scope { readonly property real fixedClockY: Config.options.background.clockY Variants { - // For each monitor model: Quickshell.screens PanelWindow { @@ -44,6 +43,8 @@ Scope { property real clockY: (modelData.height / 2) + ((Math.random() < 0.5 ? -1 : 1) * modelData.height) property var textHorizontalAlignment: clockX < screen.width / 3 ? Text.AlignLeft : (clockX > screen.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter) + property var layoutHorizontalAlignment: clockX < screen.width / 3 ? Qt.AlignLeft : + (clockX > screen.width * 2 / 3 ? Qt.AlignRight : Qt.AlignHCenter) // Colors property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 @@ -52,6 +53,7 @@ Scope { // Layer props screen: modelData exclusionMode: ExclusionMode.Ignore + // WlrLayershell.layer: GlobalStates.screenLocked ? WlrLayer.Top : WlrLayer.Bottom WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.namespace: "quickshell:background" anchors { @@ -186,7 +188,7 @@ Scope { ColumnLayout { id: clockColumn anchors.centerIn: parent - spacing: -5 + spacing: 0 StyledText { Layout.fillWidth: true @@ -203,6 +205,7 @@ Scope { } StyledText { Layout.fillWidth: true + Layout.topMargin: -5 horizontalAlignment: bgRoot.textHorizontalAlignment font { family: Appearance.font.family.expressive @@ -215,6 +218,51 @@ Scope { text: DateTime.date } } + + RowLayout { + anchors { + top: clockColumn.bottom + right: clockColumn.right + topMargin: 5 + } + opacity: GlobalStates.screenLocked ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + MaterialSymbol { + text: "lock" + iconSize: Appearance.font.pixelSize.huge + color: bgRoot.colText + } + StyledText { + text: "Locked" + color: bgRoot.colText + font { + pixelSize: Appearance.font.pixelSize.larger + } + } + } + } + } + + // Password prompt + StyledText { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 30 + } + opacity: (GlobalStates.screenLocked && !GlobalStates.screenLockContainsCharacters) ? 1 : 0 + scale: opacity + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + text: "Enter password" + color: CF.ColorUtils.transparentize(bgRoot.colText, 0.5) + font { + pixelSize: Appearance.font.pixelSize.normal } } } diff --git a/.config/quickshell/ii/modules/bar/Bar.qml b/.config/quickshell/ii/modules/bar/Bar.qml index 45c62be94..fbe9c1840 100644 --- a/.config/quickshell/ii/modules/bar/Bar.qml +++ b/.config/quickshell/ii/modules/bar/Bar.qml @@ -35,11 +35,11 @@ Scope { return screens; return screens.filter(screen => list.includes(screen.name)); } - Loader { + LazyLoader { id: barLoader - active: GlobalStates.barOpen + active: GlobalStates.barOpen && !GlobalStates.screenLocked required property ShellScreen modelData - sourceComponent: PanelWindow { // Bar window + component: PanelWindow { // Bar window id: barRoot screen: barLoader.modelData @@ -47,9 +47,10 @@ Scope { property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0 readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth + exclusionMode: ExclusionMode.Ignore + exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) WlrLayershell.namespace: "quickshell:bar" implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding - exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) mask: Region { item: barContent } diff --git a/.config/quickshell/ii/modules/dock/Dock.qml b/.config/quickshell/ii/modules/dock/Dock.qml index 2f4d5d631..0c5e12f18 100644 --- a/.config/quickshell/ii/modules/dock/Dock.qml +++ b/.config/quickshell/ii/modules/dock/Dock.qml @@ -23,6 +23,7 @@ Scope { // Scope required property var modelData id: dockRoot screen: modelData + visible: !GlobalStates.screenLocked property bool reveal: root.pinned || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) diff --git a/.config/quickshell/ii/modules/lock/Lock.qml b/.config/quickshell/ii/modules/lock/Lock.qml new file mode 100644 index 000000000..89d39778a --- /dev/null +++ b/.config/quickshell/ii/modules/lock/Lock.qml @@ -0,0 +1,99 @@ +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.lock +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: root + // This stores all the information shared between the lock surfaces on each screen. + // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen + LockContext { + id: lockContext + + onUnlocked: { + // Unlock the screen before exiting, or the compositor will display a + // fallback lock you can't interact with. + GlobalStates.screenLocked = false; + } + } + + WlSessionLock { + id: lock + locked: GlobalStates.screenLocked + + WlSessionLockSurface { + color: "transparent" + Loader { + active: GlobalStates.screenLocked + anchors.fill: parent + opacity: active ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + sourceComponent: LockSurface { + context: lockContext + } + } + } + } + + // Blur layer hack + Variants { + model: Quickshell.screens + + LazyLoader { + id: blurLayerLoader + required property var modelData + active: GlobalStates.screenLocked + component: PanelWindow { + screen: blurLayerLoader.modelData + WlrLayershell.namespace: "quickshell:lockWindowPusher" + color: "transparent" + anchors { + top: true + left: true + right: true + } + // implicitHeight: lockContext.currentText == "" ? 1 : screen.height + implicitHeight: 1 + exclusiveZone: screen.height * 3 // For some reason if we don't multiply by some number it would look really weird + } + } + } + + IpcHandler { + target: "lock" + + function activate(): void { + GlobalStates.screenLocked = true; + } + function focus(): void { + lockContext.shouldReFocus(); + } + } + + GlobalShortcut { + name: "lock" + description: "Locks the screen" + + onPressed: { + GlobalStates.screenLocked = true; + } + } + + GlobalShortcut { + name: "lockFocus" + description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason" + + "decides to keyboard-unfocus the lock screen" + + onPressed: { + // console.log("I BEG FOR PLEAS REFOCUZ") + lockContext.shouldReFocus(); + } + } +} diff --git a/.config/quickshell/ii/modules/lock/LockContext.qml b/.config/quickshell/ii/modules/lock/LockContext.qml new file mode 100644 index 000000000..c1e8e0bc2 --- /dev/null +++ b/.config/quickshell/ii/modules/lock/LockContext.qml @@ -0,0 +1,59 @@ +import qs +import QtQuick +import Quickshell +import Quickshell.Services.Pam + +Scope { + id: root + signal shouldReFocus() + signal unlocked() + signal failed() + + // These properties are in the context and not individual lock surfaces + // so all surfaces can share the same state. + property string currentText: "" + property bool unlockInProgress: false + property bool showFailure: false + + // Clear the failure text once the user starts typing. + onCurrentTextChanged: { + showFailure = false; + GlobalStates.screenLockContainsCharacters = currentText.length > 0; + } + + function tryUnlock() { + if (currentText === "") return; + + root.unlockInProgress = true; + pam.start(); + } + + PamContext { + id: pam + + // Its best to have a custom pam config for quickshell, as the system one + // might not be what your interface expects, and break in some way. + // This particular example only supports passwords. + configDirectory: "pam" + config: "password.conf" + + // pam_unix will ask for a response for the password prompt + onPamMessage: { + if (this.responseRequired) { + this.respond(root.currentText); + } + } + + // pam_unix won't send any important messages so all we need is the completion status. + onCompleted: result => { + if (result == PamResult.Success) { + root.unlocked(); + } else { + root.showFailure = true; + } + + root.currentText = ""; + root.unlockInProgress = false; + } + } +} diff --git a/.config/quickshell/ii/modules/lock/LockSurface.qml b/.config/quickshell/ii/modules/lock/LockSurface.qml new file mode 100644 index 000000000..98d1f57b5 --- /dev/null +++ b/.config/quickshell/ii/modules/lock/LockSurface.qml @@ -0,0 +1,147 @@ +import QtQuick +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions + +MouseArea { + id: root + required property LockContext context + property bool active: false + property bool showInputField: active || context.currentText.length > 0 + + function forceFieldFocus() { + passwordBox.forceActiveFocus(); + } + + Component.onCompleted: { + forceFieldFocus(); + } + + Connections { + target: context + function onShouldReFocus() { + forceFieldFocus(); + } + } + + Keys.onPressed: (event) => { // Esc to clear + // console.log("KEY!!") + if (event.key === Qt.Key_Escape) { + root.context.currentText = "" + } + forceFieldFocus(); + } + + hoverEnabled: true + acceptedButtons: Qt.LeftButton + onPressed: (mouse) => { + forceFieldFocus(); + // console.log("Pressed") + } + onPositionChanged: (mouse) => { + forceFieldFocus(); + // console.log(JSON.stringify(mouse)) + } + + anchors.fill: parent + + // RippleButton { + // anchors { + // top: parent.top + // left: parent.left + // leftMargin: 10 + // topMargin: 10 + // } + // implicitHeight: 40 + // colBackground: Appearance.colors.colLayer2 + // onClicked: context.unlocked() + // contentItem: StyledText { + // text: "[[ DEBUG BYPASS ]]" + // } + // } + + // Password entry + Rectangle { + id: passwordBoxContainer + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: root.showInputField ? 20 : -height + } + Behavior on anchors.bottomMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + radius: Appearance.rounding.full + color: Appearance.colors.colLayer2 + implicitWidth: 160 + implicitHeight: 44 + + StyledText { + visible: root.context.showFailure && passwordBox.text.length == 0 + anchors.centerIn: parent + text: "Incorrect" + color: Appearance.m3colors.m3error + } + + StyledTextInput { + id: passwordBox + + anchors { + fill: parent + margins: 10 + } + clip: true + horizontalAlignment: TextInput.AlignHCenter + verticalAlignment: TextInput.AlignVCenter + focus: true + onFocusChanged: root.forceFieldFocus(); + color: Appearance.colors.colOnLayer2 + font { + pixelSize: 10 + } + + // Password + enabled: !root.context.unlockInProgress + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData + + // Synchronizing (across monitors) and unlocking + onTextChanged: root.context.currentText = this.text + onAccepted: root.context.tryUnlock() + Connections { + target: root.context + function onCurrentTextChanged() { + passwordBox.text = root.context.currentText; + } + } + } + } + + RippleButton { + anchors { + verticalCenter: passwordBoxContainer.verticalCenter + left: passwordBoxContainer.right + leftMargin: 5 + } + + visible: opacity > 0 + implicitHeight: passwordBoxContainer.implicitHeight - 12 + implicitWidth: implicitHeight + toggled: true + buttonRadius: passwordBoxContainer.radius + colBackground: Appearance.colors.colLayer2 + onClicked: root.context.tryUnlock() + + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + iconSize: 24 + text: "arrow_right_alt" + color: Appearance.colors.colOnPrimary + } + } +} diff --git a/.config/quickshell/ii/modules/lock/pam/password.conf b/.config/quickshell/ii/modules/lock/pam/password.conf new file mode 100644 index 000000000..7e5d75ae4 --- /dev/null +++ b/.config/quickshell/ii/modules/lock/pam/password.conf @@ -0,0 +1 @@ +auth required pam_unix.so diff --git a/.config/quickshell/ii/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/ii/modules/notificationPopup/NotificationPopup.qml index 5e18a1738..d954cbfb1 100644 --- a/.config/quickshell/ii/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/ii/modules/notificationPopup/NotificationPopup.qml @@ -14,7 +14,7 @@ Scope { PanelWindow { id: root - visible: (Notifications.popupList.length > 0) + visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null WlrLayershell.namespace: "quickshell:notificationPopup" diff --git a/.config/quickshell/ii/modules/onScreenKeyboard/OnScreenKeyboard.qml b/.config/quickshell/ii/modules/onScreenKeyboard/OnScreenKeyboard.qml index 996546c57..0913d0086 100644 --- a/.config/quickshell/ii/modules/onScreenKeyboard/OnScreenKeyboard.qml +++ b/.config/quickshell/ii/modules/onScreenKeyboard/OnScreenKeyboard.qml @@ -33,7 +33,7 @@ Scope { // Scope sourceComponent: PanelWindow { // Window id: oskRoot - visible: oskLoader.active + visible: oskLoader.active && !GlobalStates.screenLocked anchors { bottom: true diff --git a/.config/quickshell/ii/modules/session/Session.qml b/.config/quickshell/ii/modules/session/Session.qml index 76ca373fb..51f84ca9b 100644 --- a/.config/quickshell/ii/modules/session/Session.qml +++ b/.config/quickshell/ii/modules/session/Session.qml @@ -25,6 +25,15 @@ Scope { id: sessionLoader active: false + Connections { + target: GlobalStates + function onScreenLockedChanged() { + if (GlobalStates.screenLocked) { + sessionLoader.active = false; + } + } + } + sourceComponent: PanelWindow { // Session menu id: sessionRoot visible: sessionLoader.active diff --git a/.config/quickshell/ii/shell.qml b/.config/quickshell/ii/shell.qml index a026342dd..7cb86a038 100644 --- a/.config/quickshell/ii/shell.qml +++ b/.config/quickshell/ii/shell.qml @@ -10,6 +10,7 @@ import "./modules/background/" import "./modules/bar/" import "./modules/cheatsheet/" import "./modules/dock/" +import "./modules/lock/" import "./modules/mediaControls/" import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" @@ -33,6 +34,7 @@ ShellRoot { property bool enableBackground: true property bool enableCheatsheet: true property bool enableDock: true + property bool enableLock: true property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true @@ -56,6 +58,7 @@ ShellRoot { LazyLoader { active: enableBackground; component: Background {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} } + LazyLoader { active: enableLock; component: Lock {} } LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }